@driftdev/cli 1.9.0 → 1.11.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.
- package/dist/drift.js +83 -9
- package/package.json +6 -5
- package/schemas/drift.config.schema.json +85 -0
package/dist/drift.js
CHANGED
|
@@ -573,8 +573,13 @@ async function extractSpecFromRef(ref, entry, cwd = process.cwd()) {
|
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
// src/utils/output.ts
|
|
576
|
+
function elapsed(startTime) {
|
|
577
|
+
if (process.env.SOURCE_DATE_EPOCH !== undefined)
|
|
578
|
+
return 0;
|
|
579
|
+
return Date.now() - startTime;
|
|
580
|
+
}
|
|
576
581
|
function formatOutput(command, data, startTime, version, humanRenderer, next) {
|
|
577
|
-
const duration =
|
|
582
|
+
const duration = elapsed(startTime);
|
|
578
583
|
const envelope = {
|
|
579
584
|
ok: true,
|
|
580
585
|
data,
|
|
@@ -593,8 +598,8 @@ function formatOutput(command, data, startTime, version, humanRenderer, next) {
|
|
|
593
598
|
}
|
|
594
599
|
return envelope;
|
|
595
600
|
}
|
|
596
|
-
function formatError(command, error, startTime, version, suggestion) {
|
|
597
|
-
const duration =
|
|
601
|
+
function formatError(command, error, startTime, version, suggestion, exitCode = 2) {
|
|
602
|
+
const duration = elapsed(startTime);
|
|
598
603
|
if (shouldRenderHuman()) {
|
|
599
604
|
process.stdout.write(`
|
|
600
605
|
${c.red("x")} ${error}
|
|
@@ -615,7 +620,7 @@ function formatError(command, error, startTime, version, suggestion) {
|
|
|
615
620
|
process.stderr.write(`drift ${command} failed: ${error}
|
|
616
621
|
`);
|
|
617
622
|
}
|
|
618
|
-
process.exitCode =
|
|
623
|
+
process.exitCode = exitCode;
|
|
619
624
|
}
|
|
620
625
|
function formatWarning(message) {
|
|
621
626
|
if (shouldRenderHuman()) {
|
|
@@ -710,7 +715,8 @@ function validateConfig(raw) {
|
|
|
710
715
|
}
|
|
711
716
|
if (errors.length > 0)
|
|
712
717
|
return { ok: false, errors };
|
|
713
|
-
|
|
718
|
+
const { $schema: _schema, ...rest } = obj;
|
|
719
|
+
return { ok: true, config: mergeDefaults(rest) };
|
|
714
720
|
}
|
|
715
721
|
|
|
716
722
|
// src/config/loader.ts
|
|
@@ -3321,9 +3327,9 @@ function renderNotFound(exportName, suggestions, startTime, version) {
|
|
|
3321
3327
|
`));
|
|
3322
3328
|
process.exitCode = 1;
|
|
3323
3329
|
} else if (suggestions.length > 0) {
|
|
3324
|
-
formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version);
|
|
3330
|
+
formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version, undefined, 1);
|
|
3325
3331
|
} else {
|
|
3326
|
-
formatError("get", `Export '${exportName}' not found`, startTime, version);
|
|
3332
|
+
formatError("get", `Export '${exportName}' not found`, startTime, version, undefined, 1);
|
|
3327
3333
|
}
|
|
3328
3334
|
}
|
|
3329
3335
|
|
|
@@ -3857,6 +3863,7 @@ function generateConfig(packages) {
|
|
|
3857
3863
|
const worstCoverage = Math.min(...packages.map((p) => p.coverage));
|
|
3858
3864
|
const threshold = Math.max(0, Math.floor(worstCoverage) - 5);
|
|
3859
3865
|
return {
|
|
3866
|
+
$schema: "https://unpkg.com/@driftdev/cli/schemas/drift.config.schema.json",
|
|
3860
3867
|
coverage: { min: threshold }
|
|
3861
3868
|
};
|
|
3862
3869
|
}
|
|
@@ -3945,6 +3952,26 @@ function renderLint(data, next) {
|
|
|
3945
3952
|
`);
|
|
3946
3953
|
}
|
|
3947
3954
|
|
|
3955
|
+
// src/utils/annotations.ts
|
|
3956
|
+
function escapeData(value) {
|
|
3957
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
3958
|
+
}
|
|
3959
|
+
function escapeProperty(value) {
|
|
3960
|
+
return escapeData(value).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
3961
|
+
}
|
|
3962
|
+
function emitAnnotations(issues, level = "error") {
|
|
3963
|
+
for (const issue of issues) {
|
|
3964
|
+
const props = [];
|
|
3965
|
+
if (issue.filePath)
|
|
3966
|
+
props.push(`file=${escapeProperty(issue.filePath)}`);
|
|
3967
|
+
if (issue.line !== undefined)
|
|
3968
|
+
props.push(`line=${issue.line}`);
|
|
3969
|
+
props.push(`title=${escapeProperty(`drift: ${issue.export || "docs"}`)}`);
|
|
3970
|
+
process.stdout.write(`::${level} ${props.join(",")}::${escapeData(issue.issue)}
|
|
3971
|
+
`);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3948
3975
|
// src/utils/docs-corpus.ts
|
|
3949
3976
|
import { readFileSync as readFileSync17, statSync as statSync2 } from "node:fs";
|
|
3950
3977
|
import * as path24 from "node:path";
|
|
@@ -3985,7 +4012,7 @@ function readPackageName(cwd = process.cwd()) {
|
|
|
3985
4012
|
|
|
3986
4013
|
// src/commands/lint.ts
|
|
3987
4014
|
function registerLintCommand(program) {
|
|
3988
|
-
program.command("lint [entry]").description("Cross-reference docs against the API surface for accuracy issues").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)").action(async (entry, options) => {
|
|
4015
|
+
program.command("lint [entry]").description("Cross-reference docs against the API surface for accuracy issues").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("--annotations", "Emit GitHub Actions ::error annotations for findings").action(async (entry, options) => {
|
|
3989
4016
|
const startTime = Date.now();
|
|
3990
4017
|
const version = getVersion();
|
|
3991
4018
|
try {
|
|
@@ -4086,6 +4113,8 @@ function registerLintCommand(program) {
|
|
|
4086
4113
|
reason: `${issues.length} issue${issues.length === 1 ? "" : "s"} found`
|
|
4087
4114
|
} : undefined;
|
|
4088
4115
|
formatOutput("lint", data, startTime, version, renderLint, next);
|
|
4116
|
+
if (options.annotations && issues.length > 0)
|
|
4117
|
+
emitAnnotations(issues);
|
|
4089
4118
|
if (issues.length > 0) {
|
|
4090
4119
|
if (!shouldRenderHuman()) {
|
|
4091
4120
|
process.stderr.write(`${issues.length} issue${issues.length === 1 ? "" : "s"} found
|
|
@@ -4385,6 +4414,51 @@ function registerMcpCommand(program) {
|
|
|
4385
4414
|
cli.push("--min", String(args.min));
|
|
4386
4415
|
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4387
4416
|
});
|
|
4417
|
+
server.registerTool("drift_lint", {
|
|
4418
|
+
title: "Lint docs accuracy",
|
|
4419
|
+
description: "Cross-reference docs against the real API surface: wrong parameter names/types, stale signatures, references to removed exports, prose drift in markdown docs. Returns issues with file/line locations. The primary fix loop: lint, fix what it reports, lint again until clean.",
|
|
4420
|
+
inputSchema: {
|
|
4421
|
+
...truthShape,
|
|
4422
|
+
docs: z.array(z.string()).optional().describe("Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)")
|
|
4423
|
+
}
|
|
4424
|
+
}, async (args) => {
|
|
4425
|
+
const cli = ["lint"];
|
|
4426
|
+
if (args.entry)
|
|
4427
|
+
cli.push(args.entry);
|
|
4428
|
+
if (args.docs?.length)
|
|
4429
|
+
cli.push("--docs", ...args.docs);
|
|
4430
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4431
|
+
});
|
|
4432
|
+
server.registerTool("drift_coverage", {
|
|
4433
|
+
title: "Measure docs coverage",
|
|
4434
|
+
description: "Documentation coverage of an API surface: documented vs total exports, score, what is missing per rule. Use to find undocumented surface before writing docs.",
|
|
4435
|
+
inputSchema: {
|
|
4436
|
+
...truthShape,
|
|
4437
|
+
min: z.number().optional().describe("Minimum coverage % (result fails below it)")
|
|
4438
|
+
}
|
|
4439
|
+
}, async (args) => {
|
|
4440
|
+
const cli = ["coverage"];
|
|
4441
|
+
if (args.entry)
|
|
4442
|
+
cli.push(args.entry);
|
|
4443
|
+
if (args.min !== undefined)
|
|
4444
|
+
cli.push("--min", String(args.min));
|
|
4445
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4446
|
+
});
|
|
4447
|
+
server.registerTool("drift_health", {
|
|
4448
|
+
title: "Docs health score",
|
|
4449
|
+
description: "Combined docs health score (coverage + accuracy) for an API surface, with the breakdown. The single number to gate on in CI.",
|
|
4450
|
+
inputSchema: {
|
|
4451
|
+
...truthShape,
|
|
4452
|
+
min: z.number().optional().describe("Minimum health % (result fails below it)")
|
|
4453
|
+
}
|
|
4454
|
+
}, async (args) => {
|
|
4455
|
+
const cli = ["health"];
|
|
4456
|
+
if (args.entry)
|
|
4457
|
+
cli.push(args.entry);
|
|
4458
|
+
if (args.min !== undefined)
|
|
4459
|
+
cli.push("--min", String(args.min));
|
|
4460
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4461
|
+
});
|
|
4388
4462
|
server.registerTool("drift_diff", {
|
|
4389
4463
|
title: "Diff two API specs",
|
|
4390
4464
|
description: "Diff two extracted API specs (TypeScript only today): spec files or git refs. Reports added/removed/changed exports. Useful for changelog and release-note verification.",
|
|
@@ -5243,5 +5317,5 @@ if (userArgs.length === 0 && !hasHelpOrVersion) {
|
|
|
5243
5317
|
process.argv.splice(2, 0, "scan");
|
|
5244
5318
|
}
|
|
5245
5319
|
program.parseAsync().catch(() => {
|
|
5246
|
-
process.exit(
|
|
5320
|
+
process.exit(2);
|
|
5247
5321
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftdev/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Drift CLI - detect when your docs drift from your code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"test": "bun run --cwd ../sdk build && bun run --cwd ../adapters/clarity build && bun run --cwd ../adapters/openapi build && for f in test/*.test.ts; do bun test \"$f\" || exit 1; done"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
|
-
"dist"
|
|
41
|
+
"dist",
|
|
42
|
+
"schemas"
|
|
42
43
|
],
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=20"
|
|
@@ -46,10 +47,10 @@
|
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"@driftdev/clarity-adapter": "^1.0.1",
|
|
48
49
|
"@driftdev/openapi-adapter": "^1.0.1",
|
|
49
|
-
"@driftdev/sdk": "^1.
|
|
50
|
+
"@driftdev/sdk": "^1.11.0",
|
|
50
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
|
-
"@openpkg-ts/sdk": "^0.
|
|
52
|
-
"@openpkg-ts/spec": "^0.
|
|
52
|
+
"@openpkg-ts/sdk": "^0.43.0",
|
|
53
|
+
"@openpkg-ts/spec": "^0.43.0",
|
|
53
54
|
"chalk": "^5.4.1",
|
|
54
55
|
"commander": "^14.0.0",
|
|
55
56
|
"zod": "^4.2.1"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://unpkg.com/@driftdev/cli/schemas/drift.config.schema.json",
|
|
4
|
+
"title": "Drift configuration",
|
|
5
|
+
"description": "Configuration for the drift CLI, loaded from drift.config.json or the \"drift\" key in package.json. JSON only — no code execution.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path or URL of this schema, for editor/agent validation"
|
|
11
|
+
},
|
|
12
|
+
"entry": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Entry point override (otherwise auto-detected)"
|
|
15
|
+
},
|
|
16
|
+
"include": {
|
|
17
|
+
"description": "Export include patterns",
|
|
18
|
+
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }]
|
|
19
|
+
},
|
|
20
|
+
"exclude": {
|
|
21
|
+
"description": "Export exclude patterns",
|
|
22
|
+
"oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }]
|
|
23
|
+
},
|
|
24
|
+
"coverage": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"description": "Coverage thresholds",
|
|
27
|
+
"properties": {
|
|
28
|
+
"min": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"minimum": 0,
|
|
31
|
+
"maximum": 100,
|
|
32
|
+
"description": "Minimum coverage % (exit 1 if below)"
|
|
33
|
+
},
|
|
34
|
+
"ratchet": {
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"description": "Ratchet: effective min = max(min, highest coverage ever recorded)"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
"lint": {
|
|
42
|
+
"type": "boolean",
|
|
43
|
+
"default": true,
|
|
44
|
+
"description": "Enable lint checks"
|
|
45
|
+
},
|
|
46
|
+
"docs": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"description": "Markdown docs discovery",
|
|
49
|
+
"properties": {
|
|
50
|
+
"include": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": { "type": "string" },
|
|
53
|
+
"description": "Glob patterns for markdown docs to include"
|
|
54
|
+
},
|
|
55
|
+
"exclude": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "type": "string" },
|
|
58
|
+
"description": "Glob patterns for markdown docs to exclude"
|
|
59
|
+
},
|
|
60
|
+
"remote": {
|
|
61
|
+
"type": "array",
|
|
62
|
+
"description": "Remote repos to sync docs on breaking changes",
|
|
63
|
+
"items": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {
|
|
66
|
+
"repo": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"pattern": "^[^/]+/[^/]+$",
|
|
69
|
+
"description": "Target repo in \"owner/repo\" format"
|
|
70
|
+
},
|
|
71
|
+
"branch": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Target branch (defaults to repo's default branch)"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": ["repo"],
|
|
77
|
+
"additionalProperties": false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"additionalProperties": false
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"additionalProperties": true
|
|
85
|
+
}
|