@driftdev/cli 1.4.0 → 1.5.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.
- package/dist/drift.js +604 -197
- package/package.json +12 -5
package/dist/drift.js
CHANGED
|
@@ -52,7 +52,7 @@ var init_global = () => {};
|
|
|
52
52
|
|
|
53
53
|
// src/drift.ts
|
|
54
54
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
55
|
-
import * as
|
|
55
|
+
import * as path30 from "node:path";
|
|
56
56
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
57
57
|
import { Command } from "commander";
|
|
58
58
|
|
|
@@ -1094,7 +1094,7 @@ function getVersion() {
|
|
|
1094
1094
|
} catch {
|
|
1095
1095
|
cached = "0.0.0";
|
|
1096
1096
|
}
|
|
1097
|
-
return cached;
|
|
1097
|
+
return cached ?? "0.0.0";
|
|
1098
1098
|
}
|
|
1099
1099
|
|
|
1100
1100
|
// src/commands/breaking.ts
|
|
@@ -2259,7 +2259,7 @@ function registerContextCommand(program) {
|
|
|
2259
2259
|
}
|
|
2260
2260
|
|
|
2261
2261
|
// src/commands/coverage.ts
|
|
2262
|
-
import * as
|
|
2262
|
+
import * as path16 from "node:path";
|
|
2263
2263
|
|
|
2264
2264
|
// src/formatters/coverage.ts
|
|
2265
2265
|
function renderCoverage(data) {
|
|
@@ -2286,12 +2286,114 @@ function renderCoverage(data) {
|
|
|
2286
2286
|
`);
|
|
2287
2287
|
}
|
|
2288
2288
|
|
|
2289
|
+
// src/utils/load-spec.ts
|
|
2290
|
+
import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
|
|
2291
|
+
import * as path15 from "node:path";
|
|
2292
|
+
import { fromSource } from "@driftdev/clarity-adapter";
|
|
2293
|
+
import { fromDocument } from "@driftdev/openapi-adapter";
|
|
2294
|
+
function getPackageInfo(cwd) {
|
|
2295
|
+
const pkgPath = path15.join(cwd, "package.json");
|
|
2296
|
+
if (!existsSync12(pkgPath))
|
|
2297
|
+
return {};
|
|
2298
|
+
try {
|
|
2299
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
2300
|
+
return { name: pkg.name, version: pkg.version };
|
|
2301
|
+
} catch (err) {
|
|
2302
|
+
formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
|
|
2303
|
+
return {};
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
function resolveLang(opts) {
|
|
2307
|
+
if (opts.lang) {
|
|
2308
|
+
if (opts.lang !== "typescript" && opts.lang !== "clarity" && opts.lang !== "openapi") {
|
|
2309
|
+
throw new Error(`Unknown language: ${opts.lang}`);
|
|
2310
|
+
}
|
|
2311
|
+
return opts.lang;
|
|
2312
|
+
}
|
|
2313
|
+
if (opts.spec)
|
|
2314
|
+
return "openapi";
|
|
2315
|
+
if (opts.abi || opts.entry?.endsWith(".clar"))
|
|
2316
|
+
return "clarity";
|
|
2317
|
+
return "typescript";
|
|
2318
|
+
}
|
|
2319
|
+
function isUrl(value) {
|
|
2320
|
+
return /^https?:\/\//.test(value);
|
|
2321
|
+
}
|
|
2322
|
+
async function readSpecDocument(specPath) {
|
|
2323
|
+
if (isUrl(specPath)) {
|
|
2324
|
+
const res = await fetch(specPath, { signal: AbortSignal.timeout(30000) });
|
|
2325
|
+
if (!res.ok)
|
|
2326
|
+
throw new Error(`Failed to fetch spec: ${res.status} ${res.statusText} (${specPath})`);
|
|
2327
|
+
return res.text();
|
|
2328
|
+
}
|
|
2329
|
+
if (!existsSync12(specPath))
|
|
2330
|
+
throw new Error(`Spec file not found: ${specPath}`);
|
|
2331
|
+
return readFileSync13(specPath, "utf-8");
|
|
2332
|
+
}
|
|
2333
|
+
async function resolveTruth(opts) {
|
|
2334
|
+
const lang = resolveLang(opts);
|
|
2335
|
+
if (lang === "openapi") {
|
|
2336
|
+
if (!opts.spec)
|
|
2337
|
+
throw new Error("--spec is required when --lang openapi");
|
|
2338
|
+
const specPath = isUrl(opts.spec) ? opts.spec : path15.resolve(process.cwd(), opts.spec);
|
|
2339
|
+
const document = await readSpecDocument(specPath);
|
|
2340
|
+
const name = isUrl(specPath) ? new URL(specPath).pathname.split("/").pop()?.replace(/\.[^.]*$/, "") || "openapi" : path15.basename(specPath, path15.extname(specPath));
|
|
2341
|
+
const apiSpec = fromDocument(document);
|
|
2342
|
+
if (!apiSpec.meta.name || apiSpec.meta.name === "openapi")
|
|
2343
|
+
apiSpec.meta.name = name;
|
|
2344
|
+
return {
|
|
2345
|
+
apiSpec,
|
|
2346
|
+
packageName: apiSpec.meta.name,
|
|
2347
|
+
packageVersion: apiSpec.meta.version,
|
|
2348
|
+
lang
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
if (lang === "clarity") {
|
|
2352
|
+
if (!opts.abi)
|
|
2353
|
+
throw new Error("--abi is required when --lang clarity");
|
|
2354
|
+
if (!opts.entry)
|
|
2355
|
+
throw new Error("Entry file required for --lang clarity");
|
|
2356
|
+
const entryFile = path15.resolve(process.cwd(), opts.entry);
|
|
2357
|
+
const abiPath = path15.resolve(process.cwd(), opts.abi);
|
|
2358
|
+
if (!existsSync12(entryFile))
|
|
2359
|
+
throw new Error(`Source file not found: ${entryFile}`);
|
|
2360
|
+
if (!existsSync12(abiPath))
|
|
2361
|
+
throw new Error(`ABI file not found: ${abiPath}`);
|
|
2362
|
+
const source = readFileSync13(entryFile, "utf-8");
|
|
2363
|
+
const abi = JSON.parse(readFileSync13(abiPath, "utf-8"));
|
|
2364
|
+
const name = path15.basename(entryFile, path15.extname(entryFile));
|
|
2365
|
+
const pkg2 = getPackageInfo(process.cwd());
|
|
2366
|
+
const apiSpec = fromSource(source, abi, { name, version: pkg2.version });
|
|
2367
|
+
return { apiSpec, packageName: pkg2.name ?? name, packageVersion: pkg2.version, lang };
|
|
2368
|
+
}
|
|
2369
|
+
if (!opts.entry)
|
|
2370
|
+
throw new Error("Entry file required");
|
|
2371
|
+
const { spec } = await cachedExtract(opts.entry);
|
|
2372
|
+
const pkg = getPackageInfo(process.cwd());
|
|
2373
|
+
return {
|
|
2374
|
+
apiSpec: spec,
|
|
2375
|
+
packageName: pkg.name,
|
|
2376
|
+
packageVersion: pkg.version,
|
|
2377
|
+
lang
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2289
2381
|
// src/commands/coverage.ts
|
|
2290
2382
|
function registerCoverageCommand(program) {
|
|
2291
|
-
program.command("coverage [entry]").description("Measure documentation coverage
|
|
2383
|
+
program.command("coverage [entry]").description("Measure documentation coverage").option("--min <n>", "Minimum coverage 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) => {
|
|
2292
2384
|
const startTime = Date.now();
|
|
2293
2385
|
const version = getVersion();
|
|
2294
2386
|
try {
|
|
2387
|
+
const lang = resolveLang({
|
|
2388
|
+
entry,
|
|
2389
|
+
lang: options.lang,
|
|
2390
|
+
spec: options.spec,
|
|
2391
|
+
abi: options.abi
|
|
2392
|
+
});
|
|
2393
|
+
if (lang !== "typescript" && options.all) {
|
|
2394
|
+
formatError("coverage", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2295
2397
|
if (options.all) {
|
|
2296
2398
|
const allPackages = discoverPackages(process.cwd());
|
|
2297
2399
|
if (!allPackages || allPackages.length === 0) {
|
|
@@ -2333,8 +2435,16 @@ function registerCoverageCommand(program) {
|
|
|
2333
2435
|
return;
|
|
2334
2436
|
}
|
|
2335
2437
|
const { config } = loadConfig();
|
|
2336
|
-
|
|
2337
|
-
|
|
2438
|
+
let entryFile = entry ? path16.resolve(process.cwd(), entry) : undefined;
|
|
2439
|
+
if (lang === "typescript" && !entryFile) {
|
|
2440
|
+
entryFile = config.entry ? path16.resolve(process.cwd(), config.entry) : detectEntry();
|
|
2441
|
+
}
|
|
2442
|
+
const { apiSpec: spec } = await resolveTruth({
|
|
2443
|
+
entry: entryFile,
|
|
2444
|
+
lang,
|
|
2445
|
+
spec: options.spec,
|
|
2446
|
+
abi: options.abi
|
|
2447
|
+
});
|
|
2338
2448
|
const exports = spec.exports ?? [];
|
|
2339
2449
|
const total = exports.length;
|
|
2340
2450
|
const undocumented = [];
|
|
@@ -2382,7 +2492,7 @@ function registerCoverageCommand(program) {
|
|
|
2382
2492
|
}
|
|
2383
2493
|
|
|
2384
2494
|
// src/commands/diff.ts
|
|
2385
|
-
import * as
|
|
2495
|
+
import * as path17 from "node:path";
|
|
2386
2496
|
import { extract as extract5 } from "@openpkg-ts/sdk";
|
|
2387
2497
|
import { categorizeBreakingChanges as categorizeBreakingChanges4, diffSpec as diffSpec4, normalize as normalize5 } from "@openpkg-ts/spec";
|
|
2388
2498
|
|
|
@@ -2456,7 +2566,7 @@ function registerDiffCommand(program) {
|
|
|
2456
2566
|
let totalAdded = 0;
|
|
2457
2567
|
let totalChanged = 0;
|
|
2458
2568
|
for (const pkg of packages) {
|
|
2459
|
-
const relEntry =
|
|
2569
|
+
const relEntry = path17.relative(cwd, pkg.entry);
|
|
2460
2570
|
const oldSpec2 = await extractSpecFromRef(options.base, relEntry, cwd);
|
|
2461
2571
|
const newSpec2 = options.head ? await extractSpecFromRef(options.head, relEntry, cwd) : normalize5((await extract5({ entryFile: pkg.entry })).spec);
|
|
2462
2572
|
const diff2 = diffSpec4(oldSpec2, newSpec2);
|
|
@@ -2526,8 +2636,8 @@ function registerDiffCommand(program) {
|
|
|
2526
2636
|
}
|
|
2527
2637
|
|
|
2528
2638
|
// src/commands/examples.ts
|
|
2529
|
-
import { readFileSync as
|
|
2530
|
-
import * as
|
|
2639
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
2640
|
+
import * as path18 from "node:path";
|
|
2531
2641
|
import { validateExamples } from "@driftdev/sdk";
|
|
2532
2642
|
|
|
2533
2643
|
// src/formatters/examples.ts
|
|
@@ -2606,16 +2716,16 @@ function renderExamples(data) {
|
|
|
2606
2716
|
|
|
2607
2717
|
// src/commands/examples.ts
|
|
2608
2718
|
function findPackagePath(entryFile) {
|
|
2609
|
-
let dir =
|
|
2610
|
-
while (dir !==
|
|
2719
|
+
let dir = path18.dirname(entryFile);
|
|
2720
|
+
while (dir !== path18.dirname(dir)) {
|
|
2611
2721
|
try {
|
|
2612
|
-
|
|
2722
|
+
readFileSync14(path18.join(dir, "package.json"), "utf-8");
|
|
2613
2723
|
return dir;
|
|
2614
2724
|
} catch {
|
|
2615
|
-
dir =
|
|
2725
|
+
dir = path18.dirname(dir);
|
|
2616
2726
|
}
|
|
2617
2727
|
}
|
|
2618
|
-
return
|
|
2728
|
+
return path18.dirname(entryFile);
|
|
2619
2729
|
}
|
|
2620
2730
|
function registerExamplesCommand(program) {
|
|
2621
2731
|
program.command("examples [entry]").description("Validate @example blocks on exports").option("--typecheck", "Type-check examples with TypeScript").option("--run", "Execute examples at runtime (implies --typecheck)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--min <n>", "Minimum presence threshold (exit 1 if below)").action(async (entry, options) => {
|
|
@@ -2683,7 +2793,7 @@ function registerExamplesCommand(program) {
|
|
|
2683
2793
|
return;
|
|
2684
2794
|
}
|
|
2685
2795
|
const { config } = loadConfig();
|
|
2686
|
-
const entryFile = entry ?
|
|
2796
|
+
const entryFile = entry ? path18.resolve(process.cwd(), entry) : config.entry ? path18.resolve(process.cwd(), config.entry) : detectEntry();
|
|
2687
2797
|
const { spec } = await cachedExtract(entryFile);
|
|
2688
2798
|
const exports = spec.exports ?? [];
|
|
2689
2799
|
const packagePath = findPackagePath(entryFile);
|
|
@@ -2718,7 +2828,7 @@ function registerExamplesCommand(program) {
|
|
|
2718
2828
|
}
|
|
2719
2829
|
|
|
2720
2830
|
// src/commands/extract.ts
|
|
2721
|
-
import * as
|
|
2831
|
+
import * as path19 from "node:path";
|
|
2722
2832
|
import { Drift } from "@driftdev/sdk";
|
|
2723
2833
|
import { normalize as normalize6 } from "@openpkg-ts/spec";
|
|
2724
2834
|
|
|
@@ -2735,10 +2845,37 @@ function renderExtract(data) {
|
|
|
2735
2845
|
|
|
2736
2846
|
// src/commands/extract.ts
|
|
2737
2847
|
function registerExtractCommand(program) {
|
|
2738
|
-
program.command("extract [entry]").description("Extract
|
|
2848
|
+
program.command("extract [entry]").description("Extract API spec from a source of truth (TypeScript, Clarity, OpenAPI)").option("-o, --output <file>", "Write JSON to file instead of stdout").option("--only <patterns>", "Include exports matching glob (comma-separated)").option("--ignore <patterns>", "Exclude exports matching glob (comma-separated)").option("--max-depth <n>", "Max type resolution depth", "10").option("--all", "Extract from 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) => {
|
|
2739
2849
|
const startTime = Date.now();
|
|
2740
2850
|
const version = getVersion();
|
|
2741
2851
|
try {
|
|
2852
|
+
const lang = resolveLang({
|
|
2853
|
+
entry,
|
|
2854
|
+
lang: options.lang,
|
|
2855
|
+
spec: options.spec,
|
|
2856
|
+
abi: options.abi
|
|
2857
|
+
});
|
|
2858
|
+
if (lang !== "typescript") {
|
|
2859
|
+
if (options.all || options.only || options.ignore) {
|
|
2860
|
+
formatError("extract", `--all/--only/--ignore not yet supported for ${lang}`, startTime, version);
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
const { apiSpec } = await resolveTruth({
|
|
2864
|
+
entry,
|
|
2865
|
+
lang,
|
|
2866
|
+
spec: options.spec,
|
|
2867
|
+
abi: options.abi
|
|
2868
|
+
});
|
|
2869
|
+
if (options.output) {
|
|
2870
|
+
const { writeFileSync: writeFileSync5 } = await import("node:fs");
|
|
2871
|
+
writeFileSync5(options.output, JSON.stringify(apiSpec, null, 2));
|
|
2872
|
+
process.stderr.write(`drift extract: wrote ${options.output}
|
|
2873
|
+
`);
|
|
2874
|
+
} else {
|
|
2875
|
+
formatOutput("extract", apiSpec, startTime, version, renderExtract);
|
|
2876
|
+
}
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2742
2879
|
if (options.all) {
|
|
2743
2880
|
const allPackages = discoverPackages(process.cwd());
|
|
2744
2881
|
if (!allPackages || allPackages.length === 0) {
|
|
@@ -2759,7 +2896,7 @@ function registerExtractCommand(program) {
|
|
|
2759
2896
|
formatOutput("extract", { packages: specs, ...skipped.length > 0 ? { skipped } : {} }, startTime, version);
|
|
2760
2897
|
return;
|
|
2761
2898
|
}
|
|
2762
|
-
const entryFile = entry ?
|
|
2899
|
+
const entryFile = entry ? path19.resolve(process.cwd(), entry) : detectEntry();
|
|
2763
2900
|
const hasFilters = !!(options.only || options.ignore);
|
|
2764
2901
|
let spec;
|
|
2765
2902
|
if (hasFilters) {
|
|
@@ -2798,8 +2935,8 @@ function registerExtractCommand(program) {
|
|
|
2798
2935
|
}
|
|
2799
2936
|
|
|
2800
2937
|
// src/commands/filter.ts
|
|
2801
|
-
import { readFileSync as
|
|
2802
|
-
import * as
|
|
2938
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
2939
|
+
import * as path20 from "node:path";
|
|
2803
2940
|
import { filterSpec } from "@openpkg-ts/sdk";
|
|
2804
2941
|
|
|
2805
2942
|
// src/formatters/filter.ts
|
|
@@ -2830,8 +2967,8 @@ function registerFilterCommand(program) {
|
|
|
2830
2967
|
const startTime = Date.now();
|
|
2831
2968
|
const version = getVersion();
|
|
2832
2969
|
try {
|
|
2833
|
-
const filePath =
|
|
2834
|
-
const content =
|
|
2970
|
+
const filePath = path20.resolve(process.cwd(), file);
|
|
2971
|
+
const content = readFileSync15(filePath, "utf-8");
|
|
2835
2972
|
const spec = JSON.parse(content);
|
|
2836
2973
|
const criteria = {};
|
|
2837
2974
|
if (options.kind) {
|
|
@@ -2857,13 +2994,17 @@ function registerFilterCommand(program) {
|
|
|
2857
2994
|
}
|
|
2858
2995
|
|
|
2859
2996
|
// src/commands/get.ts
|
|
2860
|
-
import * as
|
|
2997
|
+
import * as path21 from "node:path";
|
|
2861
2998
|
import { getExport, listExports } from "@openpkg-ts/sdk";
|
|
2862
2999
|
|
|
2863
3000
|
// src/formatters/get.ts
|
|
3001
|
+
function col(text, width) {
|
|
3002
|
+
return padRight(text, Math.max(width, text.length + 2));
|
|
3003
|
+
}
|
|
2864
3004
|
function renderGet(data) {
|
|
2865
3005
|
const lines = [""];
|
|
2866
3006
|
const exp = data.export;
|
|
3007
|
+
const schema = typeof exp.schema === "object" && exp.schema !== null ? exp.schema : undefined;
|
|
2867
3008
|
lines.push(indent(`${c.bold(exp.name)}${" ".repeat(Math.max(2, 50 - exp.name.length))}${c.gray(exp.kind)}`));
|
|
2868
3009
|
if (exp.deprecated) {
|
|
2869
3010
|
lines.push(indent(c.yellow("deprecated")));
|
|
@@ -2876,24 +3017,24 @@ function renderGet(data) {
|
|
|
2876
3017
|
lines.push(indent(` ${exp.signature}`));
|
|
2877
3018
|
lines.push("");
|
|
2878
3019
|
}
|
|
2879
|
-
const params = exp.parameters ?? extractParams(
|
|
3020
|
+
const params = exp.parameters ?? extractParams(schema);
|
|
2880
3021
|
if (params.length > 0) {
|
|
2881
3022
|
lines.push(indent(c.gray(" PARAMETERS")));
|
|
2882
3023
|
for (const p of params) {
|
|
2883
3024
|
const req = p.required ? "required" : "optional";
|
|
2884
3025
|
const type = p.type ?? "unknown";
|
|
2885
3026
|
const desc = p.description ? ` ${c.dim(JSON.stringify(p.description))}` : "";
|
|
2886
|
-
lines.push(indent(` ${
|
|
3027
|
+
lines.push(indent(` ${col(p.name ?? "", 16)}${col(type, 24)}${c.gray(req)}${desc}`));
|
|
2887
3028
|
}
|
|
2888
3029
|
lines.push("");
|
|
2889
3030
|
}
|
|
2890
|
-
const returns = exp.returns ?? extractReturns(
|
|
3031
|
+
const returns = exp.returns ?? extractReturns(schema);
|
|
2891
3032
|
if (returns) {
|
|
2892
3033
|
lines.push(indent(c.gray(" RETURNS")));
|
|
2893
3034
|
lines.push(indent(` ${returns.type ?? "void"}`));
|
|
2894
3035
|
lines.push("");
|
|
2895
3036
|
}
|
|
2896
|
-
const members = exp.members ?? extractMembers(
|
|
3037
|
+
const members = exp.members ?? extractMembers(schema);
|
|
2897
3038
|
if (members.length > 0) {
|
|
2898
3039
|
const shown = members.slice(0, 50);
|
|
2899
3040
|
const remaining = members.length - shown.length;
|
|
@@ -2901,7 +3042,7 @@ function renderGet(data) {
|
|
|
2901
3042
|
for (const m of shown) {
|
|
2902
3043
|
const req = m.required ? "required" : "optional";
|
|
2903
3044
|
const type = m.type ?? "";
|
|
2904
|
-
lines.push(indent(` ${
|
|
3045
|
+
lines.push(indent(` ${col(m.name ?? "", 20)}${col(type, 20)}${c.gray(req)}`));
|
|
2905
3046
|
}
|
|
2906
3047
|
if (remaining > 0) {
|
|
2907
3048
|
lines.push(indent(c.gray(` ... ${remaining} more`)));
|
|
@@ -2966,6 +3107,8 @@ function extractPropsFromSchema(schema) {
|
|
|
2966
3107
|
function formatType(schema) {
|
|
2967
3108
|
if (!schema)
|
|
2968
3109
|
return "unknown";
|
|
3110
|
+
if (typeof schema === "string")
|
|
3111
|
+
return schema;
|
|
2969
3112
|
if (schema.$ref)
|
|
2970
3113
|
return schema.$ref.replace("#/types/", "");
|
|
2971
3114
|
if (schema.type === "array" && schema.items)
|
|
@@ -3037,14 +3180,72 @@ function looksLikeFilePath(s) {
|
|
|
3037
3180
|
|
|
3038
3181
|
// src/commands/get.ts
|
|
3039
3182
|
function registerGetCommand(program) {
|
|
3040
|
-
program.command("get <nameOrEntry> [name]").description("Get detailed spec for a single export").action(async (nameOrEntry, name) => {
|
|
3183
|
+
program.command("get <nameOrEntry> [name]").description("Get detailed spec for a single export").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 (nameOrEntry, name, options = {}) => {
|
|
3041
3184
|
const startTime = Date.now();
|
|
3042
3185
|
const version = getVersion();
|
|
3043
3186
|
try {
|
|
3187
|
+
const lang = resolveLang({
|
|
3188
|
+
entry: name ? nameOrEntry : undefined,
|
|
3189
|
+
lang: options.lang,
|
|
3190
|
+
spec: options.spec,
|
|
3191
|
+
abi: options.abi
|
|
3192
|
+
});
|
|
3193
|
+
if (lang !== "typescript") {
|
|
3194
|
+
const entryArg = name ? nameOrEntry : undefined;
|
|
3195
|
+
const exportName2 = name ?? nameOrEntry;
|
|
3196
|
+
const { apiSpec } = await resolveTruth({
|
|
3197
|
+
entry: entryArg,
|
|
3198
|
+
lang,
|
|
3199
|
+
spec: options.spec,
|
|
3200
|
+
abi: options.abi
|
|
3201
|
+
});
|
|
3202
|
+
const allExports = apiSpec.exports ?? [];
|
|
3203
|
+
const exp = allExports.find((e) => e.name === exportName2 || e.id === exportName2);
|
|
3204
|
+
if (!exp) {
|
|
3205
|
+
const suggestions = fuzzyTop(exportName2, allExports);
|
|
3206
|
+
renderNotFound(exportName2, suggestions, startTime, version);
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
const sig = exp.signatures?.[0];
|
|
3210
|
+
const data = {
|
|
3211
|
+
export: {
|
|
3212
|
+
name: exp.name,
|
|
3213
|
+
kind: exp.kind,
|
|
3214
|
+
...exp.description ? { description: exp.description } : {},
|
|
3215
|
+
...exp.deprecated ? { deprecated: true } : {},
|
|
3216
|
+
...sig?.parameters ? {
|
|
3217
|
+
parameters: sig.parameters.map((p) => ({
|
|
3218
|
+
name: p.name,
|
|
3219
|
+
type: schemaTypeString(p.schema),
|
|
3220
|
+
required: p.required,
|
|
3221
|
+
...p.description ? { description: p.description } : {},
|
|
3222
|
+
schema: p.schema
|
|
3223
|
+
}))
|
|
3224
|
+
} : {},
|
|
3225
|
+
...sig?.returns ? {
|
|
3226
|
+
returns: {
|
|
3227
|
+
type: schemaTypeString(sig.returns.schema),
|
|
3228
|
+
...sig.returns.description ? { description: sig.returns.description } : {},
|
|
3229
|
+
schema: sig.returns.schema
|
|
3230
|
+
}
|
|
3231
|
+
} : {},
|
|
3232
|
+
...exp.members ? {
|
|
3233
|
+
members: exp.members.map((m) => ({
|
|
3234
|
+
name: m.name,
|
|
3235
|
+
...m.description ? { description: m.description } : {}
|
|
3236
|
+
}))
|
|
3237
|
+
} : {},
|
|
3238
|
+
...exp.schema ? { schema: exp.schema } : {},
|
|
3239
|
+
...exp.flags ? { flags: exp.flags } : {}
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
formatOutput("get", data, startTime, version, renderGet);
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3044
3245
|
let entryFile;
|
|
3045
3246
|
let exportName;
|
|
3046
3247
|
if (name) {
|
|
3047
|
-
entryFile =
|
|
3248
|
+
entryFile = path21.resolve(process.cwd(), nameOrEntry);
|
|
3048
3249
|
exportName = name;
|
|
3049
3250
|
} else {
|
|
3050
3251
|
entryFile = detectEntry();
|
|
@@ -3054,39 +3255,70 @@ function registerGetCommand(program) {
|
|
|
3054
3255
|
if (!result.export) {
|
|
3055
3256
|
const listResult = await listExports({ entryFile });
|
|
3056
3257
|
const suggestions = fuzzyTop(exportName, listResult.exports);
|
|
3057
|
-
|
|
3058
|
-
const lines = [
|
|
3059
|
-
"",
|
|
3060
|
-
indent(`${c.red("x")} Export "${exportName}" not found.`),
|
|
3061
|
-
"",
|
|
3062
|
-
indent(c.gray("Similar:"))
|
|
3063
|
-
];
|
|
3064
|
-
for (const s of suggestions) {
|
|
3065
|
-
lines.push(indent(` ${s}`));
|
|
3066
|
-
}
|
|
3067
|
-
lines.push("");
|
|
3068
|
-
lines.push(indent(`drift get ${suggestions[0]}`));
|
|
3069
|
-
lines.push("");
|
|
3070
|
-
process.stdout.write(lines.join(`
|
|
3071
|
-
`));
|
|
3072
|
-
process.exitCode = 1;
|
|
3073
|
-
} else if (suggestions.length > 0) {
|
|
3074
|
-
formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version);
|
|
3075
|
-
} else {
|
|
3076
|
-
formatError("get", `Export '${exportName}' not found`, startTime, version);
|
|
3077
|
-
}
|
|
3258
|
+
renderNotFound(exportName, suggestions, startTime, version);
|
|
3078
3259
|
return;
|
|
3079
3260
|
}
|
|
3080
|
-
|
|
3261
|
+
const types = Object.fromEntries((result.types ?? []).map((t) => [
|
|
3262
|
+
t.name,
|
|
3263
|
+
t.schema ?? {}
|
|
3264
|
+
]));
|
|
3265
|
+
formatOutput("get", { export: result.export, types }, startTime, version, renderGet);
|
|
3081
3266
|
} catch (err) {
|
|
3082
3267
|
formatError("get", err instanceof Error ? err.message : String(err), startTime, version);
|
|
3083
3268
|
}
|
|
3084
3269
|
});
|
|
3085
3270
|
}
|
|
3271
|
+
function schemaTypeString(schema, depth = 0) {
|
|
3272
|
+
if (schema === undefined || schema === null)
|
|
3273
|
+
return;
|
|
3274
|
+
if (typeof schema === "string")
|
|
3275
|
+
return schema;
|
|
3276
|
+
if (typeof schema !== "object" || depth > 3)
|
|
3277
|
+
return;
|
|
3278
|
+
const s = schema;
|
|
3279
|
+
if (typeof s.$ref === "string")
|
|
3280
|
+
return s.$ref.split("/").pop();
|
|
3281
|
+
const composite = s.oneOf ?? s.anyOf;
|
|
3282
|
+
if (Array.isArray(composite)) {
|
|
3283
|
+
const arms = composite.map((arm) => schemaTypeString(arm, depth + 1) ?? "unknown").filter((v, i, a) => a.indexOf(v) === i);
|
|
3284
|
+
return arms.join(" | ");
|
|
3285
|
+
}
|
|
3286
|
+
if (s.type === "array") {
|
|
3287
|
+
const item = schemaTypeString(s.items, depth + 1);
|
|
3288
|
+
return item ? `${item}[]` : "array";
|
|
3289
|
+
}
|
|
3290
|
+
if (typeof s.title === "string" && (s.type === "object" || s.type === undefined))
|
|
3291
|
+
return s.title;
|
|
3292
|
+
if (typeof s.type === "string")
|
|
3293
|
+
return s.type;
|
|
3294
|
+
return;
|
|
3295
|
+
}
|
|
3296
|
+
function renderNotFound(exportName, suggestions, startTime, version) {
|
|
3297
|
+
if (suggestions.length > 0 && shouldRenderHuman()) {
|
|
3298
|
+
const lines = [
|
|
3299
|
+
"",
|
|
3300
|
+
indent(`${c.red("x")} Export "${exportName}" not found.`),
|
|
3301
|
+
"",
|
|
3302
|
+
indent(c.gray("Similar:"))
|
|
3303
|
+
];
|
|
3304
|
+
for (const s of suggestions) {
|
|
3305
|
+
lines.push(indent(` ${s}`));
|
|
3306
|
+
}
|
|
3307
|
+
lines.push("");
|
|
3308
|
+
lines.push(indent(`drift get ${suggestions[0]}`));
|
|
3309
|
+
lines.push("");
|
|
3310
|
+
process.stdout.write(lines.join(`
|
|
3311
|
+
`));
|
|
3312
|
+
process.exitCode = 1;
|
|
3313
|
+
} else if (suggestions.length > 0) {
|
|
3314
|
+
formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version);
|
|
3315
|
+
} else {
|
|
3316
|
+
formatError("get", `Export '${exportName}' not found`, startTime, version);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3086
3319
|
|
|
3087
3320
|
// src/commands/health.ts
|
|
3088
|
-
import
|
|
3089
|
-
import * as path21 from "node:path";
|
|
3321
|
+
import * as path22 from "node:path";
|
|
3090
3322
|
import { computeDrift as computeDrift3 } from "@driftdev/sdk";
|
|
3091
3323
|
|
|
3092
3324
|
// src/formatters/health.ts
|
|
@@ -3153,23 +3385,21 @@ function computeHealth(totalExports, documented, issues) {
|
|
|
3153
3385
|
}
|
|
3154
3386
|
|
|
3155
3387
|
// src/commands/health.ts
|
|
3156
|
-
function getPackageInfo(cwd) {
|
|
3157
|
-
const pkgPath = path21.join(cwd, "package.json");
|
|
3158
|
-
if (!existsSync12(pkgPath))
|
|
3159
|
-
return {};
|
|
3160
|
-
try {
|
|
3161
|
-
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
3162
|
-
return { name: pkg.name, version: pkg.version };
|
|
3163
|
-
} catch (err) {
|
|
3164
|
-
formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
|
|
3165
|
-
return {};
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3168
3388
|
function registerHealthCommand(program) {
|
|
3169
|
-
program.command("health [entry]").description("Show documentation health score").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").action(async (entry, options) => {
|
|
3389
|
+
program.command("health [entry]").description("Show documentation health score").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) => {
|
|
3170
3390
|
const startTime = Date.now();
|
|
3171
3391
|
const version = getVersion();
|
|
3172
3392
|
try {
|
|
3393
|
+
const lang = resolveLang({
|
|
3394
|
+
entry,
|
|
3395
|
+
lang: options.lang,
|
|
3396
|
+
spec: options.spec,
|
|
3397
|
+
abi: options.abi
|
|
3398
|
+
});
|
|
3399
|
+
if (lang !== "typescript" && options.all) {
|
|
3400
|
+
formatError("health", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3173
3403
|
if (options.all) {
|
|
3174
3404
|
const allPackages = discoverPackages(process.cwd());
|
|
3175
3405
|
if (!allPackages || allPackages.length === 0) {
|
|
@@ -3185,8 +3415,8 @@ function registerHealthCommand(program) {
|
|
|
3185
3415
|
const rows = [];
|
|
3186
3416
|
let totalDoc = 0;
|
|
3187
3417
|
let totalAll = 0;
|
|
3188
|
-
for (const
|
|
3189
|
-
const { spec: spec2 } = await cachedExtract(
|
|
3418
|
+
for (const pkg of packages) {
|
|
3419
|
+
const { spec: spec2 } = await cachedExtract(pkg.entry);
|
|
3190
3420
|
const exps = spec2.exports ?? [];
|
|
3191
3421
|
let doc = 0;
|
|
3192
3422
|
for (const e of exps) {
|
|
@@ -3194,7 +3424,7 @@ function registerHealthCommand(program) {
|
|
|
3194
3424
|
doc++;
|
|
3195
3425
|
}
|
|
3196
3426
|
const score = exps.length > 0 ? Math.round(doc / exps.length * 100) : 100;
|
|
3197
|
-
rows.push({ name:
|
|
3427
|
+
rows.push({ name: pkg.name, exports: exps.length, score });
|
|
3198
3428
|
totalDoc += doc;
|
|
3199
3429
|
totalAll += exps.length;
|
|
3200
3430
|
}
|
|
@@ -3208,8 +3438,15 @@ function registerHealthCommand(program) {
|
|
|
3208
3438
|
return;
|
|
3209
3439
|
}
|
|
3210
3440
|
const { config } = loadConfig();
|
|
3211
|
-
|
|
3212
|
-
|
|
3441
|
+
let entryFile = entry ? path22.resolve(process.cwd(), entry) : undefined;
|
|
3442
|
+
if (lang === "typescript" && !entryFile) {
|
|
3443
|
+
entryFile = config.entry ? path22.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3444
|
+
}
|
|
3445
|
+
const {
|
|
3446
|
+
apiSpec: spec,
|
|
3447
|
+
packageName,
|
|
3448
|
+
packageVersion
|
|
3449
|
+
} = await resolveTruth({ entry: entryFile, lang, spec: options.spec, abi: options.abi });
|
|
3213
3450
|
const exports = spec.exports ?? [];
|
|
3214
3451
|
const total = exports.length;
|
|
3215
3452
|
let documented = 0;
|
|
@@ -3226,11 +3463,10 @@ function registerHealthCommand(program) {
|
|
|
3226
3463
|
}
|
|
3227
3464
|
}
|
|
3228
3465
|
const health = computeHealth(total, documented, issues);
|
|
3229
|
-
const pkg = getPackageInfo(process.cwd());
|
|
3230
3466
|
const data = {
|
|
3231
3467
|
...health,
|
|
3232
|
-
packageName
|
|
3233
|
-
packageVersion
|
|
3468
|
+
packageName,
|
|
3469
|
+
packageVersion
|
|
3234
3470
|
};
|
|
3235
3471
|
let min = options.min ? parseInt(options.min, 10) : config.coverage?.min;
|
|
3236
3472
|
if (min !== undefined && config.coverage?.ratchet) {
|
|
@@ -3267,7 +3503,7 @@ function registerHealthCommand(program) {
|
|
|
3267
3503
|
// src/commands/init.ts
|
|
3268
3504
|
init_global();
|
|
3269
3505
|
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3270
|
-
import * as
|
|
3506
|
+
import * as path23 from "node:path";
|
|
3271
3507
|
import { extract as extract6 } from "@openpkg-ts/sdk";
|
|
3272
3508
|
import { normalize as normalize7 } from "@openpkg-ts/spec";
|
|
3273
3509
|
|
|
@@ -3573,10 +3809,10 @@ ${detailLine}`);
|
|
|
3573
3809
|
|
|
3574
3810
|
// src/commands/init.ts
|
|
3575
3811
|
async function scanPackage(cwd, pkgDir) {
|
|
3576
|
-
const absDir =
|
|
3812
|
+
const absDir = path23.join(cwd, pkgDir);
|
|
3577
3813
|
if (!existsSync13(absDir))
|
|
3578
3814
|
return null;
|
|
3579
|
-
const pkgPath =
|
|
3815
|
+
const pkgPath = path23.join(absDir, "package.json");
|
|
3580
3816
|
let name = pkgDir;
|
|
3581
3817
|
if (existsSync13(pkgPath)) {
|
|
3582
3818
|
try {
|
|
@@ -3598,7 +3834,7 @@ async function scanPackage(cwd, pkgDir) {
|
|
|
3598
3834
|
}
|
|
3599
3835
|
const coverage = total > 0 ? Math.round(documented / total * 100) : 100;
|
|
3600
3836
|
const health = Math.round(coverage * 0.5 + 100 * 0.5);
|
|
3601
|
-
return { name, entry:
|
|
3837
|
+
return { name, entry: path23.relative(cwd, entryFile), exports: total, coverage, health };
|
|
3602
3838
|
} catch {
|
|
3603
3839
|
return null;
|
|
3604
3840
|
}
|
|
@@ -3634,7 +3870,7 @@ function registerInitCommand(program) {
|
|
|
3634
3870
|
return;
|
|
3635
3871
|
}
|
|
3636
3872
|
const config = generateConfig(packages);
|
|
3637
|
-
const configPath = opts.project ?
|
|
3873
|
+
const configPath = opts.project ? path23.resolve(cwd, "drift.config.json") : getGlobalConfigPath();
|
|
3638
3874
|
if (!opts.project) {
|
|
3639
3875
|
const globalDir = getGlobalDir();
|
|
3640
3876
|
if (!existsSync13(globalDir))
|
|
@@ -3659,7 +3895,7 @@ function registerInitCommand(program) {
|
|
|
3659
3895
|
|
|
3660
3896
|
// src/commands/lint.ts
|
|
3661
3897
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
3662
|
-
import * as
|
|
3898
|
+
import * as path24 from "node:path";
|
|
3663
3899
|
import {
|
|
3664
3900
|
buildExportRegistry,
|
|
3665
3901
|
computeDrift as computeDrift4,
|
|
@@ -3703,10 +3939,20 @@ function renderLint(data, next) {
|
|
|
3703
3939
|
|
|
3704
3940
|
// src/commands/lint.ts
|
|
3705
3941
|
function registerLintCommand(program) {
|
|
3706
|
-
program.command("lint [entry]").description("Cross-reference
|
|
3942
|
+
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) => {
|
|
3707
3943
|
const startTime = Date.now();
|
|
3708
3944
|
const version = getVersion();
|
|
3709
3945
|
try {
|
|
3946
|
+
const lang = resolveLang({
|
|
3947
|
+
entry,
|
|
3948
|
+
lang: options.lang,
|
|
3949
|
+
spec: options.spec,
|
|
3950
|
+
abi: options.abi
|
|
3951
|
+
});
|
|
3952
|
+
if (lang !== "typescript" && options.all) {
|
|
3953
|
+
formatError("lint", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3710
3956
|
if (options.all) {
|
|
3711
3957
|
const allPackages = discoverPackages(process.cwd());
|
|
3712
3958
|
if (!allPackages || allPackages.length === 0) {
|
|
@@ -3745,8 +3991,16 @@ function registerLintCommand(program) {
|
|
|
3745
3991
|
formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
|
|
3746
3992
|
return;
|
|
3747
3993
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3994
|
+
let entryFile = entry ? path24.resolve(process.cwd(), entry) : undefined;
|
|
3995
|
+
if (lang === "typescript" && !entryFile) {
|
|
3996
|
+
entryFile = config.entry ? path24.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3997
|
+
}
|
|
3998
|
+
const { apiSpec: spec } = await resolveTruth({
|
|
3999
|
+
entry: entryFile,
|
|
4000
|
+
lang,
|
|
4001
|
+
spec: options.spec,
|
|
4002
|
+
abi: options.abi
|
|
4003
|
+
});
|
|
3750
4004
|
const driftResult = computeDrift4(spec);
|
|
3751
4005
|
const issues = [];
|
|
3752
4006
|
for (const [exportName, drifts] of driftResult.exports) {
|
|
@@ -3760,27 +4014,28 @@ function registerLintCommand(program) {
|
|
|
3760
4014
|
});
|
|
3761
4015
|
}
|
|
3762
4016
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
4017
|
+
if (lang === "typescript")
|
|
4018
|
+
try {
|
|
4019
|
+
const pkgJsonPath = path24.resolve(process.cwd(), "package.json");
|
|
4020
|
+
const pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
|
|
4021
|
+
const packageName = pkgJson.name;
|
|
4022
|
+
if (packageName) {
|
|
4023
|
+
const registry = buildExportRegistry(spec);
|
|
4024
|
+
const markdownFiles = discoverMarkdownFiles(process.cwd(), config.docs);
|
|
4025
|
+
const proseDrifts = detectProseDrift({ packageName, markdownFiles, registry });
|
|
4026
|
+
for (const drift of proseDrifts) {
|
|
4027
|
+
issues.push({
|
|
4028
|
+
export: drift.target ?? "",
|
|
4029
|
+
issue: drift.issue,
|
|
4030
|
+
...drift.suggestion ? { location: drift.suggestion } : {},
|
|
4031
|
+
filePath: drift.filePath,
|
|
4032
|
+
line: drift.line
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
3779
4035
|
}
|
|
4036
|
+
} catch (err) {
|
|
4037
|
+
formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
3780
4038
|
}
|
|
3781
|
-
} catch (err) {
|
|
3782
|
-
formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
3783
|
-
}
|
|
3784
4039
|
const data = { issues, count: issues.length };
|
|
3785
4040
|
const next = issues.length > 0 ? {
|
|
3786
4041
|
suggested: "drift-fix skill",
|
|
@@ -3804,7 +4059,7 @@ function registerLintCommand(program) {
|
|
|
3804
4059
|
}
|
|
3805
4060
|
|
|
3806
4061
|
// src/commands/list.ts
|
|
3807
|
-
import * as
|
|
4062
|
+
import * as path25 from "node:path";
|
|
3808
4063
|
import { computeDrift as computeDrift5 } from "@driftdev/sdk";
|
|
3809
4064
|
import { listExports as listExports2 } from "@openpkg-ts/sdk";
|
|
3810
4065
|
|
|
@@ -3861,10 +4116,20 @@ function renderList(data) {
|
|
|
3861
4116
|
|
|
3862
4117
|
// src/commands/list.ts
|
|
3863
4118
|
function registerListCommand(program) {
|
|
3864
|
-
program.command("list [searchOrEntry]").description("List exports (positional arg = search term or entry file)").option("--kind <kinds>", "Filter by kind (comma-separated)").option("--undocumented", "Only exports missing
|
|
4119
|
+
program.command("list [searchOrEntry]").description("List exports (positional arg = search term or entry file)").option("--kind <kinds>", "Filter by kind (comma-separated)").option("--undocumented", "Only exports missing docs").option("--drifted", "Only exports with stale docs").option("--full", "Show full list (no truncation)").option("--all", "Run across all workspace packages").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 (searchOrEntry, options) => {
|
|
3865
4120
|
const startTime = Date.now();
|
|
3866
4121
|
const version = getVersion();
|
|
3867
4122
|
try {
|
|
4123
|
+
const lang = resolveLang({
|
|
4124
|
+
entry: searchOrEntry,
|
|
4125
|
+
lang: options.lang,
|
|
4126
|
+
spec: options.spec,
|
|
4127
|
+
abi: options.abi
|
|
4128
|
+
});
|
|
4129
|
+
if (lang !== "typescript" && options.all) {
|
|
4130
|
+
formatError("list", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
4131
|
+
return;
|
|
4132
|
+
}
|
|
3868
4133
|
if (options.all) {
|
|
3869
4134
|
const packages = discoverPackages(process.cwd());
|
|
3870
4135
|
if (!packages || packages.length === 0) {
|
|
@@ -3884,23 +4149,50 @@ function registerListCommand(program) {
|
|
|
3884
4149
|
formatOutput("list", { packages: rows, filter: filter2 }, startTime, version, renderBatchList);
|
|
3885
4150
|
return;
|
|
3886
4151
|
}
|
|
3887
|
-
let entryFile;
|
|
3888
4152
|
let searchTerm;
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4153
|
+
let exports;
|
|
4154
|
+
let driftedNames;
|
|
4155
|
+
if (lang !== "typescript") {
|
|
4156
|
+
const entryArg = lang === "clarity" ? searchOrEntry : undefined;
|
|
4157
|
+
if (lang === "openapi")
|
|
4158
|
+
searchTerm = searchOrEntry;
|
|
4159
|
+
const { apiSpec } = await resolveTruth({
|
|
4160
|
+
entry: entryArg,
|
|
4161
|
+
lang,
|
|
4162
|
+
spec: options.spec,
|
|
4163
|
+
abi: options.abi
|
|
4164
|
+
});
|
|
4165
|
+
exports = (apiSpec.exports ?? []).map((e) => ({
|
|
4166
|
+
name: e.name,
|
|
4167
|
+
kind: e.kind,
|
|
4168
|
+
description: e.description,
|
|
4169
|
+
...e.deprecated ? { deprecated: true } : {}
|
|
4170
|
+
}));
|
|
4171
|
+
if (options.drifted) {
|
|
4172
|
+
driftedNames = new Set(computeDrift5(apiSpec).exports.keys());
|
|
4173
|
+
}
|
|
3894
4174
|
} else {
|
|
3895
|
-
entryFile
|
|
4175
|
+
let entryFile;
|
|
4176
|
+
if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
|
|
4177
|
+
entryFile = path25.resolve(process.cwd(), searchOrEntry);
|
|
4178
|
+
} else if (searchOrEntry) {
|
|
4179
|
+
entryFile = detectEntry();
|
|
4180
|
+
searchTerm = searchOrEntry;
|
|
4181
|
+
} else {
|
|
4182
|
+
entryFile = detectEntry();
|
|
4183
|
+
}
|
|
4184
|
+
const result = await listExports2({ entryFile });
|
|
4185
|
+
exports = result.exports.map((e) => ({
|
|
4186
|
+
name: e.name,
|
|
4187
|
+
kind: e.kind,
|
|
4188
|
+
description: e.description,
|
|
4189
|
+
...e.deprecated ? { deprecated: true } : {}
|
|
4190
|
+
}));
|
|
4191
|
+
if (options.drifted) {
|
|
4192
|
+
const { spec } = await cachedExtract(entryFile);
|
|
4193
|
+
driftedNames = new Set(computeDrift5(spec).exports.keys());
|
|
4194
|
+
}
|
|
3896
4195
|
}
|
|
3897
|
-
const result = await listExports2({ entryFile });
|
|
3898
|
-
let exports = result.exports.map((e) => ({
|
|
3899
|
-
name: e.name,
|
|
3900
|
-
kind: e.kind,
|
|
3901
|
-
description: e.description,
|
|
3902
|
-
...e.deprecated ? { deprecated: true } : {}
|
|
3903
|
-
}));
|
|
3904
4196
|
if (options.kind) {
|
|
3905
4197
|
const kinds = new Set(options.kind.split(",").map((k) => k.trim().toLowerCase()));
|
|
3906
4198
|
exports = exports.filter((e) => kinds.has(e.kind));
|
|
@@ -3908,11 +4200,9 @@ function registerListCommand(program) {
|
|
|
3908
4200
|
if (options.undocumented) {
|
|
3909
4201
|
exports = exports.filter((e) => !e.description || e.description.trim().length === 0);
|
|
3910
4202
|
}
|
|
3911
|
-
if (
|
|
3912
|
-
const
|
|
3913
|
-
|
|
3914
|
-
const driftedNames = new Set(driftResult.exports.keys());
|
|
3915
|
-
exports = exports.filter((e) => driftedNames.has(e.name));
|
|
4203
|
+
if (driftedNames) {
|
|
4204
|
+
const names = driftedNames;
|
|
4205
|
+
exports = exports.filter((e) => names.has(e.name));
|
|
3916
4206
|
}
|
|
3917
4207
|
if (searchTerm) {
|
|
3918
4208
|
const matches = fuzzySearch(searchTerm, exports);
|
|
@@ -3937,10 +4227,166 @@ function registerListCommand(program) {
|
|
|
3937
4227
|
});
|
|
3938
4228
|
}
|
|
3939
4229
|
|
|
4230
|
+
// src/commands/mcp.ts
|
|
4231
|
+
import { spawn } from "node:child_process";
|
|
4232
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4233
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4234
|
+
import { z } from "zod";
|
|
4235
|
+
var truthShape = {
|
|
4236
|
+
cwd: z.string().optional().describe("Directory to run in (project root; where package.json/config live)"),
|
|
4237
|
+
entry: z.string().optional().describe("Entry file: TypeScript entry or Clarity .clar source. Omit for OpenAPI specs."),
|
|
4238
|
+
lang: z.enum(["typescript", "clarity", "openapi"]).optional().describe("Source language override; inferred from spec/abi/.clar extension otherwise"),
|
|
4239
|
+
spec: z.string().optional().describe("OpenAPI 3.x JSON document — local path or https URL. Implies lang=openapi."),
|
|
4240
|
+
abi: z.string().optional().describe("Clarity ABI JSON path (required for Clarity sources)")
|
|
4241
|
+
};
|
|
4242
|
+
function truthFlags(args) {
|
|
4243
|
+
const out = [];
|
|
4244
|
+
if (args.lang)
|
|
4245
|
+
out.push("--lang", args.lang);
|
|
4246
|
+
if (args.spec)
|
|
4247
|
+
out.push("--spec", args.spec);
|
|
4248
|
+
if (args.abi)
|
|
4249
|
+
out.push("--abi", args.abi);
|
|
4250
|
+
return out;
|
|
4251
|
+
}
|
|
4252
|
+
function runDrift(cliArgs, cwd) {
|
|
4253
|
+
return new Promise((resolve18) => {
|
|
4254
|
+
const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
|
|
4255
|
+
cwd: cwd ?? process.cwd(),
|
|
4256
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
4257
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4258
|
+
});
|
|
4259
|
+
let stdout = "";
|
|
4260
|
+
let stderr = "";
|
|
4261
|
+
child.stdout.on("data", (d) => {
|
|
4262
|
+
stdout += d;
|
|
4263
|
+
});
|
|
4264
|
+
child.stderr.on("data", (d) => {
|
|
4265
|
+
stderr += d;
|
|
4266
|
+
});
|
|
4267
|
+
child.on("close", (code) => {
|
|
4268
|
+
const text = stdout.trim() || stderr.trim() || `drift exited with code ${code}`;
|
|
4269
|
+
let ok = code === 0;
|
|
4270
|
+
try {
|
|
4271
|
+
ok = JSON.parse(stdout).ok === true;
|
|
4272
|
+
} catch {}
|
|
4273
|
+
resolve18({ text, ok });
|
|
4274
|
+
});
|
|
4275
|
+
child.on("error", (err) => resolve18({ text: `Failed to run drift: ${err.message}`, ok: false }));
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
function toResult({ text, ok }) {
|
|
4279
|
+
return { content: [{ type: "text", text }], isError: !ok };
|
|
4280
|
+
}
|
|
4281
|
+
function registerMcpCommand(program) {
|
|
4282
|
+
program.command("mcp").description("Run an MCP stdio server exposing drift tools to agents").action(async () => {
|
|
4283
|
+
const server = new McpServer({ name: "drift", version: getVersion() });
|
|
4284
|
+
server.registerTool("drift_extract", {
|
|
4285
|
+
title: "Extract API spec",
|
|
4286
|
+
description: "Extract the full machine-readable API spec from a source of truth: a TypeScript package (entry auto-detected from cwd), an OpenAPI 3.x document (spec path/URL), or a Clarity contract (entry .clar + abi). Returns every export with signatures, types, and docs. This is ground truth — use it instead of assuming what an API looks like.",
|
|
4287
|
+
inputSchema: truthShape
|
|
4288
|
+
}, async (args) => toResult(await runDrift(["extract", ...args.entry ? [args.entry] : [], ...truthFlags(args)], args.cwd)));
|
|
4289
|
+
server.registerTool("drift_list", {
|
|
4290
|
+
title: "List exports/operations",
|
|
4291
|
+
description: "List every export (TypeScript), operation (OpenAPI), or function (Clarity) in an API surface: name, kind, one-line description, deprecated flag. Cheap way to check what exists before drilling in with drift_get. Supports filtering to undocumented or drifted items.",
|
|
4292
|
+
inputSchema: {
|
|
4293
|
+
...truthShape,
|
|
4294
|
+
search: z.string().optional().describe("Fuzzy search term"),
|
|
4295
|
+
kind: z.string().optional().describe("Filter by kind (comma-separated)"),
|
|
4296
|
+
undocumented: z.boolean().optional().describe("Only items missing docs"),
|
|
4297
|
+
drifted: z.boolean().optional().describe("Only items whose docs drifted")
|
|
4298
|
+
}
|
|
4299
|
+
}, async (args) => {
|
|
4300
|
+
const cli = ["list"];
|
|
4301
|
+
if (args.entry)
|
|
4302
|
+
cli.push(args.entry);
|
|
4303
|
+
else if (args.search)
|
|
4304
|
+
cli.push(args.search);
|
|
4305
|
+
if (args.kind)
|
|
4306
|
+
cli.push("--kind", args.kind);
|
|
4307
|
+
if (args.undocumented)
|
|
4308
|
+
cli.push("--undocumented");
|
|
4309
|
+
if (args.drifted)
|
|
4310
|
+
cli.push("--drifted");
|
|
4311
|
+
cli.push("--full");
|
|
4312
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4313
|
+
});
|
|
4314
|
+
server.registerTool("drift_get", {
|
|
4315
|
+
title: "Get one export/operation",
|
|
4316
|
+
description: "Get the authoritative definition of a single export, endpoint operation, or contract function by name: parameters with types/required/descriptions, return shape, deprecation, referenced types. Use this to verify every claim a docs page makes — one drift_get per claim, never from memory. Unknown names return fuzzy suggestions.",
|
|
4317
|
+
inputSchema: {
|
|
4318
|
+
...truthShape,
|
|
4319
|
+
name: z.string().describe("Export/operation name (e.g. candidateInfo, transfer, createClient)")
|
|
4320
|
+
}
|
|
4321
|
+
}, async (args) => {
|
|
4322
|
+
const cli = ["get"];
|
|
4323
|
+
if (args.entry)
|
|
4324
|
+
cli.push(args.entry, args.name);
|
|
4325
|
+
else
|
|
4326
|
+
cli.push(args.name);
|
|
4327
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4328
|
+
});
|
|
4329
|
+
server.registerTool("drift_scan", {
|
|
4330
|
+
title: "Scan docs health",
|
|
4331
|
+
description: "Full docs-drift scan of an API surface: coverage score, drift issues with file/line locations, health score. The one-shot summary — use drift_list/drift_get for targeted follow-up.",
|
|
4332
|
+
inputSchema: {
|
|
4333
|
+
...truthShape,
|
|
4334
|
+
min: z.number().optional().describe("Minimum health threshold (result.pass=false below it)")
|
|
4335
|
+
}
|
|
4336
|
+
}, async (args) => {
|
|
4337
|
+
const cli = ["scan"];
|
|
4338
|
+
if (args.entry)
|
|
4339
|
+
cli.push(args.entry);
|
|
4340
|
+
if (args.min !== undefined)
|
|
4341
|
+
cli.push("--min", String(args.min));
|
|
4342
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4343
|
+
});
|
|
4344
|
+
server.registerTool("drift_diff", {
|
|
4345
|
+
title: "Diff two API specs",
|
|
4346
|
+
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.",
|
|
4347
|
+
inputSchema: {
|
|
4348
|
+
cwd: truthShape.cwd,
|
|
4349
|
+
old: z.string().optional().describe("Old spec file path"),
|
|
4350
|
+
new: z.string().optional().describe("New spec file path"),
|
|
4351
|
+
base: z.string().optional().describe("Git ref for old spec (alternative to files)"),
|
|
4352
|
+
head: z.string().optional().describe("Git ref for new spec (default: working tree)"),
|
|
4353
|
+
entry: z.string().optional().describe("Entry file for git ref extraction")
|
|
4354
|
+
}
|
|
4355
|
+
}, async (args) => toResult(await runDrift(diffArgs("diff", args), args.cwd)));
|
|
4356
|
+
server.registerTool("drift_breaking", {
|
|
4357
|
+
title: "Detect breaking changes",
|
|
4358
|
+
description: "Detect breaking API changes between two specs or git refs (TypeScript only today). Use to check whether docs claiming compatibility are still true.",
|
|
4359
|
+
inputSchema: {
|
|
4360
|
+
cwd: truthShape.cwd,
|
|
4361
|
+
old: z.string().optional().describe("Old spec file path"),
|
|
4362
|
+
new: z.string().optional().describe("New spec file path"),
|
|
4363
|
+
base: z.string().optional().describe("Git ref for old spec (alternative to files)"),
|
|
4364
|
+
head: z.string().optional().describe("Git ref for new spec (default: working tree)"),
|
|
4365
|
+
entry: z.string().optional().describe("Entry file for git ref extraction")
|
|
4366
|
+
}
|
|
4367
|
+
}, async (args) => toResult(await runDrift(diffArgs("breaking", args), args.cwd)));
|
|
4368
|
+
await server.connect(new StdioServerTransport);
|
|
4369
|
+
});
|
|
4370
|
+
}
|
|
4371
|
+
function diffArgs(command, args) {
|
|
4372
|
+
const cli = [command];
|
|
4373
|
+
if (args.old)
|
|
4374
|
+
cli.push(args.old);
|
|
4375
|
+
if (args.new)
|
|
4376
|
+
cli.push(args.new);
|
|
4377
|
+
if (args.base)
|
|
4378
|
+
cli.push("--base", args.base);
|
|
4379
|
+
if (args.head)
|
|
4380
|
+
cli.push("--head", args.head);
|
|
4381
|
+
if (args.entry)
|
|
4382
|
+
cli.push("--entry", args.entry);
|
|
4383
|
+
return cli;
|
|
4384
|
+
}
|
|
4385
|
+
|
|
3940
4386
|
// src/commands/release.ts
|
|
3941
4387
|
import { execSync as execSync4 } from "node:child_process";
|
|
3942
4388
|
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
3943
|
-
import * as
|
|
4389
|
+
import * as path26 from "node:path";
|
|
3944
4390
|
import { computeDrift as computeDrift6 } from "@driftdev/sdk";
|
|
3945
4391
|
|
|
3946
4392
|
// src/formatters/release.ts
|
|
@@ -3988,7 +4434,7 @@ function getLastTag() {
|
|
|
3988
4434
|
}
|
|
3989
4435
|
}
|
|
3990
4436
|
function getPackageVersion(cwd) {
|
|
3991
|
-
const pkgPath =
|
|
4437
|
+
const pkgPath = path26.join(cwd, "package.json");
|
|
3992
4438
|
if (!existsSync14(pkgPath))
|
|
3993
4439
|
return null;
|
|
3994
4440
|
try {
|
|
@@ -4004,7 +4450,7 @@ function registerReleaseCommand(program) {
|
|
|
4004
4450
|
const cwd = process.cwd();
|
|
4005
4451
|
try {
|
|
4006
4452
|
const { config } = loadConfig();
|
|
4007
|
-
const entryFile = entry ?
|
|
4453
|
+
const entryFile = entry ? path26.resolve(cwd, entry) : config.entry ? path26.resolve(cwd, config.entry) : detectEntry();
|
|
4008
4454
|
const { spec } = await cachedExtract(entryFile);
|
|
4009
4455
|
const exports = spec.exports ?? [];
|
|
4010
4456
|
const total = exports.length;
|
|
@@ -4129,7 +4575,7 @@ function renderReport(data) {
|
|
|
4129
4575
|
// src/utils/scan-packages.ts
|
|
4130
4576
|
import { execSync as execSync5 } from "node:child_process";
|
|
4131
4577
|
import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
|
|
4132
|
-
import * as
|
|
4578
|
+
import * as path27 from "node:path";
|
|
4133
4579
|
import { computeDrift as computeDrift7 } from "@driftdev/sdk";
|
|
4134
4580
|
function detectPackageDirs2(cwd) {
|
|
4135
4581
|
const workspaces = detectWorkspaces(cwd);
|
|
@@ -4148,11 +4594,11 @@ async function scanAllPackages(cwd) {
|
|
|
4148
4594
|
const packageDirs = detectPackageDirs2(cwd);
|
|
4149
4595
|
const results = [];
|
|
4150
4596
|
for (const dir of packageDirs) {
|
|
4151
|
-
const absDir = dir === "." ? cwd :
|
|
4597
|
+
const absDir = dir === "." ? cwd : path27.join(cwd, dir);
|
|
4152
4598
|
if (!existsSync15(absDir))
|
|
4153
4599
|
continue;
|
|
4154
4600
|
let name = dir;
|
|
4155
|
-
const pkgPath =
|
|
4601
|
+
const pkgPath = path27.join(absDir, "package.json");
|
|
4156
4602
|
if (existsSync15(pkgPath)) {
|
|
4157
4603
|
try {
|
|
4158
4604
|
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
@@ -4251,10 +4697,8 @@ function registerReportCommand(program) {
|
|
|
4251
4697
|
}
|
|
4252
4698
|
|
|
4253
4699
|
// src/commands/scan.ts
|
|
4254
|
-
import {
|
|
4255
|
-
import * as
|
|
4256
|
-
import { fromSource } from "@driftdev/clarity-adapter";
|
|
4257
|
-
import { fromDocument } from "@driftdev/openapi-adapter";
|
|
4700
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
4701
|
+
import * as path28 from "node:path";
|
|
4258
4702
|
import {
|
|
4259
4703
|
buildExportRegistry as buildExportRegistry2,
|
|
4260
4704
|
computeDrift as computeDrift8,
|
|
@@ -4321,59 +4765,17 @@ function renderBatchScan(data) {
|
|
|
4321
4765
|
}
|
|
4322
4766
|
|
|
4323
4767
|
// src/commands/scan.ts
|
|
4324
|
-
function getPackageInfo2(cwd) {
|
|
4325
|
-
const pkgPath = path27.join(cwd, "package.json");
|
|
4326
|
-
if (!existsSync16(pkgPath))
|
|
4327
|
-
return {};
|
|
4328
|
-
try {
|
|
4329
|
-
const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
|
|
4330
|
-
return { name: pkg.name, version: pkg.version };
|
|
4331
|
-
} catch (err) {
|
|
4332
|
-
formatWarning(`Could not parse package.json${err instanceof Error ? `: ${err.message}` : ""}`);
|
|
4333
|
-
return {};
|
|
4334
|
-
}
|
|
4335
|
-
}
|
|
4336
|
-
async function loadSpec2(entryFile, lang, abiPath, specPath) {
|
|
4337
|
-
if (lang === "openapi") {
|
|
4338
|
-
if (!specPath)
|
|
4339
|
-
throw new Error("--spec is required when --lang openapi");
|
|
4340
|
-
if (!existsSync16(specPath))
|
|
4341
|
-
throw new Error(`Spec file not found: ${specPath}`);
|
|
4342
|
-
const document = readFileSync20(specPath, "utf-8");
|
|
4343
|
-
const name = path27.basename(specPath, path27.extname(specPath));
|
|
4344
|
-
const apiSpec = fromDocument(document);
|
|
4345
|
-
if (!apiSpec.meta.name || apiSpec.meta.name === "openapi")
|
|
4346
|
-
apiSpec.meta.name = name;
|
|
4347
|
-
return { apiSpec, packageName: apiSpec.meta.name, packageVersion: apiSpec.meta.version };
|
|
4348
|
-
}
|
|
4349
|
-
if (lang === "clarity") {
|
|
4350
|
-
if (!abiPath)
|
|
4351
|
-
throw new Error("--abi is required when --lang clarity");
|
|
4352
|
-
if (!existsSync16(entryFile))
|
|
4353
|
-
throw new Error(`Source file not found: ${entryFile}`);
|
|
4354
|
-
if (!existsSync16(abiPath))
|
|
4355
|
-
throw new Error(`ABI file not found: ${abiPath}`);
|
|
4356
|
-
const source = readFileSync20(entryFile, "utf-8");
|
|
4357
|
-
const abi = JSON.parse(readFileSync20(abiPath, "utf-8"));
|
|
4358
|
-
const name = path27.basename(entryFile, path27.extname(entryFile));
|
|
4359
|
-
const pkg2 = getPackageInfo2(process.cwd());
|
|
4360
|
-
const apiSpec = fromSource(source, abi, { name, version: pkg2.version });
|
|
4361
|
-
return { apiSpec, packageName: pkg2.name ?? name, packageVersion: pkg2.version };
|
|
4362
|
-
}
|
|
4363
|
-
const { spec } = await cachedExtract(entryFile);
|
|
4364
|
-
const pkg = getPackageInfo2(process.cwd());
|
|
4365
|
-
return { apiSpec: spec, packageName: pkg.name, packageVersion: pkg.version };
|
|
4366
|
-
}
|
|
4367
4768
|
function registerScanCommand(program) {
|
|
4368
|
-
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
|
|
4769
|
+
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) => {
|
|
4369
4770
|
const startTime = Date.now();
|
|
4370
4771
|
const version = getVersion();
|
|
4371
4772
|
try {
|
|
4372
|
-
const lang =
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4773
|
+
const lang = resolveLang({
|
|
4774
|
+
entry,
|
|
4775
|
+
lang: options.lang,
|
|
4776
|
+
spec: options.spec,
|
|
4777
|
+
abi: options.abi
|
|
4778
|
+
});
|
|
4377
4779
|
if (lang !== "typescript" && options.all) {
|
|
4378
4780
|
formatError("scan", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
4379
4781
|
return;
|
|
@@ -4439,12 +4841,16 @@ function registerScanCommand(program) {
|
|
|
4439
4841
|
return;
|
|
4440
4842
|
}
|
|
4441
4843
|
const { config } = loadConfig();
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
}
|
|
4446
|
-
const
|
|
4447
|
-
|
|
4844
|
+
let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
|
|
4845
|
+
if (lang === "typescript" && !entryFile) {
|
|
4846
|
+
entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4847
|
+
}
|
|
4848
|
+
const { apiSpec, packageName, packageVersion } = await resolveTruth({
|
|
4849
|
+
entry: entryFile,
|
|
4850
|
+
lang,
|
|
4851
|
+
spec: options.spec,
|
|
4852
|
+
abi: options.abi
|
|
4853
|
+
});
|
|
4448
4854
|
const exports = apiSpec.exports ?? [];
|
|
4449
4855
|
const total = exports.length;
|
|
4450
4856
|
let documented = 0;
|
|
@@ -4468,7 +4874,7 @@ function registerScanCommand(program) {
|
|
|
4468
4874
|
}
|
|
4469
4875
|
if (lang === "typescript") {
|
|
4470
4876
|
try {
|
|
4471
|
-
const pkgJsonPath =
|
|
4877
|
+
const pkgJsonPath = path28.resolve(process.cwd(), "package.json");
|
|
4472
4878
|
const pkgJson = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
|
|
4473
4879
|
const pkgName = pkgJson.name;
|
|
4474
4880
|
if (pkgName) {
|
|
@@ -4576,7 +4982,7 @@ function registerSemverCommand(program) {
|
|
|
4576
4982
|
|
|
4577
4983
|
// src/commands/validate.ts
|
|
4578
4984
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
4579
|
-
import * as
|
|
4985
|
+
import * as path29 from "node:path";
|
|
4580
4986
|
import { validateSpec } from "@openpkg-ts/spec";
|
|
4581
4987
|
|
|
4582
4988
|
// src/formatters/validate.ts
|
|
@@ -4602,7 +5008,7 @@ function registerValidateCommand(program) {
|
|
|
4602
5008
|
const startTime = Date.now();
|
|
4603
5009
|
const version = getVersion();
|
|
4604
5010
|
try {
|
|
4605
|
-
const filePath =
|
|
5011
|
+
const filePath = path29.resolve(process.cwd(), file);
|
|
4606
5012
|
const content = readFileSync21(filePath, "utf-8");
|
|
4607
5013
|
const spec = JSON.parse(content);
|
|
4608
5014
|
const result = validateSpec(spec);
|
|
@@ -4728,13 +5134,13 @@ function extractCapabilities(program) {
|
|
|
4728
5134
|
|
|
4729
5135
|
// src/drift.ts
|
|
4730
5136
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
4731
|
-
var __dirname3 =
|
|
4732
|
-
var packageJson = JSON.parse(readFileSync22(
|
|
5137
|
+
var __dirname3 = path30.dirname(__filename2);
|
|
5138
|
+
var packageJson = JSON.parse(readFileSync22(path30.join(__dirname3, "../package.json"), "utf-8"));
|
|
4733
5139
|
var program = new Command;
|
|
4734
|
-
program.name("drift").description("drift —
|
|
5140
|
+
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) => {
|
|
4735
5141
|
const opts = program.opts();
|
|
4736
5142
|
if (opts.cwd) {
|
|
4737
|
-
process.chdir(
|
|
5143
|
+
process.chdir(path30.resolve(opts.cwd));
|
|
4738
5144
|
}
|
|
4739
5145
|
setOutputMode({ json: opts.json, human: opts.human });
|
|
4740
5146
|
setConfigPath(opts.config);
|
|
@@ -4763,6 +5169,7 @@ registerConfigCommand(program);
|
|
|
4763
5169
|
registerContextCommand(program);
|
|
4764
5170
|
registerCacheCommand(program);
|
|
4765
5171
|
registerCommandsCommand(program);
|
|
5172
|
+
registerMcpCommand(program);
|
|
4766
5173
|
var HUMAN_COMMANDS = new Set(["scan", "ci", "init", "commands"]);
|
|
4767
5174
|
for (const cmd of program.commands) {
|
|
4768
5175
|
if (!HUMAN_COMMANDS.has(cmd.name())) {
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftdev/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Drift CLI -
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"description": "Drift CLI - detect when your docs drift from your code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
7
7
|
"cli",
|
|
8
8
|
"documentation",
|
|
9
9
|
"drift",
|
|
10
10
|
"docs-coverage",
|
|
11
|
-
"drift-detection"
|
|
11
|
+
"drift-detection",
|
|
12
|
+
"openapi",
|
|
13
|
+
"rest",
|
|
14
|
+
"api",
|
|
15
|
+
"agent",
|
|
16
|
+
"mcp"
|
|
12
17
|
],
|
|
13
18
|
"homepage": "https://github.com/ryanwaits/drift#readme",
|
|
14
19
|
"repository": {
|
|
@@ -40,12 +45,14 @@
|
|
|
40
45
|
},
|
|
41
46
|
"dependencies": {
|
|
42
47
|
"@driftdev/clarity-adapter": "^1.0.1",
|
|
43
|
-
"@driftdev/openapi-adapter": "^1.0.
|
|
48
|
+
"@driftdev/openapi-adapter": "^1.0.1",
|
|
44
49
|
"@driftdev/sdk": "^1.4.0",
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
51
|
"@openpkg-ts/sdk": "^0.38.0",
|
|
46
52
|
"@openpkg-ts/spec": "^0.37.0",
|
|
47
53
|
"chalk": "^5.4.1",
|
|
48
|
-
"commander": "^14.0.0"
|
|
54
|
+
"commander": "^14.0.0",
|
|
55
|
+
"zod": "^4.2.1"
|
|
49
56
|
},
|
|
50
57
|
"devDependencies": {
|
|
51
58
|
"@types/bun": "^1.3.14",
|