@driftdev/cli 1.5.0 → 1.7.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/README.md +1 -1
- package/dist/drift.js +133 -73
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ drift scan --all --private # include private packages
|
|
|
123
123
|
|
|
124
124
|
## lint
|
|
125
125
|
|
|
126
|
-
Cross-reference JSDoc against code signatures. Detects
|
|
126
|
+
Cross-reference JSDoc against code signatures. Detects 17 drift types across 4 categories (structural, semantic, example, prose). Prose detection scans markdown files for broken import references, method calls that don't exist on any exported type, and references to deprecated APIs with no deprecation note.
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
129
|
drift lint # single package
|
package/dist/drift.js
CHANGED
|
@@ -51,8 +51,8 @@ function getGlobalConfigPath() {
|
|
|
51
51
|
var init_global = () => {};
|
|
52
52
|
|
|
53
53
|
// src/drift.ts
|
|
54
|
-
import { readFileSync as
|
|
55
|
-
import * as
|
|
54
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
55
|
+
import * as path31 from "node:path";
|
|
56
56
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
57
57
|
import { Command } from "commander";
|
|
58
58
|
|
|
@@ -2260,13 +2260,15 @@ function registerContextCommand(program) {
|
|
|
2260
2260
|
|
|
2261
2261
|
// src/commands/coverage.ts
|
|
2262
2262
|
import * as path16 from "node:path";
|
|
2263
|
+
import { isExternalExport } from "@driftdev/sdk";
|
|
2263
2264
|
|
|
2264
2265
|
// src/formatters/coverage.ts
|
|
2265
2266
|
function renderCoverage(data) {
|
|
2266
2267
|
const lines = [""];
|
|
2267
2268
|
const color = coverageColor(data.score);
|
|
2268
2269
|
const bar = progressBar(data.score);
|
|
2269
|
-
|
|
2270
|
+
const externalNote = data.external ? c.gray(` +${data.external} external (not resolvable here)`) : "";
|
|
2271
|
+
lines.push(indent(`Coverage ${color(`${data.score}%`)} ${bar} (${data.documented}/${data.total})${externalNote}`));
|
|
2270
2272
|
lines.push("");
|
|
2271
2273
|
if (data.undocumented.length > 0) {
|
|
2272
2274
|
lines.push(indent(c.gray("UNDOCUMENTED")));
|
|
@@ -2411,7 +2413,7 @@ function registerCoverageCommand(program) {
|
|
|
2411
2413
|
let totalAll = 0;
|
|
2412
2414
|
for (const pkg of packages) {
|
|
2413
2415
|
const { spec: spec2 } = await cachedExtract(pkg.entry);
|
|
2414
|
-
const exps = spec2.exports ?? [];
|
|
2416
|
+
const exps = (spec2.exports ?? []).filter((e) => !isExternalExport(e));
|
|
2415
2417
|
let doc = 0;
|
|
2416
2418
|
for (const e of exps) {
|
|
2417
2419
|
if (e.description?.trim())
|
|
@@ -2445,7 +2447,9 @@ function registerCoverageCommand(program) {
|
|
|
2445
2447
|
spec: options.spec,
|
|
2446
2448
|
abi: options.abi
|
|
2447
2449
|
});
|
|
2448
|
-
const
|
|
2450
|
+
const allExports = spec.exports ?? [];
|
|
2451
|
+
const exports = allExports.filter((exp) => !isExternalExport(exp));
|
|
2452
|
+
const external = allExports.length - exports.length;
|
|
2449
2453
|
const total = exports.length;
|
|
2450
2454
|
const undocumented = [];
|
|
2451
2455
|
for (const exp of exports) {
|
|
@@ -2455,7 +2459,13 @@ function registerCoverageCommand(program) {
|
|
|
2455
2459
|
}
|
|
2456
2460
|
const documented = total - undocumented.length;
|
|
2457
2461
|
const score = total > 0 ? Math.round(documented / total * 100) : 100;
|
|
2458
|
-
const data = {
|
|
2462
|
+
const data = {
|
|
2463
|
+
score,
|
|
2464
|
+
documented,
|
|
2465
|
+
total,
|
|
2466
|
+
undocumented,
|
|
2467
|
+
...external > 0 ? { external } : {}
|
|
2468
|
+
};
|
|
2459
2469
|
const next = undocumented.length > 0 ? {
|
|
2460
2470
|
suggested: "drift-enrich skill",
|
|
2461
2471
|
reason: `${undocumented.length} exports lack documentation`
|
|
@@ -2998,6 +3008,9 @@ import * as path21 from "node:path";
|
|
|
2998
3008
|
import { getExport, listExports } from "@openpkg-ts/sdk";
|
|
2999
3009
|
|
|
3000
3010
|
// src/formatters/get.ts
|
|
3011
|
+
function col(text, width) {
|
|
3012
|
+
return padRight(text, Math.max(width, text.length + 2));
|
|
3013
|
+
}
|
|
3001
3014
|
function renderGet(data) {
|
|
3002
3015
|
const lines = [""];
|
|
3003
3016
|
const exp = data.export;
|
|
@@ -3021,7 +3034,7 @@ function renderGet(data) {
|
|
|
3021
3034
|
const req = p.required ? "required" : "optional";
|
|
3022
3035
|
const type = p.type ?? "unknown";
|
|
3023
3036
|
const desc = p.description ? ` ${c.dim(JSON.stringify(p.description))}` : "";
|
|
3024
|
-
lines.push(indent(` ${
|
|
3037
|
+
lines.push(indent(` ${col(p.name ?? "", 16)}${col(type, 24)}${c.gray(req)}${desc}`));
|
|
3025
3038
|
}
|
|
3026
3039
|
lines.push("");
|
|
3027
3040
|
}
|
|
@@ -3039,7 +3052,7 @@ function renderGet(data) {
|
|
|
3039
3052
|
for (const m of shown) {
|
|
3040
3053
|
const req = m.required ? "required" : "optional";
|
|
3041
3054
|
const type = m.type ?? "";
|
|
3042
|
-
lines.push(indent(` ${
|
|
3055
|
+
lines.push(indent(` ${col(m.name ?? "", 20)}${col(type, 20)}${c.gray(req)}`));
|
|
3043
3056
|
}
|
|
3044
3057
|
if (remaining > 0) {
|
|
3045
3058
|
lines.push(indent(c.gray(` ... ${remaining} more`)));
|
|
@@ -3265,22 +3278,29 @@ function registerGetCommand(program) {
|
|
|
3265
3278
|
}
|
|
3266
3279
|
});
|
|
3267
3280
|
}
|
|
3268
|
-
function schemaTypeString(schema) {
|
|
3281
|
+
function schemaTypeString(schema, depth = 0) {
|
|
3269
3282
|
if (schema === undefined || schema === null)
|
|
3270
3283
|
return;
|
|
3271
3284
|
if (typeof schema === "string")
|
|
3272
3285
|
return schema;
|
|
3273
|
-
if (typeof schema
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3286
|
+
if (typeof schema !== "object" || depth > 3)
|
|
3287
|
+
return;
|
|
3288
|
+
const s = schema;
|
|
3289
|
+
if (typeof s.$ref === "string")
|
|
3290
|
+
return s.$ref.split("/").pop();
|
|
3291
|
+
const composite = s.oneOf ?? s.anyOf;
|
|
3292
|
+
if (Array.isArray(composite)) {
|
|
3293
|
+
const arms = composite.map((arm) => schemaTypeString(arm, depth + 1) ?? "unknown").filter((v, i, a) => a.indexOf(v) === i);
|
|
3294
|
+
return arms.join(" | ");
|
|
3295
|
+
}
|
|
3296
|
+
if (s.type === "array") {
|
|
3297
|
+
const item = schemaTypeString(s.items, depth + 1);
|
|
3298
|
+
return item ? `${item}[]` : "array";
|
|
3299
|
+
}
|
|
3300
|
+
if (typeof s.title === "string" && (s.type === "object" || s.type === undefined))
|
|
3301
|
+
return s.title;
|
|
3302
|
+
if (typeof s.type === "string")
|
|
3303
|
+
return s.type;
|
|
3284
3304
|
return;
|
|
3285
3305
|
}
|
|
3286
3306
|
function renderNotFound(exportName, suggestions, startTime, version) {
|
|
@@ -3309,7 +3329,7 @@ function renderNotFound(exportName, suggestions, startTime, version) {
|
|
|
3309
3329
|
|
|
3310
3330
|
// src/commands/health.ts
|
|
3311
3331
|
import * as path22 from "node:path";
|
|
3312
|
-
import { computeDrift as computeDrift3 } from "@driftdev/sdk";
|
|
3332
|
+
import { computeDrift as computeDrift3, isExternalExport as isExternalExport2 } from "@driftdev/sdk";
|
|
3313
3333
|
|
|
3314
3334
|
// src/formatters/health.ts
|
|
3315
3335
|
function renderHealth(data, next) {
|
|
@@ -3324,7 +3344,8 @@ function renderHealth(data, next) {
|
|
|
3324
3344
|
lines.push(indent(`${sym.branch} completeness ${data.completeness}% (${data.undocumented} missing docs)`));
|
|
3325
3345
|
lines.push(indent(`${sym.end} accuracy ${data.accuracy}% (${data.drifted} stale signatures)`));
|
|
3326
3346
|
lines.push("");
|
|
3327
|
-
|
|
3347
|
+
const externalNote = data.external ? ` ${c.gray(sym.dot)} ${c.gray(`${data.external} external (not resolvable here)`)}` : "";
|
|
3348
|
+
lines.push(indent(`${data.totalExports} exports ${c.gray(sym.dot)} ${data.documented} documented ${c.gray(sym.dot)} ${data.drifted} drifted${externalNote}`));
|
|
3328
3349
|
lines.push("");
|
|
3329
3350
|
if (data.issues.length > 0) {
|
|
3330
3351
|
lines.push(indent("Top issues"));
|
|
@@ -3407,7 +3428,7 @@ function registerHealthCommand(program) {
|
|
|
3407
3428
|
let totalAll = 0;
|
|
3408
3429
|
for (const pkg of packages) {
|
|
3409
3430
|
const { spec: spec2 } = await cachedExtract(pkg.entry);
|
|
3410
|
-
const exps = spec2.exports ?? [];
|
|
3431
|
+
const exps = (spec2.exports ?? []).filter((e) => !isExternalExport2(e));
|
|
3411
3432
|
let doc = 0;
|
|
3412
3433
|
for (const e of exps) {
|
|
3413
3434
|
if (e.description?.trim())
|
|
@@ -3437,7 +3458,9 @@ function registerHealthCommand(program) {
|
|
|
3437
3458
|
packageName,
|
|
3438
3459
|
packageVersion
|
|
3439
3460
|
} = await resolveTruth({ entry: entryFile, lang, spec: options.spec, abi: options.abi });
|
|
3440
|
-
const
|
|
3461
|
+
const allExports = spec.exports ?? [];
|
|
3462
|
+
const exports = allExports.filter((exp) => !isExternalExport2(exp));
|
|
3463
|
+
const external = allExports.length - exports.length;
|
|
3441
3464
|
const total = exports.length;
|
|
3442
3465
|
let documented = 0;
|
|
3443
3466
|
for (const exp of exports) {
|
|
@@ -3455,6 +3478,7 @@ function registerHealthCommand(program) {
|
|
|
3455
3478
|
const health = computeHealth(total, documented, issues);
|
|
3456
3479
|
const data = {
|
|
3457
3480
|
...health,
|
|
3481
|
+
...external > 0 ? { external } : {},
|
|
3458
3482
|
packageName,
|
|
3459
3483
|
packageVersion
|
|
3460
3484
|
};
|
|
@@ -3884,14 +3908,8 @@ function registerInitCommand(program) {
|
|
|
3884
3908
|
}
|
|
3885
3909
|
|
|
3886
3910
|
// src/commands/lint.ts
|
|
3887
|
-
import
|
|
3888
|
-
import
|
|
3889
|
-
import {
|
|
3890
|
-
buildExportRegistry,
|
|
3891
|
-
computeDrift as computeDrift4,
|
|
3892
|
-
detectProseDrift,
|
|
3893
|
-
discoverMarkdownFiles
|
|
3894
|
-
} from "@driftdev/sdk";
|
|
3911
|
+
import * as path25 from "node:path";
|
|
3912
|
+
import { buildExportRegistry, computeDrift as computeDrift4, detectProseDrift } from "@driftdev/sdk";
|
|
3895
3913
|
|
|
3896
3914
|
// src/formatters/lint.ts
|
|
3897
3915
|
function renderLint(data, next) {
|
|
@@ -3927,9 +3945,47 @@ function renderLint(data, next) {
|
|
|
3927
3945
|
`);
|
|
3928
3946
|
}
|
|
3929
3947
|
|
|
3948
|
+
// src/utils/docs-corpus.ts
|
|
3949
|
+
import { readFileSync as readFileSync17, statSync as statSync2 } from "node:fs";
|
|
3950
|
+
import * as path24 from "node:path";
|
|
3951
|
+
import { discoverMarkdownFiles } from "@driftdev/sdk";
|
|
3952
|
+
function resolveDocsCorpus(cwd, docsPatterns, configDocs) {
|
|
3953
|
+
if (!docsPatterns || docsPatterns.length === 0) {
|
|
3954
|
+
return discoverMarkdownFiles(cwd, configDocs);
|
|
3955
|
+
}
|
|
3956
|
+
const include = [];
|
|
3957
|
+
for (const pattern of docsPatterns) {
|
|
3958
|
+
if (isDirectory(path24.resolve(cwd, pattern))) {
|
|
3959
|
+
include.push(path24.join(pattern, "**/*.md"), path24.join(pattern, "**/*.mdx"));
|
|
3960
|
+
} else {
|
|
3961
|
+
include.push(pattern);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
const files = discoverMarkdownFiles(cwd, { include });
|
|
3965
|
+
if (files.length === 0) {
|
|
3966
|
+
formatWarning(`--docs matched no markdown files: ${docsPatterns.join(", ")}`);
|
|
3967
|
+
}
|
|
3968
|
+
return files;
|
|
3969
|
+
}
|
|
3970
|
+
function isDirectory(p) {
|
|
3971
|
+
try {
|
|
3972
|
+
return statSync2(p).isDirectory();
|
|
3973
|
+
} catch {
|
|
3974
|
+
return false;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
function readPackageName(cwd = process.cwd()) {
|
|
3978
|
+
try {
|
|
3979
|
+
const pkgJson = JSON.parse(readFileSync17(path24.resolve(cwd, "package.json"), "utf-8"));
|
|
3980
|
+
return typeof pkgJson.name === "string" ? pkgJson.name : undefined;
|
|
3981
|
+
} catch {
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3930
3986
|
// src/commands/lint.ts
|
|
3931
3987
|
function registerLintCommand(program) {
|
|
3932
|
-
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)").action(async (entry, options) => {
|
|
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) => {
|
|
3933
3989
|
const startTime = Date.now();
|
|
3934
3990
|
const version = getVersion();
|
|
3935
3991
|
try {
|
|
@@ -3981,9 +4037,9 @@ function registerLintCommand(program) {
|
|
|
3981
4037
|
formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
|
|
3982
4038
|
return;
|
|
3983
4039
|
}
|
|
3984
|
-
let entryFile = entry ?
|
|
4040
|
+
let entryFile = entry ? path25.resolve(process.cwd(), entry) : undefined;
|
|
3985
4041
|
if (lang === "typescript" && !entryFile) {
|
|
3986
|
-
entryFile = config.entry ?
|
|
4042
|
+
entryFile = config.entry ? path25.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3987
4043
|
}
|
|
3988
4044
|
const { apiSpec: spec } = await resolveTruth({
|
|
3989
4045
|
entry: entryFile,
|
|
@@ -4004,14 +4060,12 @@ function registerLintCommand(program) {
|
|
|
4004
4060
|
});
|
|
4005
4061
|
}
|
|
4006
4062
|
}
|
|
4007
|
-
if (lang === "typescript")
|
|
4063
|
+
if (lang === "typescript" || options.docs)
|
|
4008
4064
|
try {
|
|
4009
|
-
const
|
|
4010
|
-
const pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
|
|
4011
|
-
const packageName = pkgJson.name;
|
|
4065
|
+
const packageName = readPackageName() ?? spec.meta?.name;
|
|
4012
4066
|
if (packageName) {
|
|
4013
4067
|
const registry = buildExportRegistry(spec);
|
|
4014
|
-
const markdownFiles =
|
|
4068
|
+
const markdownFiles = resolveDocsCorpus(process.cwd(), options.docs, config.docs);
|
|
4015
4069
|
const proseDrifts = detectProseDrift({ packageName, markdownFiles, registry });
|
|
4016
4070
|
for (const drift of proseDrifts) {
|
|
4017
4071
|
issues.push({
|
|
@@ -4049,7 +4103,7 @@ function registerLintCommand(program) {
|
|
|
4049
4103
|
}
|
|
4050
4104
|
|
|
4051
4105
|
// src/commands/list.ts
|
|
4052
|
-
import * as
|
|
4106
|
+
import * as path26 from "node:path";
|
|
4053
4107
|
import { computeDrift as computeDrift5 } from "@driftdev/sdk";
|
|
4054
4108
|
import { listExports as listExports2 } from "@openpkg-ts/sdk";
|
|
4055
4109
|
|
|
@@ -4164,7 +4218,7 @@ function registerListCommand(program) {
|
|
|
4164
4218
|
} else {
|
|
4165
4219
|
let entryFile;
|
|
4166
4220
|
if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
|
|
4167
|
-
entryFile =
|
|
4221
|
+
entryFile = path26.resolve(process.cwd(), searchOrEntry);
|
|
4168
4222
|
} else if (searchOrEntry) {
|
|
4169
4223
|
entryFile = detectEntry();
|
|
4170
4224
|
searchTerm = searchOrEntry;
|
|
@@ -4240,7 +4294,7 @@ function truthFlags(args) {
|
|
|
4240
4294
|
return out;
|
|
4241
4295
|
}
|
|
4242
4296
|
function runDrift(cliArgs, cwd) {
|
|
4243
|
-
return new Promise((
|
|
4297
|
+
return new Promise((resolve19) => {
|
|
4244
4298
|
const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
|
|
4245
4299
|
cwd: cwd ?? process.cwd(),
|
|
4246
4300
|
env: { ...process.env, NO_COLOR: "1" },
|
|
@@ -4260,9 +4314,9 @@ function runDrift(cliArgs, cwd) {
|
|
|
4260
4314
|
try {
|
|
4261
4315
|
ok = JSON.parse(stdout).ok === true;
|
|
4262
4316
|
} catch {}
|
|
4263
|
-
|
|
4317
|
+
resolve19({ text, ok });
|
|
4264
4318
|
});
|
|
4265
|
-
child.on("error", (err) =>
|
|
4319
|
+
child.on("error", (err) => resolve19({ text: `Failed to run drift: ${err.message}`, ok: false }));
|
|
4266
4320
|
});
|
|
4267
4321
|
}
|
|
4268
4322
|
function toResult({ text, ok }) {
|
|
@@ -4376,7 +4430,7 @@ function diffArgs(command, args) {
|
|
|
4376
4430
|
// src/commands/release.ts
|
|
4377
4431
|
import { execSync as execSync4 } from "node:child_process";
|
|
4378
4432
|
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
4379
|
-
import * as
|
|
4433
|
+
import * as path27 from "node:path";
|
|
4380
4434
|
import { computeDrift as computeDrift6 } from "@driftdev/sdk";
|
|
4381
4435
|
|
|
4382
4436
|
// src/formatters/release.ts
|
|
@@ -4424,7 +4478,7 @@ function getLastTag() {
|
|
|
4424
4478
|
}
|
|
4425
4479
|
}
|
|
4426
4480
|
function getPackageVersion(cwd) {
|
|
4427
|
-
const pkgPath =
|
|
4481
|
+
const pkgPath = path27.join(cwd, "package.json");
|
|
4428
4482
|
if (!existsSync14(pkgPath))
|
|
4429
4483
|
return null;
|
|
4430
4484
|
try {
|
|
@@ -4440,7 +4494,7 @@ function registerReleaseCommand(program) {
|
|
|
4440
4494
|
const cwd = process.cwd();
|
|
4441
4495
|
try {
|
|
4442
4496
|
const { config } = loadConfig();
|
|
4443
|
-
const entryFile = entry ?
|
|
4497
|
+
const entryFile = entry ? path27.resolve(cwd, entry) : config.entry ? path27.resolve(cwd, config.entry) : detectEntry();
|
|
4444
4498
|
const { spec } = await cachedExtract(entryFile);
|
|
4445
4499
|
const exports = spec.exports ?? [];
|
|
4446
4500
|
const total = exports.length;
|
|
@@ -4565,7 +4619,7 @@ function renderReport(data) {
|
|
|
4565
4619
|
// src/utils/scan-packages.ts
|
|
4566
4620
|
import { execSync as execSync5 } from "node:child_process";
|
|
4567
4621
|
import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
|
|
4568
|
-
import * as
|
|
4622
|
+
import * as path28 from "node:path";
|
|
4569
4623
|
import { computeDrift as computeDrift7 } from "@driftdev/sdk";
|
|
4570
4624
|
function detectPackageDirs2(cwd) {
|
|
4571
4625
|
const workspaces = detectWorkspaces(cwd);
|
|
@@ -4584,11 +4638,11 @@ async function scanAllPackages(cwd) {
|
|
|
4584
4638
|
const packageDirs = detectPackageDirs2(cwd);
|
|
4585
4639
|
const results = [];
|
|
4586
4640
|
for (const dir of packageDirs) {
|
|
4587
|
-
const absDir = dir === "." ? cwd :
|
|
4641
|
+
const absDir = dir === "." ? cwd : path28.join(cwd, dir);
|
|
4588
4642
|
if (!existsSync15(absDir))
|
|
4589
4643
|
continue;
|
|
4590
4644
|
let name = dir;
|
|
4591
|
-
const pkgPath =
|
|
4645
|
+
const pkgPath = path28.join(absDir, "package.json");
|
|
4592
4646
|
if (existsSync15(pkgPath)) {
|
|
4593
4647
|
try {
|
|
4594
4648
|
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
@@ -4687,13 +4741,12 @@ function registerReportCommand(program) {
|
|
|
4687
4741
|
}
|
|
4688
4742
|
|
|
4689
4743
|
// src/commands/scan.ts
|
|
4690
|
-
import
|
|
4691
|
-
import * as path28 from "node:path";
|
|
4744
|
+
import * as path29 from "node:path";
|
|
4692
4745
|
import {
|
|
4693
4746
|
buildExportRegistry as buildExportRegistry2,
|
|
4694
4747
|
computeDrift as computeDrift8,
|
|
4695
4748
|
detectProseDrift as detectProseDrift2,
|
|
4696
|
-
|
|
4749
|
+
isExternalExport as isExternalExport3
|
|
4697
4750
|
} from "@driftdev/sdk";
|
|
4698
4751
|
|
|
4699
4752
|
// src/formatters/scan.ts
|
|
@@ -4706,7 +4759,8 @@ function renderScan(data, next) {
|
|
|
4706
4759
|
}
|
|
4707
4760
|
const hColor = coverageColor(data.health);
|
|
4708
4761
|
lines.push(indent(`Health ${hColor(`${data.health}%`)}`));
|
|
4709
|
-
|
|
4762
|
+
const externalNote = data.coverage.external ? c.gray(` +${data.coverage.external} external (not resolvable here)`) : "";
|
|
4763
|
+
lines.push(indent(`Coverage ${coverageColor(data.coverage.score)(`${data.coverage.score}%`)} (${data.coverage.documented}/${data.coverage.total} exports)${externalNote}`));
|
|
4710
4764
|
lines.push(indent(`Lint ${data.lint.count === 0 ? c.green("0 issues") : c.red(`${data.lint.count} issues`)}`));
|
|
4711
4765
|
lines.push("");
|
|
4712
4766
|
if (data.lint.count > 0) {
|
|
@@ -4756,7 +4810,7 @@ function renderBatchScan(data) {
|
|
|
4756
4810
|
|
|
4757
4811
|
// src/commands/scan.ts
|
|
4758
4812
|
function registerScanCommand(program) {
|
|
4759
|
-
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)").action(async (entry, options) => {
|
|
4813
|
+
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)").action(async (entry, options) => {
|
|
4760
4814
|
const startTime = Date.now();
|
|
4761
4815
|
const version = getVersion();
|
|
4762
4816
|
try {
|
|
@@ -4794,7 +4848,7 @@ function registerScanCommand(program) {
|
|
|
4794
4848
|
let anyFail = false;
|
|
4795
4849
|
for (const pkg of packages) {
|
|
4796
4850
|
const { spec } = await cachedExtract(pkg.entry);
|
|
4797
|
-
const exps = spec.exports ?? [];
|
|
4851
|
+
const exps = (spec.exports ?? []).filter((e) => !isExternalExport3(e));
|
|
4798
4852
|
let documented2 = 0;
|
|
4799
4853
|
for (const e of exps) {
|
|
4800
4854
|
if (e.description?.trim())
|
|
@@ -4831,9 +4885,9 @@ function registerScanCommand(program) {
|
|
|
4831
4885
|
return;
|
|
4832
4886
|
}
|
|
4833
4887
|
const { config } = loadConfig();
|
|
4834
|
-
let entryFile = entry ?
|
|
4888
|
+
let entryFile = entry ? path29.resolve(process.cwd(), entry) : undefined;
|
|
4835
4889
|
if (lang === "typescript" && !entryFile) {
|
|
4836
|
-
entryFile = config.entry ?
|
|
4890
|
+
entryFile = config.entry ? path29.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4837
4891
|
}
|
|
4838
4892
|
const { apiSpec, packageName, packageVersion } = await resolveTruth({
|
|
4839
4893
|
entry: entryFile,
|
|
@@ -4841,7 +4895,9 @@ function registerScanCommand(program) {
|
|
|
4841
4895
|
spec: options.spec,
|
|
4842
4896
|
abi: options.abi
|
|
4843
4897
|
});
|
|
4844
|
-
const
|
|
4898
|
+
const allExports = apiSpec.exports ?? [];
|
|
4899
|
+
const exports = allExports.filter((exp) => !isExternalExport3(exp));
|
|
4900
|
+
const external = allExports.length - exports.length;
|
|
4845
4901
|
const total = exports.length;
|
|
4846
4902
|
let documented = 0;
|
|
4847
4903
|
for (const exp of exports) {
|
|
@@ -4862,14 +4918,12 @@ function registerScanCommand(program) {
|
|
|
4862
4918
|
});
|
|
4863
4919
|
}
|
|
4864
4920
|
}
|
|
4865
|
-
if (lang === "typescript") {
|
|
4921
|
+
if (lang === "typescript" || options.docs) {
|
|
4866
4922
|
try {
|
|
4867
|
-
const
|
|
4868
|
-
const pkgJson = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
|
|
4869
|
-
const pkgName = pkgJson.name;
|
|
4923
|
+
const pkgName = readPackageName() ?? packageName;
|
|
4870
4924
|
if (pkgName) {
|
|
4871
4925
|
const registry = buildExportRegistry2(apiSpec);
|
|
4872
|
-
const markdownFiles =
|
|
4926
|
+
const markdownFiles = resolveDocsCorpus(process.cwd(), options.docs, config.docs);
|
|
4873
4927
|
const proseDrifts = detectProseDrift2({
|
|
4874
4928
|
packageName: pkgName,
|
|
4875
4929
|
markdownFiles,
|
|
@@ -4898,7 +4952,13 @@ function registerScanCommand(program) {
|
|
|
4898
4952
|
}
|
|
4899
4953
|
const pass = min === undefined || h.health >= min;
|
|
4900
4954
|
const data = {
|
|
4901
|
-
coverage: {
|
|
4955
|
+
coverage: {
|
|
4956
|
+
score: coverageScore,
|
|
4957
|
+
documented,
|
|
4958
|
+
total,
|
|
4959
|
+
undocumented: total - documented,
|
|
4960
|
+
...external > 0 ? { external } : {}
|
|
4961
|
+
},
|
|
4902
4962
|
lint: { issues, count: issues.length },
|
|
4903
4963
|
health: h.health,
|
|
4904
4964
|
pass,
|
|
@@ -4971,8 +5031,8 @@ function registerSemverCommand(program) {
|
|
|
4971
5031
|
}
|
|
4972
5032
|
|
|
4973
5033
|
// src/commands/validate.ts
|
|
4974
|
-
import { readFileSync as
|
|
4975
|
-
import * as
|
|
5034
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
5035
|
+
import * as path30 from "node:path";
|
|
4976
5036
|
import { validateSpec } from "@openpkg-ts/spec";
|
|
4977
5037
|
|
|
4978
5038
|
// src/formatters/validate.ts
|
|
@@ -4998,8 +5058,8 @@ function registerValidateCommand(program) {
|
|
|
4998
5058
|
const startTime = Date.now();
|
|
4999
5059
|
const version = getVersion();
|
|
5000
5060
|
try {
|
|
5001
|
-
const filePath =
|
|
5002
|
-
const content =
|
|
5061
|
+
const filePath = path30.resolve(process.cwd(), file);
|
|
5062
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
5003
5063
|
const spec = JSON.parse(content);
|
|
5004
5064
|
const result = validateSpec(spec);
|
|
5005
5065
|
const data = {
|
|
@@ -5124,13 +5184,13 @@ function extractCapabilities(program) {
|
|
|
5124
5184
|
|
|
5125
5185
|
// src/drift.ts
|
|
5126
5186
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
5127
|
-
var __dirname3 =
|
|
5128
|
-
var packageJson = JSON.parse(
|
|
5187
|
+
var __dirname3 = path31.dirname(__filename2);
|
|
5188
|
+
var packageJson = JSON.parse(readFileSync21(path31.join(__dirname3, "../package.json"), "utf-8"));
|
|
5129
5189
|
var program = new Command;
|
|
5130
5190
|
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) => {
|
|
5131
5191
|
const opts = program.opts();
|
|
5132
5192
|
if (opts.cwd) {
|
|
5133
|
-
process.chdir(
|
|
5193
|
+
process.chdir(path31.resolve(opts.cwd));
|
|
5134
5194
|
}
|
|
5135
5195
|
setOutputMode({ json: opts.json, human: opts.human });
|
|
5136
5196
|
setConfigPath(opts.config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftdev/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Drift CLI - detect when your docs drift from your code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@driftdev/clarity-adapter": "^1.0.1",
|
|
48
|
-
"@driftdev/openapi-adapter": "^1.0.
|
|
49
|
-
"@driftdev/sdk": "^1.
|
|
48
|
+
"@driftdev/openapi-adapter": "^1.0.1",
|
|
49
|
+
"@driftdev/sdk": "^1.7.0",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
51
|
"@openpkg-ts/sdk": "^0.38.0",
|
|
52
52
|
"@openpkg-ts/spec": "^0.37.0",
|