@driftdev/cli 1.4.0 → 1.5.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 +594 -197
- package/package.json +11 -4
package/dist/drift.js
CHANGED
|
@@ -52,7 +52,7 @@ var init_global = () => {};
|
|
|
52
52
|
|
|
53
53
|
// src/drift.ts
|
|
54
54
|
import { readFileSync as 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,14 @@ 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
|
|
2864
3001
|
function renderGet(data) {
|
|
2865
3002
|
const lines = [""];
|
|
2866
3003
|
const exp = data.export;
|
|
3004
|
+
const schema = typeof exp.schema === "object" && exp.schema !== null ? exp.schema : undefined;
|
|
2867
3005
|
lines.push(indent(`${c.bold(exp.name)}${" ".repeat(Math.max(2, 50 - exp.name.length))}${c.gray(exp.kind)}`));
|
|
2868
3006
|
if (exp.deprecated) {
|
|
2869
3007
|
lines.push(indent(c.yellow("deprecated")));
|
|
@@ -2876,24 +3014,24 @@ function renderGet(data) {
|
|
|
2876
3014
|
lines.push(indent(` ${exp.signature}`));
|
|
2877
3015
|
lines.push("");
|
|
2878
3016
|
}
|
|
2879
|
-
const params = exp.parameters ?? extractParams(
|
|
3017
|
+
const params = exp.parameters ?? extractParams(schema);
|
|
2880
3018
|
if (params.length > 0) {
|
|
2881
3019
|
lines.push(indent(c.gray(" PARAMETERS")));
|
|
2882
3020
|
for (const p of params) {
|
|
2883
3021
|
const req = p.required ? "required" : "optional";
|
|
2884
3022
|
const type = p.type ?? "unknown";
|
|
2885
3023
|
const desc = p.description ? ` ${c.dim(JSON.stringify(p.description))}` : "";
|
|
2886
|
-
lines.push(indent(` ${padRight(p.name, 16)}${padRight(type, 24)}${c.gray(req)}${desc}`));
|
|
3024
|
+
lines.push(indent(` ${padRight(p.name ?? "", 16)}${padRight(type, 24)}${c.gray(req)}${desc}`));
|
|
2887
3025
|
}
|
|
2888
3026
|
lines.push("");
|
|
2889
3027
|
}
|
|
2890
|
-
const returns = exp.returns ?? extractReturns(
|
|
3028
|
+
const returns = exp.returns ?? extractReturns(schema);
|
|
2891
3029
|
if (returns) {
|
|
2892
3030
|
lines.push(indent(c.gray(" RETURNS")));
|
|
2893
3031
|
lines.push(indent(` ${returns.type ?? "void"}`));
|
|
2894
3032
|
lines.push("");
|
|
2895
3033
|
}
|
|
2896
|
-
const members = exp.members ?? extractMembers(
|
|
3034
|
+
const members = exp.members ?? extractMembers(schema);
|
|
2897
3035
|
if (members.length > 0) {
|
|
2898
3036
|
const shown = members.slice(0, 50);
|
|
2899
3037
|
const remaining = members.length - shown.length;
|
|
@@ -2901,7 +3039,7 @@ function renderGet(data) {
|
|
|
2901
3039
|
for (const m of shown) {
|
|
2902
3040
|
const req = m.required ? "required" : "optional";
|
|
2903
3041
|
const type = m.type ?? "";
|
|
2904
|
-
lines.push(indent(` ${padRight(m.name, 20)}${padRight(type, 20)}${c.gray(req)}`));
|
|
3042
|
+
lines.push(indent(` ${padRight(m.name ?? "", 20)}${padRight(type, 20)}${c.gray(req)}`));
|
|
2905
3043
|
}
|
|
2906
3044
|
if (remaining > 0) {
|
|
2907
3045
|
lines.push(indent(c.gray(` ... ${remaining} more`)));
|
|
@@ -2966,6 +3104,8 @@ function extractPropsFromSchema(schema) {
|
|
|
2966
3104
|
function formatType(schema) {
|
|
2967
3105
|
if (!schema)
|
|
2968
3106
|
return "unknown";
|
|
3107
|
+
if (typeof schema === "string")
|
|
3108
|
+
return schema;
|
|
2969
3109
|
if (schema.$ref)
|
|
2970
3110
|
return schema.$ref.replace("#/types/", "");
|
|
2971
3111
|
if (schema.type === "array" && schema.items)
|
|
@@ -3037,14 +3177,72 @@ function looksLikeFilePath(s) {
|
|
|
3037
3177
|
|
|
3038
3178
|
// src/commands/get.ts
|
|
3039
3179
|
function registerGetCommand(program) {
|
|
3040
|
-
program.command("get <nameOrEntry> [name]").description("Get detailed spec for a single export").action(async (nameOrEntry, name) => {
|
|
3180
|
+
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
3181
|
const startTime = Date.now();
|
|
3042
3182
|
const version = getVersion();
|
|
3043
3183
|
try {
|
|
3184
|
+
const lang = resolveLang({
|
|
3185
|
+
entry: name ? nameOrEntry : undefined,
|
|
3186
|
+
lang: options.lang,
|
|
3187
|
+
spec: options.spec,
|
|
3188
|
+
abi: options.abi
|
|
3189
|
+
});
|
|
3190
|
+
if (lang !== "typescript") {
|
|
3191
|
+
const entryArg = name ? nameOrEntry : undefined;
|
|
3192
|
+
const exportName2 = name ?? nameOrEntry;
|
|
3193
|
+
const { apiSpec } = await resolveTruth({
|
|
3194
|
+
entry: entryArg,
|
|
3195
|
+
lang,
|
|
3196
|
+
spec: options.spec,
|
|
3197
|
+
abi: options.abi
|
|
3198
|
+
});
|
|
3199
|
+
const allExports = apiSpec.exports ?? [];
|
|
3200
|
+
const exp = allExports.find((e) => e.name === exportName2 || e.id === exportName2);
|
|
3201
|
+
if (!exp) {
|
|
3202
|
+
const suggestions = fuzzyTop(exportName2, allExports);
|
|
3203
|
+
renderNotFound(exportName2, suggestions, startTime, version);
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
const sig = exp.signatures?.[0];
|
|
3207
|
+
const data = {
|
|
3208
|
+
export: {
|
|
3209
|
+
name: exp.name,
|
|
3210
|
+
kind: exp.kind,
|
|
3211
|
+
...exp.description ? { description: exp.description } : {},
|
|
3212
|
+
...exp.deprecated ? { deprecated: true } : {},
|
|
3213
|
+
...sig?.parameters ? {
|
|
3214
|
+
parameters: sig.parameters.map((p) => ({
|
|
3215
|
+
name: p.name,
|
|
3216
|
+
type: schemaTypeString(p.schema),
|
|
3217
|
+
required: p.required,
|
|
3218
|
+
...p.description ? { description: p.description } : {},
|
|
3219
|
+
schema: p.schema
|
|
3220
|
+
}))
|
|
3221
|
+
} : {},
|
|
3222
|
+
...sig?.returns ? {
|
|
3223
|
+
returns: {
|
|
3224
|
+
type: schemaTypeString(sig.returns.schema),
|
|
3225
|
+
...sig.returns.description ? { description: sig.returns.description } : {},
|
|
3226
|
+
schema: sig.returns.schema
|
|
3227
|
+
}
|
|
3228
|
+
} : {},
|
|
3229
|
+
...exp.members ? {
|
|
3230
|
+
members: exp.members.map((m) => ({
|
|
3231
|
+
name: m.name,
|
|
3232
|
+
...m.description ? { description: m.description } : {}
|
|
3233
|
+
}))
|
|
3234
|
+
} : {},
|
|
3235
|
+
...exp.schema ? { schema: exp.schema } : {},
|
|
3236
|
+
...exp.flags ? { flags: exp.flags } : {}
|
|
3237
|
+
}
|
|
3238
|
+
};
|
|
3239
|
+
formatOutput("get", data, startTime, version, renderGet);
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3044
3242
|
let entryFile;
|
|
3045
3243
|
let exportName;
|
|
3046
3244
|
if (name) {
|
|
3047
|
-
entryFile =
|
|
3245
|
+
entryFile = path21.resolve(process.cwd(), nameOrEntry);
|
|
3048
3246
|
exportName = name;
|
|
3049
3247
|
} else {
|
|
3050
3248
|
entryFile = detectEntry();
|
|
@@ -3054,39 +3252,63 @@ function registerGetCommand(program) {
|
|
|
3054
3252
|
if (!result.export) {
|
|
3055
3253
|
const listResult = await listExports({ entryFile });
|
|
3056
3254
|
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
|
-
}
|
|
3255
|
+
renderNotFound(exportName, suggestions, startTime, version);
|
|
3078
3256
|
return;
|
|
3079
3257
|
}
|
|
3080
|
-
|
|
3258
|
+
const types = Object.fromEntries((result.types ?? []).map((t) => [
|
|
3259
|
+
t.name,
|
|
3260
|
+
t.schema ?? {}
|
|
3261
|
+
]));
|
|
3262
|
+
formatOutput("get", { export: result.export, types }, startTime, version, renderGet);
|
|
3081
3263
|
} catch (err) {
|
|
3082
3264
|
formatError("get", err instanceof Error ? err.message : String(err), startTime, version);
|
|
3083
3265
|
}
|
|
3084
3266
|
});
|
|
3085
3267
|
}
|
|
3268
|
+
function schemaTypeString(schema) {
|
|
3269
|
+
if (schema === undefined || schema === null)
|
|
3270
|
+
return;
|
|
3271
|
+
if (typeof schema === "string")
|
|
3272
|
+
return schema;
|
|
3273
|
+
if (typeof schema === "object") {
|
|
3274
|
+
const s = schema;
|
|
3275
|
+
if (typeof s.$ref === "string")
|
|
3276
|
+
return s.$ref.split("/").pop();
|
|
3277
|
+
if (typeof s.type === "string")
|
|
3278
|
+
return s.type;
|
|
3279
|
+
if (Array.isArray(s.oneOf))
|
|
3280
|
+
return "oneOf";
|
|
3281
|
+
if (Array.isArray(s.anyOf))
|
|
3282
|
+
return "anyOf";
|
|
3283
|
+
}
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
function renderNotFound(exportName, suggestions, startTime, version) {
|
|
3287
|
+
if (suggestions.length > 0 && shouldRenderHuman()) {
|
|
3288
|
+
const lines = [
|
|
3289
|
+
"",
|
|
3290
|
+
indent(`${c.red("x")} Export "${exportName}" not found.`),
|
|
3291
|
+
"",
|
|
3292
|
+
indent(c.gray("Similar:"))
|
|
3293
|
+
];
|
|
3294
|
+
for (const s of suggestions) {
|
|
3295
|
+
lines.push(indent(` ${s}`));
|
|
3296
|
+
}
|
|
3297
|
+
lines.push("");
|
|
3298
|
+
lines.push(indent(`drift get ${suggestions[0]}`));
|
|
3299
|
+
lines.push("");
|
|
3300
|
+
process.stdout.write(lines.join(`
|
|
3301
|
+
`));
|
|
3302
|
+
process.exitCode = 1;
|
|
3303
|
+
} else if (suggestions.length > 0) {
|
|
3304
|
+
formatError("get", `Export '${exportName}' not found. Similar: ${suggestions.join(", ")}`, startTime, version);
|
|
3305
|
+
} else {
|
|
3306
|
+
formatError("get", `Export '${exportName}' not found`, startTime, version);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3086
3309
|
|
|
3087
3310
|
// src/commands/health.ts
|
|
3088
|
-
import
|
|
3089
|
-
import * as path21 from "node:path";
|
|
3311
|
+
import * as path22 from "node:path";
|
|
3090
3312
|
import { computeDrift as computeDrift3 } from "@driftdev/sdk";
|
|
3091
3313
|
|
|
3092
3314
|
// src/formatters/health.ts
|
|
@@ -3153,23 +3375,21 @@ function computeHealth(totalExports, documented, issues) {
|
|
|
3153
3375
|
}
|
|
3154
3376
|
|
|
3155
3377
|
// 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
3378
|
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) => {
|
|
3379
|
+
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
3380
|
const startTime = Date.now();
|
|
3171
3381
|
const version = getVersion();
|
|
3172
3382
|
try {
|
|
3383
|
+
const lang = resolveLang({
|
|
3384
|
+
entry,
|
|
3385
|
+
lang: options.lang,
|
|
3386
|
+
spec: options.spec,
|
|
3387
|
+
abi: options.abi
|
|
3388
|
+
});
|
|
3389
|
+
if (lang !== "typescript" && options.all) {
|
|
3390
|
+
formatError("health", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3173
3393
|
if (options.all) {
|
|
3174
3394
|
const allPackages = discoverPackages(process.cwd());
|
|
3175
3395
|
if (!allPackages || allPackages.length === 0) {
|
|
@@ -3185,8 +3405,8 @@ function registerHealthCommand(program) {
|
|
|
3185
3405
|
const rows = [];
|
|
3186
3406
|
let totalDoc = 0;
|
|
3187
3407
|
let totalAll = 0;
|
|
3188
|
-
for (const
|
|
3189
|
-
const { spec: spec2 } = await cachedExtract(
|
|
3408
|
+
for (const pkg of packages) {
|
|
3409
|
+
const { spec: spec2 } = await cachedExtract(pkg.entry);
|
|
3190
3410
|
const exps = spec2.exports ?? [];
|
|
3191
3411
|
let doc = 0;
|
|
3192
3412
|
for (const e of exps) {
|
|
@@ -3194,7 +3414,7 @@ function registerHealthCommand(program) {
|
|
|
3194
3414
|
doc++;
|
|
3195
3415
|
}
|
|
3196
3416
|
const score = exps.length > 0 ? Math.round(doc / exps.length * 100) : 100;
|
|
3197
|
-
rows.push({ name:
|
|
3417
|
+
rows.push({ name: pkg.name, exports: exps.length, score });
|
|
3198
3418
|
totalDoc += doc;
|
|
3199
3419
|
totalAll += exps.length;
|
|
3200
3420
|
}
|
|
@@ -3208,8 +3428,15 @@ function registerHealthCommand(program) {
|
|
|
3208
3428
|
return;
|
|
3209
3429
|
}
|
|
3210
3430
|
const { config } = loadConfig();
|
|
3211
|
-
|
|
3212
|
-
|
|
3431
|
+
let entryFile = entry ? path22.resolve(process.cwd(), entry) : undefined;
|
|
3432
|
+
if (lang === "typescript" && !entryFile) {
|
|
3433
|
+
entryFile = config.entry ? path22.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3434
|
+
}
|
|
3435
|
+
const {
|
|
3436
|
+
apiSpec: spec,
|
|
3437
|
+
packageName,
|
|
3438
|
+
packageVersion
|
|
3439
|
+
} = await resolveTruth({ entry: entryFile, lang, spec: options.spec, abi: options.abi });
|
|
3213
3440
|
const exports = spec.exports ?? [];
|
|
3214
3441
|
const total = exports.length;
|
|
3215
3442
|
let documented = 0;
|
|
@@ -3226,11 +3453,10 @@ function registerHealthCommand(program) {
|
|
|
3226
3453
|
}
|
|
3227
3454
|
}
|
|
3228
3455
|
const health = computeHealth(total, documented, issues);
|
|
3229
|
-
const pkg = getPackageInfo(process.cwd());
|
|
3230
3456
|
const data = {
|
|
3231
3457
|
...health,
|
|
3232
|
-
packageName
|
|
3233
|
-
packageVersion
|
|
3458
|
+
packageName,
|
|
3459
|
+
packageVersion
|
|
3234
3460
|
};
|
|
3235
3461
|
let min = options.min ? parseInt(options.min, 10) : config.coverage?.min;
|
|
3236
3462
|
if (min !== undefined && config.coverage?.ratchet) {
|
|
@@ -3267,7 +3493,7 @@ function registerHealthCommand(program) {
|
|
|
3267
3493
|
// src/commands/init.ts
|
|
3268
3494
|
init_global();
|
|
3269
3495
|
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3270
|
-
import * as
|
|
3496
|
+
import * as path23 from "node:path";
|
|
3271
3497
|
import { extract as extract6 } from "@openpkg-ts/sdk";
|
|
3272
3498
|
import { normalize as normalize7 } from "@openpkg-ts/spec";
|
|
3273
3499
|
|
|
@@ -3573,10 +3799,10 @@ ${detailLine}`);
|
|
|
3573
3799
|
|
|
3574
3800
|
// src/commands/init.ts
|
|
3575
3801
|
async function scanPackage(cwd, pkgDir) {
|
|
3576
|
-
const absDir =
|
|
3802
|
+
const absDir = path23.join(cwd, pkgDir);
|
|
3577
3803
|
if (!existsSync13(absDir))
|
|
3578
3804
|
return null;
|
|
3579
|
-
const pkgPath =
|
|
3805
|
+
const pkgPath = path23.join(absDir, "package.json");
|
|
3580
3806
|
let name = pkgDir;
|
|
3581
3807
|
if (existsSync13(pkgPath)) {
|
|
3582
3808
|
try {
|
|
@@ -3598,7 +3824,7 @@ async function scanPackage(cwd, pkgDir) {
|
|
|
3598
3824
|
}
|
|
3599
3825
|
const coverage = total > 0 ? Math.round(documented / total * 100) : 100;
|
|
3600
3826
|
const health = Math.round(coverage * 0.5 + 100 * 0.5);
|
|
3601
|
-
return { name, entry:
|
|
3827
|
+
return { name, entry: path23.relative(cwd, entryFile), exports: total, coverage, health };
|
|
3602
3828
|
} catch {
|
|
3603
3829
|
return null;
|
|
3604
3830
|
}
|
|
@@ -3634,7 +3860,7 @@ function registerInitCommand(program) {
|
|
|
3634
3860
|
return;
|
|
3635
3861
|
}
|
|
3636
3862
|
const config = generateConfig(packages);
|
|
3637
|
-
const configPath = opts.project ?
|
|
3863
|
+
const configPath = opts.project ? path23.resolve(cwd, "drift.config.json") : getGlobalConfigPath();
|
|
3638
3864
|
if (!opts.project) {
|
|
3639
3865
|
const globalDir = getGlobalDir();
|
|
3640
3866
|
if (!existsSync13(globalDir))
|
|
@@ -3659,7 +3885,7 @@ function registerInitCommand(program) {
|
|
|
3659
3885
|
|
|
3660
3886
|
// src/commands/lint.ts
|
|
3661
3887
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
3662
|
-
import * as
|
|
3888
|
+
import * as path24 from "node:path";
|
|
3663
3889
|
import {
|
|
3664
3890
|
buildExportRegistry,
|
|
3665
3891
|
computeDrift as computeDrift4,
|
|
@@ -3703,10 +3929,20 @@ function renderLint(data, next) {
|
|
|
3703
3929
|
|
|
3704
3930
|
// src/commands/lint.ts
|
|
3705
3931
|
function registerLintCommand(program) {
|
|
3706
|
-
program.command("lint [entry]").description("Cross-reference
|
|
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) => {
|
|
3707
3933
|
const startTime = Date.now();
|
|
3708
3934
|
const version = getVersion();
|
|
3709
3935
|
try {
|
|
3936
|
+
const lang = resolveLang({
|
|
3937
|
+
entry,
|
|
3938
|
+
lang: options.lang,
|
|
3939
|
+
spec: options.spec,
|
|
3940
|
+
abi: options.abi
|
|
3941
|
+
});
|
|
3942
|
+
if (lang !== "typescript" && options.all) {
|
|
3943
|
+
formatError("lint", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
3944
|
+
return;
|
|
3945
|
+
}
|
|
3710
3946
|
if (options.all) {
|
|
3711
3947
|
const allPackages = discoverPackages(process.cwd());
|
|
3712
3948
|
if (!allPackages || allPackages.length === 0) {
|
|
@@ -3745,8 +3981,16 @@ function registerLintCommand(program) {
|
|
|
3745
3981
|
formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
|
|
3746
3982
|
return;
|
|
3747
3983
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3984
|
+
let entryFile = entry ? path24.resolve(process.cwd(), entry) : undefined;
|
|
3985
|
+
if (lang === "typescript" && !entryFile) {
|
|
3986
|
+
entryFile = config.entry ? path24.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3987
|
+
}
|
|
3988
|
+
const { apiSpec: spec } = await resolveTruth({
|
|
3989
|
+
entry: entryFile,
|
|
3990
|
+
lang,
|
|
3991
|
+
spec: options.spec,
|
|
3992
|
+
abi: options.abi
|
|
3993
|
+
});
|
|
3750
3994
|
const driftResult = computeDrift4(spec);
|
|
3751
3995
|
const issues = [];
|
|
3752
3996
|
for (const [exportName, drifts] of driftResult.exports) {
|
|
@@ -3760,27 +4004,28 @@ function registerLintCommand(program) {
|
|
|
3760
4004
|
});
|
|
3761
4005
|
}
|
|
3762
4006
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
4007
|
+
if (lang === "typescript")
|
|
4008
|
+
try {
|
|
4009
|
+
const pkgJsonPath = path24.resolve(process.cwd(), "package.json");
|
|
4010
|
+
const pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
|
|
4011
|
+
const packageName = pkgJson.name;
|
|
4012
|
+
if (packageName) {
|
|
4013
|
+
const registry = buildExportRegistry(spec);
|
|
4014
|
+
const markdownFiles = discoverMarkdownFiles(process.cwd(), config.docs);
|
|
4015
|
+
const proseDrifts = detectProseDrift({ packageName, markdownFiles, registry });
|
|
4016
|
+
for (const drift of proseDrifts) {
|
|
4017
|
+
issues.push({
|
|
4018
|
+
export: drift.target ?? "",
|
|
4019
|
+
issue: drift.issue,
|
|
4020
|
+
...drift.suggestion ? { location: drift.suggestion } : {},
|
|
4021
|
+
filePath: drift.filePath,
|
|
4022
|
+
line: drift.line
|
|
4023
|
+
});
|
|
4024
|
+
}
|
|
3779
4025
|
}
|
|
4026
|
+
} catch (err) {
|
|
4027
|
+
formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
3780
4028
|
}
|
|
3781
|
-
} catch (err) {
|
|
3782
|
-
formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
3783
|
-
}
|
|
3784
4029
|
const data = { issues, count: issues.length };
|
|
3785
4030
|
const next = issues.length > 0 ? {
|
|
3786
4031
|
suggested: "drift-fix skill",
|
|
@@ -3804,7 +4049,7 @@ function registerLintCommand(program) {
|
|
|
3804
4049
|
}
|
|
3805
4050
|
|
|
3806
4051
|
// src/commands/list.ts
|
|
3807
|
-
import * as
|
|
4052
|
+
import * as path25 from "node:path";
|
|
3808
4053
|
import { computeDrift as computeDrift5 } from "@driftdev/sdk";
|
|
3809
4054
|
import { listExports as listExports2 } from "@openpkg-ts/sdk";
|
|
3810
4055
|
|
|
@@ -3861,10 +4106,20 @@ function renderList(data) {
|
|
|
3861
4106
|
|
|
3862
4107
|
// src/commands/list.ts
|
|
3863
4108
|
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
|
|
4109
|
+
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
4110
|
const startTime = Date.now();
|
|
3866
4111
|
const version = getVersion();
|
|
3867
4112
|
try {
|
|
4113
|
+
const lang = resolveLang({
|
|
4114
|
+
entry: searchOrEntry,
|
|
4115
|
+
lang: options.lang,
|
|
4116
|
+
spec: options.spec,
|
|
4117
|
+
abi: options.abi
|
|
4118
|
+
});
|
|
4119
|
+
if (lang !== "typescript" && options.all) {
|
|
4120
|
+
formatError("list", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
4121
|
+
return;
|
|
4122
|
+
}
|
|
3868
4123
|
if (options.all) {
|
|
3869
4124
|
const packages = discoverPackages(process.cwd());
|
|
3870
4125
|
if (!packages || packages.length === 0) {
|
|
@@ -3884,23 +4139,50 @@ function registerListCommand(program) {
|
|
|
3884
4139
|
formatOutput("list", { packages: rows, filter: filter2 }, startTime, version, renderBatchList);
|
|
3885
4140
|
return;
|
|
3886
4141
|
}
|
|
3887
|
-
let entryFile;
|
|
3888
4142
|
let searchTerm;
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4143
|
+
let exports;
|
|
4144
|
+
let driftedNames;
|
|
4145
|
+
if (lang !== "typescript") {
|
|
4146
|
+
const entryArg = lang === "clarity" ? searchOrEntry : undefined;
|
|
4147
|
+
if (lang === "openapi")
|
|
4148
|
+
searchTerm = searchOrEntry;
|
|
4149
|
+
const { apiSpec } = await resolveTruth({
|
|
4150
|
+
entry: entryArg,
|
|
4151
|
+
lang,
|
|
4152
|
+
spec: options.spec,
|
|
4153
|
+
abi: options.abi
|
|
4154
|
+
});
|
|
4155
|
+
exports = (apiSpec.exports ?? []).map((e) => ({
|
|
4156
|
+
name: e.name,
|
|
4157
|
+
kind: e.kind,
|
|
4158
|
+
description: e.description,
|
|
4159
|
+
...e.deprecated ? { deprecated: true } : {}
|
|
4160
|
+
}));
|
|
4161
|
+
if (options.drifted) {
|
|
4162
|
+
driftedNames = new Set(computeDrift5(apiSpec).exports.keys());
|
|
4163
|
+
}
|
|
3894
4164
|
} else {
|
|
3895
|
-
entryFile
|
|
4165
|
+
let entryFile;
|
|
4166
|
+
if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
|
|
4167
|
+
entryFile = path25.resolve(process.cwd(), searchOrEntry);
|
|
4168
|
+
} else if (searchOrEntry) {
|
|
4169
|
+
entryFile = detectEntry();
|
|
4170
|
+
searchTerm = searchOrEntry;
|
|
4171
|
+
} else {
|
|
4172
|
+
entryFile = detectEntry();
|
|
4173
|
+
}
|
|
4174
|
+
const result = await listExports2({ entryFile });
|
|
4175
|
+
exports = result.exports.map((e) => ({
|
|
4176
|
+
name: e.name,
|
|
4177
|
+
kind: e.kind,
|
|
4178
|
+
description: e.description,
|
|
4179
|
+
...e.deprecated ? { deprecated: true } : {}
|
|
4180
|
+
}));
|
|
4181
|
+
if (options.drifted) {
|
|
4182
|
+
const { spec } = await cachedExtract(entryFile);
|
|
4183
|
+
driftedNames = new Set(computeDrift5(spec).exports.keys());
|
|
4184
|
+
}
|
|
3896
4185
|
}
|
|
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
4186
|
if (options.kind) {
|
|
3905
4187
|
const kinds = new Set(options.kind.split(",").map((k) => k.trim().toLowerCase()));
|
|
3906
4188
|
exports = exports.filter((e) => kinds.has(e.kind));
|
|
@@ -3908,11 +4190,9 @@ function registerListCommand(program) {
|
|
|
3908
4190
|
if (options.undocumented) {
|
|
3909
4191
|
exports = exports.filter((e) => !e.description || e.description.trim().length === 0);
|
|
3910
4192
|
}
|
|
3911
|
-
if (
|
|
3912
|
-
const
|
|
3913
|
-
|
|
3914
|
-
const driftedNames = new Set(driftResult.exports.keys());
|
|
3915
|
-
exports = exports.filter((e) => driftedNames.has(e.name));
|
|
4193
|
+
if (driftedNames) {
|
|
4194
|
+
const names = driftedNames;
|
|
4195
|
+
exports = exports.filter((e) => names.has(e.name));
|
|
3916
4196
|
}
|
|
3917
4197
|
if (searchTerm) {
|
|
3918
4198
|
const matches = fuzzySearch(searchTerm, exports);
|
|
@@ -3937,10 +4217,166 @@ function registerListCommand(program) {
|
|
|
3937
4217
|
});
|
|
3938
4218
|
}
|
|
3939
4219
|
|
|
4220
|
+
// src/commands/mcp.ts
|
|
4221
|
+
import { spawn } from "node:child_process";
|
|
4222
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4223
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4224
|
+
import { z } from "zod";
|
|
4225
|
+
var truthShape = {
|
|
4226
|
+
cwd: z.string().optional().describe("Directory to run in (project root; where package.json/config live)"),
|
|
4227
|
+
entry: z.string().optional().describe("Entry file: TypeScript entry or Clarity .clar source. Omit for OpenAPI specs."),
|
|
4228
|
+
lang: z.enum(["typescript", "clarity", "openapi"]).optional().describe("Source language override; inferred from spec/abi/.clar extension otherwise"),
|
|
4229
|
+
spec: z.string().optional().describe("OpenAPI 3.x JSON document — local path or https URL. Implies lang=openapi."),
|
|
4230
|
+
abi: z.string().optional().describe("Clarity ABI JSON path (required for Clarity sources)")
|
|
4231
|
+
};
|
|
4232
|
+
function truthFlags(args) {
|
|
4233
|
+
const out = [];
|
|
4234
|
+
if (args.lang)
|
|
4235
|
+
out.push("--lang", args.lang);
|
|
4236
|
+
if (args.spec)
|
|
4237
|
+
out.push("--spec", args.spec);
|
|
4238
|
+
if (args.abi)
|
|
4239
|
+
out.push("--abi", args.abi);
|
|
4240
|
+
return out;
|
|
4241
|
+
}
|
|
4242
|
+
function runDrift(cliArgs, cwd) {
|
|
4243
|
+
return new Promise((resolve18) => {
|
|
4244
|
+
const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
|
|
4245
|
+
cwd: cwd ?? process.cwd(),
|
|
4246
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
4247
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4248
|
+
});
|
|
4249
|
+
let stdout = "";
|
|
4250
|
+
let stderr = "";
|
|
4251
|
+
child.stdout.on("data", (d) => {
|
|
4252
|
+
stdout += d;
|
|
4253
|
+
});
|
|
4254
|
+
child.stderr.on("data", (d) => {
|
|
4255
|
+
stderr += d;
|
|
4256
|
+
});
|
|
4257
|
+
child.on("close", (code) => {
|
|
4258
|
+
const text = stdout.trim() || stderr.trim() || `drift exited with code ${code}`;
|
|
4259
|
+
let ok = code === 0;
|
|
4260
|
+
try {
|
|
4261
|
+
ok = JSON.parse(stdout).ok === true;
|
|
4262
|
+
} catch {}
|
|
4263
|
+
resolve18({ text, ok });
|
|
4264
|
+
});
|
|
4265
|
+
child.on("error", (err) => resolve18({ text: `Failed to run drift: ${err.message}`, ok: false }));
|
|
4266
|
+
});
|
|
4267
|
+
}
|
|
4268
|
+
function toResult({ text, ok }) {
|
|
4269
|
+
return { content: [{ type: "text", text }], isError: !ok };
|
|
4270
|
+
}
|
|
4271
|
+
function registerMcpCommand(program) {
|
|
4272
|
+
program.command("mcp").description("Run an MCP stdio server exposing drift tools to agents").action(async () => {
|
|
4273
|
+
const server = new McpServer({ name: "drift", version: getVersion() });
|
|
4274
|
+
server.registerTool("drift_extract", {
|
|
4275
|
+
title: "Extract API spec",
|
|
4276
|
+
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.",
|
|
4277
|
+
inputSchema: truthShape
|
|
4278
|
+
}, async (args) => toResult(await runDrift(["extract", ...args.entry ? [args.entry] : [], ...truthFlags(args)], args.cwd)));
|
|
4279
|
+
server.registerTool("drift_list", {
|
|
4280
|
+
title: "List exports/operations",
|
|
4281
|
+
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.",
|
|
4282
|
+
inputSchema: {
|
|
4283
|
+
...truthShape,
|
|
4284
|
+
search: z.string().optional().describe("Fuzzy search term"),
|
|
4285
|
+
kind: z.string().optional().describe("Filter by kind (comma-separated)"),
|
|
4286
|
+
undocumented: z.boolean().optional().describe("Only items missing docs"),
|
|
4287
|
+
drifted: z.boolean().optional().describe("Only items whose docs drifted")
|
|
4288
|
+
}
|
|
4289
|
+
}, async (args) => {
|
|
4290
|
+
const cli = ["list"];
|
|
4291
|
+
if (args.entry)
|
|
4292
|
+
cli.push(args.entry);
|
|
4293
|
+
else if (args.search)
|
|
4294
|
+
cli.push(args.search);
|
|
4295
|
+
if (args.kind)
|
|
4296
|
+
cli.push("--kind", args.kind);
|
|
4297
|
+
if (args.undocumented)
|
|
4298
|
+
cli.push("--undocumented");
|
|
4299
|
+
if (args.drifted)
|
|
4300
|
+
cli.push("--drifted");
|
|
4301
|
+
cli.push("--full");
|
|
4302
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4303
|
+
});
|
|
4304
|
+
server.registerTool("drift_get", {
|
|
4305
|
+
title: "Get one export/operation",
|
|
4306
|
+
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.",
|
|
4307
|
+
inputSchema: {
|
|
4308
|
+
...truthShape,
|
|
4309
|
+
name: z.string().describe("Export/operation name (e.g. candidateInfo, transfer, createClient)")
|
|
4310
|
+
}
|
|
4311
|
+
}, async (args) => {
|
|
4312
|
+
const cli = ["get"];
|
|
4313
|
+
if (args.entry)
|
|
4314
|
+
cli.push(args.entry, args.name);
|
|
4315
|
+
else
|
|
4316
|
+
cli.push(args.name);
|
|
4317
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4318
|
+
});
|
|
4319
|
+
server.registerTool("drift_scan", {
|
|
4320
|
+
title: "Scan docs health",
|
|
4321
|
+
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.",
|
|
4322
|
+
inputSchema: {
|
|
4323
|
+
...truthShape,
|
|
4324
|
+
min: z.number().optional().describe("Minimum health threshold (result.pass=false below it)")
|
|
4325
|
+
}
|
|
4326
|
+
}, async (args) => {
|
|
4327
|
+
const cli = ["scan"];
|
|
4328
|
+
if (args.entry)
|
|
4329
|
+
cli.push(args.entry);
|
|
4330
|
+
if (args.min !== undefined)
|
|
4331
|
+
cli.push("--min", String(args.min));
|
|
4332
|
+
return toResult(await runDrift([...cli, ...truthFlags(args)], args.cwd));
|
|
4333
|
+
});
|
|
4334
|
+
server.registerTool("drift_diff", {
|
|
4335
|
+
title: "Diff two API specs",
|
|
4336
|
+
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.",
|
|
4337
|
+
inputSchema: {
|
|
4338
|
+
cwd: truthShape.cwd,
|
|
4339
|
+
old: z.string().optional().describe("Old spec file path"),
|
|
4340
|
+
new: z.string().optional().describe("New spec file path"),
|
|
4341
|
+
base: z.string().optional().describe("Git ref for old spec (alternative to files)"),
|
|
4342
|
+
head: z.string().optional().describe("Git ref for new spec (default: working tree)"),
|
|
4343
|
+
entry: z.string().optional().describe("Entry file for git ref extraction")
|
|
4344
|
+
}
|
|
4345
|
+
}, async (args) => toResult(await runDrift(diffArgs("diff", args), args.cwd)));
|
|
4346
|
+
server.registerTool("drift_breaking", {
|
|
4347
|
+
title: "Detect breaking changes",
|
|
4348
|
+
description: "Detect breaking API changes between two specs or git refs (TypeScript only today). Use to check whether docs claiming compatibility are still true.",
|
|
4349
|
+
inputSchema: {
|
|
4350
|
+
cwd: truthShape.cwd,
|
|
4351
|
+
old: z.string().optional().describe("Old spec file path"),
|
|
4352
|
+
new: z.string().optional().describe("New spec file path"),
|
|
4353
|
+
base: z.string().optional().describe("Git ref for old spec (alternative to files)"),
|
|
4354
|
+
head: z.string().optional().describe("Git ref for new spec (default: working tree)"),
|
|
4355
|
+
entry: z.string().optional().describe("Entry file for git ref extraction")
|
|
4356
|
+
}
|
|
4357
|
+
}, async (args) => toResult(await runDrift(diffArgs("breaking", args), args.cwd)));
|
|
4358
|
+
await server.connect(new StdioServerTransport);
|
|
4359
|
+
});
|
|
4360
|
+
}
|
|
4361
|
+
function diffArgs(command, args) {
|
|
4362
|
+
const cli = [command];
|
|
4363
|
+
if (args.old)
|
|
4364
|
+
cli.push(args.old);
|
|
4365
|
+
if (args.new)
|
|
4366
|
+
cli.push(args.new);
|
|
4367
|
+
if (args.base)
|
|
4368
|
+
cli.push("--base", args.base);
|
|
4369
|
+
if (args.head)
|
|
4370
|
+
cli.push("--head", args.head);
|
|
4371
|
+
if (args.entry)
|
|
4372
|
+
cli.push("--entry", args.entry);
|
|
4373
|
+
return cli;
|
|
4374
|
+
}
|
|
4375
|
+
|
|
3940
4376
|
// src/commands/release.ts
|
|
3941
4377
|
import { execSync as execSync4 } from "node:child_process";
|
|
3942
4378
|
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
|
|
3943
|
-
import * as
|
|
4379
|
+
import * as path26 from "node:path";
|
|
3944
4380
|
import { computeDrift as computeDrift6 } from "@driftdev/sdk";
|
|
3945
4381
|
|
|
3946
4382
|
// src/formatters/release.ts
|
|
@@ -3988,7 +4424,7 @@ function getLastTag() {
|
|
|
3988
4424
|
}
|
|
3989
4425
|
}
|
|
3990
4426
|
function getPackageVersion(cwd) {
|
|
3991
|
-
const pkgPath =
|
|
4427
|
+
const pkgPath = path26.join(cwd, "package.json");
|
|
3992
4428
|
if (!existsSync14(pkgPath))
|
|
3993
4429
|
return null;
|
|
3994
4430
|
try {
|
|
@@ -4004,7 +4440,7 @@ function registerReleaseCommand(program) {
|
|
|
4004
4440
|
const cwd = process.cwd();
|
|
4005
4441
|
try {
|
|
4006
4442
|
const { config } = loadConfig();
|
|
4007
|
-
const entryFile = entry ?
|
|
4443
|
+
const entryFile = entry ? path26.resolve(cwd, entry) : config.entry ? path26.resolve(cwd, config.entry) : detectEntry();
|
|
4008
4444
|
const { spec } = await cachedExtract(entryFile);
|
|
4009
4445
|
const exports = spec.exports ?? [];
|
|
4010
4446
|
const total = exports.length;
|
|
@@ -4129,7 +4565,7 @@ function renderReport(data) {
|
|
|
4129
4565
|
// src/utils/scan-packages.ts
|
|
4130
4566
|
import { execSync as execSync5 } from "node:child_process";
|
|
4131
4567
|
import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
|
|
4132
|
-
import * as
|
|
4568
|
+
import * as path27 from "node:path";
|
|
4133
4569
|
import { computeDrift as computeDrift7 } from "@driftdev/sdk";
|
|
4134
4570
|
function detectPackageDirs2(cwd) {
|
|
4135
4571
|
const workspaces = detectWorkspaces(cwd);
|
|
@@ -4148,11 +4584,11 @@ async function scanAllPackages(cwd) {
|
|
|
4148
4584
|
const packageDirs = detectPackageDirs2(cwd);
|
|
4149
4585
|
const results = [];
|
|
4150
4586
|
for (const dir of packageDirs) {
|
|
4151
|
-
const absDir = dir === "." ? cwd :
|
|
4587
|
+
const absDir = dir === "." ? cwd : path27.join(cwd, dir);
|
|
4152
4588
|
if (!existsSync15(absDir))
|
|
4153
4589
|
continue;
|
|
4154
4590
|
let name = dir;
|
|
4155
|
-
const pkgPath =
|
|
4591
|
+
const pkgPath = path27.join(absDir, "package.json");
|
|
4156
4592
|
if (existsSync15(pkgPath)) {
|
|
4157
4593
|
try {
|
|
4158
4594
|
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
@@ -4251,10 +4687,8 @@ function registerReportCommand(program) {
|
|
|
4251
4687
|
}
|
|
4252
4688
|
|
|
4253
4689
|
// src/commands/scan.ts
|
|
4254
|
-
import {
|
|
4255
|
-
import * as
|
|
4256
|
-
import { fromSource } from "@driftdev/clarity-adapter";
|
|
4257
|
-
import { fromDocument } from "@driftdev/openapi-adapter";
|
|
4690
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
4691
|
+
import * as path28 from "node:path";
|
|
4258
4692
|
import {
|
|
4259
4693
|
buildExportRegistry as buildExportRegistry2,
|
|
4260
4694
|
computeDrift as computeDrift8,
|
|
@@ -4321,59 +4755,17 @@ function renderBatchScan(data) {
|
|
|
4321
4755
|
}
|
|
4322
4756
|
|
|
4323
4757
|
// 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
4758
|
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
|
|
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) => {
|
|
4369
4760
|
const startTime = Date.now();
|
|
4370
4761
|
const version = getVersion();
|
|
4371
4762
|
try {
|
|
4372
|
-
const lang =
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4763
|
+
const lang = resolveLang({
|
|
4764
|
+
entry,
|
|
4765
|
+
lang: options.lang,
|
|
4766
|
+
spec: options.spec,
|
|
4767
|
+
abi: options.abi
|
|
4768
|
+
});
|
|
4377
4769
|
if (lang !== "typescript" && options.all) {
|
|
4378
4770
|
formatError("scan", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
4379
4771
|
return;
|
|
@@ -4439,12 +4831,16 @@ function registerScanCommand(program) {
|
|
|
4439
4831
|
return;
|
|
4440
4832
|
}
|
|
4441
4833
|
const { config } = loadConfig();
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
}
|
|
4446
|
-
const
|
|
4447
|
-
|
|
4834
|
+
let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
|
|
4835
|
+
if (lang === "typescript" && !entryFile) {
|
|
4836
|
+
entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4837
|
+
}
|
|
4838
|
+
const { apiSpec, packageName, packageVersion } = await resolveTruth({
|
|
4839
|
+
entry: entryFile,
|
|
4840
|
+
lang,
|
|
4841
|
+
spec: options.spec,
|
|
4842
|
+
abi: options.abi
|
|
4843
|
+
});
|
|
4448
4844
|
const exports = apiSpec.exports ?? [];
|
|
4449
4845
|
const total = exports.length;
|
|
4450
4846
|
let documented = 0;
|
|
@@ -4468,7 +4864,7 @@ function registerScanCommand(program) {
|
|
|
4468
4864
|
}
|
|
4469
4865
|
if (lang === "typescript") {
|
|
4470
4866
|
try {
|
|
4471
|
-
const pkgJsonPath =
|
|
4867
|
+
const pkgJsonPath = path28.resolve(process.cwd(), "package.json");
|
|
4472
4868
|
const pkgJson = JSON.parse(readFileSync20(pkgJsonPath, "utf-8"));
|
|
4473
4869
|
const pkgName = pkgJson.name;
|
|
4474
4870
|
if (pkgName) {
|
|
@@ -4576,7 +4972,7 @@ function registerSemverCommand(program) {
|
|
|
4576
4972
|
|
|
4577
4973
|
// src/commands/validate.ts
|
|
4578
4974
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
4579
|
-
import * as
|
|
4975
|
+
import * as path29 from "node:path";
|
|
4580
4976
|
import { validateSpec } from "@openpkg-ts/spec";
|
|
4581
4977
|
|
|
4582
4978
|
// src/formatters/validate.ts
|
|
@@ -4602,7 +4998,7 @@ function registerValidateCommand(program) {
|
|
|
4602
4998
|
const startTime = Date.now();
|
|
4603
4999
|
const version = getVersion();
|
|
4604
5000
|
try {
|
|
4605
|
-
const filePath =
|
|
5001
|
+
const filePath = path29.resolve(process.cwd(), file);
|
|
4606
5002
|
const content = readFileSync21(filePath, "utf-8");
|
|
4607
5003
|
const spec = JSON.parse(content);
|
|
4608
5004
|
const result = validateSpec(spec);
|
|
@@ -4728,13 +5124,13 @@ function extractCapabilities(program) {
|
|
|
4728
5124
|
|
|
4729
5125
|
// src/drift.ts
|
|
4730
5126
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
4731
|
-
var __dirname3 =
|
|
4732
|
-
var packageJson = JSON.parse(readFileSync22(
|
|
5127
|
+
var __dirname3 = path30.dirname(__filename2);
|
|
5128
|
+
var packageJson = JSON.parse(readFileSync22(path30.join(__dirname3, "../package.json"), "utf-8"));
|
|
4733
5129
|
var program = new Command;
|
|
4734
|
-
program.name("drift").description("drift —
|
|
5130
|
+
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
5131
|
const opts = program.opts();
|
|
4736
5132
|
if (opts.cwd) {
|
|
4737
|
-
process.chdir(
|
|
5133
|
+
process.chdir(path30.resolve(opts.cwd));
|
|
4738
5134
|
}
|
|
4739
5135
|
setOutputMode({ json: opts.json, human: opts.human });
|
|
4740
5136
|
setConfigPath(opts.config);
|
|
@@ -4763,6 +5159,7 @@ registerConfigCommand(program);
|
|
|
4763
5159
|
registerContextCommand(program);
|
|
4764
5160
|
registerCacheCommand(program);
|
|
4765
5161
|
registerCommandsCommand(program);
|
|
5162
|
+
registerMcpCommand(program);
|
|
4766
5163
|
var HUMAN_COMMANDS = new Set(["scan", "ci", "init", "commands"]);
|
|
4767
5164
|
for (const cmd of program.commands) {
|
|
4768
5165
|
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.0",
|
|
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": {
|
|
@@ -42,10 +47,12 @@
|
|
|
42
47
|
"@driftdev/clarity-adapter": "^1.0.1",
|
|
43
48
|
"@driftdev/openapi-adapter": "^1.0.0",
|
|
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",
|