@driftdev/cli 1.12.1 → 1.13.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.
Files changed (2) hide show
  1. package/dist/drift.js +78 -47
  2. package/package.json +4 -4
package/dist/drift.js CHANGED
@@ -52,7 +52,7 @@ var init_global = () => {};
52
52
 
53
53
  // src/drift.ts
54
54
  import { readFileSync as readFileSync23 } from "node:fs";
55
- import * as path34 from "node:path";
55
+ import * as path35 from "node:path";
56
56
  import { fileURLToPath as fileURLToPath2 } from "node:url";
57
57
  import { Command } from "commander";
58
58
 
@@ -4271,7 +4271,7 @@ function registerInitCommand(program) {
4271
4271
  }
4272
4272
 
4273
4273
  // src/commands/lint.ts
4274
- import * as path28 from "node:path";
4274
+ import * as path29 from "node:path";
4275
4275
  import { buildExportRegistry, computeDrift as computeDrift4, detectProseDrift } from "@driftdev/sdk";
4276
4276
 
4277
4277
  // src/formatters/lint.ts
@@ -4309,6 +4309,13 @@ function renderLint(data, next) {
4309
4309
  }
4310
4310
 
4311
4311
  // src/utils/annotations.ts
4312
+ import * as path28 from "node:path";
4313
+ function annotationPath(filePath) {
4314
+ if (!path28.isAbsolute(filePath))
4315
+ return filePath;
4316
+ const rel = path28.relative(process.cwd(), filePath);
4317
+ return rel.startsWith("..") ? filePath : rel;
4318
+ }
4312
4319
  function escapeData(value) {
4313
4320
  return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
4314
4321
  }
@@ -4319,7 +4326,7 @@ function emitAnnotations(issues, level = "error") {
4319
4326
  for (const issue of issues) {
4320
4327
  const props = [];
4321
4328
  if (issue.filePath)
4322
- props.push(`file=${escapeProperty(issue.filePath)}`);
4329
+ props.push(`file=${escapeProperty(annotationPath(issue.filePath))}`);
4323
4330
  if (issue.line !== undefined)
4324
4331
  props.push(`line=${issue.line}`);
4325
4332
  props.push(`title=${escapeProperty(`drift: ${issue.export || "docs"}`)}`);
@@ -4382,9 +4389,9 @@ function registerLintCommand(program) {
4382
4389
  formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
4383
4390
  return;
4384
4391
  }
4385
- let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
4392
+ let entryFile = entry ? path29.resolve(process.cwd(), entry) : undefined;
4386
4393
  if (lang === "typescript" && !entryFile) {
4387
- entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
4394
+ entryFile = config.entry ? path29.resolve(process.cwd(), config.entry) : detectEntry();
4388
4395
  }
4389
4396
  const { apiSpec: spec } = await resolveTruth({
4390
4397
  entry: entryFile,
@@ -4450,7 +4457,7 @@ function registerLintCommand(program) {
4450
4457
  }
4451
4458
 
4452
4459
  // src/commands/list.ts
4453
- import * as path29 from "node:path";
4460
+ import * as path30 from "node:path";
4454
4461
  import { computeDrift as computeDrift5 } from "@driftdev/sdk";
4455
4462
  import { listExports as listExports2 } from "@openpkg-ts/sdk";
4456
4463
 
@@ -4565,7 +4572,7 @@ function registerListCommand(program) {
4565
4572
  } else {
4566
4573
  let entryFile;
4567
4574
  if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
4568
- entryFile = path29.resolve(process.cwd(), searchOrEntry);
4575
+ entryFile = path30.resolve(process.cwd(), searchOrEntry);
4569
4576
  } else if (searchOrEntry) {
4570
4577
  entryFile = detectEntry();
4571
4578
  searchTerm = searchOrEntry;
@@ -4822,7 +4829,7 @@ function diffArgs(command, args) {
4822
4829
  // src/commands/release.ts
4823
4830
  import { execSync as execSync4 } from "node:child_process";
4824
4831
  import { existsSync as existsSync15, readFileSync as readFileSync20 } from "node:fs";
4825
- import * as path30 from "node:path";
4832
+ import * as path31 from "node:path";
4826
4833
  import { computeDrift as computeDrift6 } from "@driftdev/sdk";
4827
4834
 
4828
4835
  // src/formatters/release.ts
@@ -4870,7 +4877,7 @@ function getLastTag() {
4870
4877
  }
4871
4878
  }
4872
4879
  function getPackageVersion(cwd) {
4873
- const pkgPath = path30.join(cwd, "package.json");
4880
+ const pkgPath = path31.join(cwd, "package.json");
4874
4881
  if (!existsSync15(pkgPath))
4875
4882
  return null;
4876
4883
  try {
@@ -4886,7 +4893,7 @@ function registerReleaseCommand(program) {
4886
4893
  const cwd = process.cwd();
4887
4894
  try {
4888
4895
  const { config } = loadConfig();
4889
- const entryFile = entry ? path30.resolve(cwd, entry) : config.entry ? path30.resolve(cwd, config.entry) : detectEntry();
4896
+ const entryFile = entry ? path31.resolve(cwd, entry) : config.entry ? path31.resolve(cwd, config.entry) : detectEntry();
4890
4897
  const { spec } = await cachedExtract(entryFile);
4891
4898
  const exports = spec.exports ?? [];
4892
4899
  const total = exports.length;
@@ -5011,7 +5018,7 @@ function renderReport(data) {
5011
5018
  // src/utils/scan-packages.ts
5012
5019
  import { execSync as execSync5 } from "node:child_process";
5013
5020
  import { existsSync as existsSync16, readFileSync as readFileSync21 } from "node:fs";
5014
- import * as path31 from "node:path";
5021
+ import * as path32 from "node:path";
5015
5022
  import { computeDrift as computeDrift7 } from "@driftdev/sdk";
5016
5023
  function detectPackageDirs2(cwd) {
5017
5024
  const workspaces = detectWorkspaces(cwd);
@@ -5030,11 +5037,11 @@ async function scanAllPackages(cwd) {
5030
5037
  const packageDirs = detectPackageDirs2(cwd);
5031
5038
  const results = [];
5032
5039
  for (const dir of packageDirs) {
5033
- const absDir = dir === "." ? cwd : path31.join(cwd, dir);
5040
+ const absDir = dir === "." ? cwd : path32.join(cwd, dir);
5034
5041
  if (!existsSync16(absDir))
5035
5042
  continue;
5036
5043
  let name = dir;
5037
- const pkgPath = path31.join(absDir, "package.json");
5044
+ const pkgPath = path32.join(absDir, "package.json");
5038
5045
  if (existsSync16(pkgPath)) {
5039
5046
  try {
5040
5047
  const pkg = JSON.parse(readFileSync21(pkgPath, "utf-8"));
@@ -5133,7 +5140,7 @@ function registerReportCommand(program) {
5133
5140
  }
5134
5141
 
5135
5142
  // src/commands/scan.ts
5136
- import * as path32 from "node:path";
5143
+ import * as path33 from "node:path";
5137
5144
  import {
5138
5145
  buildExportRegistry as buildExportRegistry2,
5139
5146
  computeDrift as computeDrift8,
@@ -5149,13 +5156,15 @@ function renderScan(data, next) {
5149
5156
  lines.push(indent(`${c.bold(data.packageName)}${c.gray(ver)}`));
5150
5157
  lines.push("");
5151
5158
  }
5152
- const hColor = coverageColor(data.health);
5153
- lines.push(indent(`Health ${hColor(`${data.health}%`)}`));
5154
- const externalNote = data.coverage.external ? c.gray(` +${data.coverage.external} external (not resolvable here)`) : "";
5155
- lines.push(indent(`Coverage ${coverageColor(data.coverage.score)(`${data.coverage.score}%`)} (${data.coverage.documented}/${data.coverage.total} exports)${externalNote}`));
5156
- lines.push(indent(`Lint ${data.lint.count === 0 ? c.green("0 issues") : c.red(`${data.lint.count} issues`)}`));
5157
- lines.push("");
5158
- if (data.lint.count > 0) {
5159
+ if (data.health !== undefined && data.coverage && data.lint) {
5160
+ const hColor = coverageColor(data.health);
5161
+ lines.push(indent(`Health ${hColor(`${data.health}%`)}`));
5162
+ const externalNote = data.coverage.external ? c.gray(` +${data.coverage.external} external (not resolvable here)`) : "";
5163
+ lines.push(indent(`Coverage ${coverageColor(data.coverage.score)(`${data.coverage.score}%`)} (${data.coverage.documented}/${data.coverage.total} exports)${externalNote}`));
5164
+ lines.push(indent(`Lint ${data.lint.count === 0 ? c.green("0 issues") : c.red(`${data.lint.count} issues`)}`));
5165
+ lines.push("");
5166
+ }
5167
+ if (data.lint && data.lint.count > 0) {
5159
5168
  lines.push(indent("Issues"));
5160
5169
  lines.push(indent(c.gray(separator())));
5161
5170
  const shown = data.lint.issues.slice(0, 10);
@@ -5215,6 +5224,25 @@ function renderBatchScan(data) {
5215
5224
  }
5216
5225
 
5217
5226
  // src/commands/scan.ts
5227
+ function toDocsCoverageResult(run) {
5228
+ return {
5229
+ pass: run.pass,
5230
+ pages: run.pages.map((p) => ({
5231
+ page: p.page,
5232
+ type: p.type,
5233
+ status: p.status,
5234
+ baselineGaps: p.baselineGaps,
5235
+ counts: p.result.counts,
5236
+ failures: p.failures,
5237
+ warnings: p.warnings,
5238
+ gaps: p.result.gaps,
5239
+ ghosts: p.result.ghosts,
5240
+ inversions: p.result.inversions,
5241
+ documentedKeysFromOtherTypes: p.result.documentedKeysFromOtherTypes,
5242
+ annotated: p.result.annotated
5243
+ }))
5244
+ };
5245
+ }
5218
5246
  function registerScanCommand(program) {
5219
5247
  program.command("scan [entry]").description("Run coverage + lint + prose drift in one pass").option("--min <n>", "Minimum health threshold (exit 1 if below)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").option("--docs-map <file>", "Docs map (page→type) activating key-coverage mode: gaps/ghosts/inversions per page").option("--annotations", "Emit GitHub Actions ::error/::warning annotations for findings").action(async (entry, options) => {
5220
5248
  const startTime = Date.now();
@@ -5294,10 +5322,31 @@ function registerScanCommand(program) {
5294
5322
  process.exitCode = 1;
5295
5323
  return;
5296
5324
  }
5325
+ if (options.docsMap && !entry) {
5326
+ const loaded = loadDocsMap(options.docsMap);
5327
+ if (loaded.map.pages.every((p) => p.spec || p.entry)) {
5328
+ const run = await runDocsCoverage(loaded);
5329
+ const data2 = { pass: run.pass, docsCoverage: toDocsCoverageResult(run) };
5330
+ formatOutput("scan", data2, startTime, version, renderScan);
5331
+ if (options.annotations) {
5332
+ emitAnnotations(run.annotations.errors, "error");
5333
+ emitAnnotations(run.annotations.warnings, "warning");
5334
+ }
5335
+ if (!run.pass) {
5336
+ if (!shouldRenderHuman()) {
5337
+ const covFails = run.pages.flatMap((p) => p.failures.map((f) => `${p.page}: ${f}`));
5338
+ process.stderr.write(`scan failed: docs coverage: ${covFails.join(" | ")}
5339
+ `);
5340
+ }
5341
+ process.exitCode = 1;
5342
+ }
5343
+ return;
5344
+ }
5345
+ }
5297
5346
  const { config } = loadConfig();
5298
- let entryFile = entry ? path32.resolve(process.cwd(), entry) : undefined;
5347
+ let entryFile = entry ? path33.resolve(process.cwd(), entry) : undefined;
5299
5348
  if (lang === "typescript" && !entryFile) {
5300
- entryFile = config.entry ? path32.resolve(process.cwd(), config.entry) : detectEntry();
5349
+ entryFile = config.entry ? path33.resolve(process.cwd(), config.entry) : detectEntry();
5301
5350
  }
5302
5351
  const { apiSpec, packageName, packageVersion } = await resolveTruth({
5303
5352
  entry: entryFile,
@@ -5379,25 +5428,7 @@ function registerScanCommand(program) {
5379
5428
  pass,
5380
5429
  packageName,
5381
5430
  packageVersion,
5382
- ...docsCoverage ? {
5383
- docsCoverage: {
5384
- pass: docsCoverage.pass,
5385
- pages: docsCoverage.pages.map((p) => ({
5386
- page: p.page,
5387
- type: p.type,
5388
- status: p.status,
5389
- baselineGaps: p.baselineGaps,
5390
- counts: p.result.counts,
5391
- failures: p.failures,
5392
- warnings: p.warnings,
5393
- gaps: p.result.gaps,
5394
- ghosts: p.result.ghosts,
5395
- inversions: p.result.inversions,
5396
- documentedKeysFromOtherTypes: p.result.documentedKeysFromOtherTypes,
5397
- annotated: p.result.annotated
5398
- }))
5399
- }
5400
- } : {}
5431
+ ...docsCoverage ? { docsCoverage: toDocsCoverageResult(docsCoverage) } : {}
5401
5432
  };
5402
5433
  let next;
5403
5434
  if (issues.length > 0) {
@@ -5473,7 +5504,7 @@ function registerSemverCommand(program) {
5473
5504
 
5474
5505
  // src/commands/validate.ts
5475
5506
  import { readFileSync as readFileSync22 } from "node:fs";
5476
- import * as path33 from "node:path";
5507
+ import * as path34 from "node:path";
5477
5508
  import { validateSpec } from "@openpkg-ts/spec";
5478
5509
 
5479
5510
  // src/formatters/validate.ts
@@ -5499,7 +5530,7 @@ function registerValidateCommand(program) {
5499
5530
  const startTime = Date.now();
5500
5531
  const version = getVersion();
5501
5532
  try {
5502
- const filePath = path33.resolve(process.cwd(), file);
5533
+ const filePath = path34.resolve(process.cwd(), file);
5503
5534
  const content = readFileSync22(filePath, "utf-8");
5504
5535
  const spec = JSON.parse(content);
5505
5536
  const result = validateSpec(spec);
@@ -5642,13 +5673,13 @@ function extractCapabilities(program) {
5642
5673
 
5643
5674
  // src/drift.ts
5644
5675
  var __filename2 = fileURLToPath2(import.meta.url);
5645
- var __dirname3 = path34.dirname(__filename2);
5646
- var packageJson = JSON.parse(readFileSync23(path34.join(__dirname3, "../package.json"), "utf-8"));
5676
+ var __dirname3 = path35.dirname(__filename2);
5677
+ var packageJson = JSON.parse(readFileSync23(path35.join(__dirname3, "../package.json"), "utf-8"));
5647
5678
  var program = new Command;
5648
5679
  program.name("drift").description("drift — detect when your docs drift from your code").version(packageJson.version).option("--json", "Force JSON output (default when piped)").option("--human", "Force human-readable output (default in terminal)").option("--config <path>", "Path to drift config file").option("--cwd <dir>", "Run as if started in <dir>").option("--no-cache", "Bypass spec cache").option("--tools", "List all available tools for agent use (JSON)").hook("preAction", (_thisCommand) => {
5649
5680
  const opts = program.opts();
5650
5681
  if (opts.cwd) {
5651
- process.chdir(path34.resolve(opts.cwd));
5682
+ process.chdir(path35.resolve(opts.cwd));
5652
5683
  }
5653
5684
  setOutputMode({ json: opts.json, human: opts.human });
5654
5685
  setConfigPath(opts.config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftdev/cli",
3
- "version": "1.12.1",
3
+ "version": "1.13.0",
4
4
  "description": "Drift CLI - detect when your docs drift from your code",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,10 +47,10 @@
47
47
  "dependencies": {
48
48
  "@driftdev/clarity-adapter": "^1.0.1",
49
49
  "@driftdev/openapi-adapter": "^1.0.1",
50
- "@driftdev/sdk": "^1.12.0",
50
+ "@driftdev/sdk": "^1.13.0",
51
51
  "@modelcontextprotocol/sdk": "^1.29.0",
52
- "@openpkg-ts/sdk": "^0.43.0",
53
- "@openpkg-ts/spec": "^0.43.0",
52
+ "@openpkg-ts/sdk": "^0.51.0",
53
+ "@openpkg-ts/spec": "^0.50.0",
54
54
  "chalk": "^5.4.1",
55
55
  "commander": "^14.0.0",
56
56
  "zod": "^4.2.1"