@driftdev/cli 1.11.0 → 1.12.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 +528 -143
- package/package.json +2 -2
- package/schemas/drift.docs-map.schema.json +75 -0
package/dist/drift.js
CHANGED
|
@@ -51,8 +51,8 @@ function getGlobalConfigPath() {
|
|
|
51
51
|
var init_global = () => {};
|
|
52
52
|
|
|
53
53
|
// src/drift.ts
|
|
54
|
-
import { readFileSync as
|
|
55
|
-
import * as
|
|
54
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
55
|
+
import * as path34 from "node:path";
|
|
56
56
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
57
57
|
import { Command } from "commander";
|
|
58
58
|
|
|
@@ -63,13 +63,37 @@ import {
|
|
|
63
63
|
existsSync as existsSync2,
|
|
64
64
|
mkdirSync as mkdirSync2,
|
|
65
65
|
readdirSync,
|
|
66
|
-
readFileSync,
|
|
66
|
+
readFileSync as readFileSync2,
|
|
67
67
|
rmSync,
|
|
68
68
|
statSync,
|
|
69
69
|
writeFileSync
|
|
70
70
|
} from "node:fs";
|
|
71
71
|
import * as os2 from "node:os";
|
|
72
72
|
import * as path2 from "node:path";
|
|
73
|
+
|
|
74
|
+
// src/utils/version.ts
|
|
75
|
+
import { readFileSync } from "node:fs";
|
|
76
|
+
import { dirname, join as join2 } from "node:path";
|
|
77
|
+
import { fileURLToPath } from "node:url";
|
|
78
|
+
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
79
|
+
var cached;
|
|
80
|
+
function getVersion() {
|
|
81
|
+
if (cached)
|
|
82
|
+
return cached;
|
|
83
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
84
|
+
try {
|
|
85
|
+
const pkg = JSON.parse(readFileSync(join2(__dirname2, rel), "utf-8"));
|
|
86
|
+
if (pkg.name === "@driftdev/cli" && typeof pkg.version === "string") {
|
|
87
|
+
cached = pkg.version;
|
|
88
|
+
return pkg.version;
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
cached = "0.0.0";
|
|
93
|
+
return cached;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/cache/spec-cache.ts
|
|
73
97
|
var _noCache = false;
|
|
74
98
|
function setNoCache(value) {
|
|
75
99
|
_noCache = value;
|
|
@@ -141,7 +165,7 @@ function buildCacheKey(input) {
|
|
|
141
165
|
const pkgJson = findPackageJson(absEntry);
|
|
142
166
|
const pkgMtime = pkgJson ? getMtime(pkgJson) : 0;
|
|
143
167
|
const srcMtime = getSourceMaxMtime(absEntry);
|
|
144
|
-
const parts = [absEntry, String(entryMtime), String(pkgMtime), String(srcMtime)];
|
|
168
|
+
const parts = [getVersion(), absEntry, String(entryMtime), String(pkgMtime), String(srcMtime)];
|
|
145
169
|
if (input.configHash)
|
|
146
170
|
parts.push(input.configHash);
|
|
147
171
|
return hashString(parts.join("|"));
|
|
@@ -154,7 +178,7 @@ function getCachedSpec(input) {
|
|
|
154
178
|
if (!existsSync2(cacheFile))
|
|
155
179
|
return null;
|
|
156
180
|
try {
|
|
157
|
-
const raw = JSON.parse(
|
|
181
|
+
const raw = JSON.parse(readFileSync2(cacheFile, "utf-8"));
|
|
158
182
|
return raw;
|
|
159
183
|
} catch {
|
|
160
184
|
return null;
|
|
@@ -177,7 +201,7 @@ function getConfigHash(configPath) {
|
|
|
177
201
|
if (!configPath || !existsSync2(configPath))
|
|
178
202
|
return;
|
|
179
203
|
try {
|
|
180
|
-
const content =
|
|
204
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
181
205
|
return hashString(content);
|
|
182
206
|
} catch {
|
|
183
207
|
return;
|
|
@@ -633,13 +657,13 @@ function formatWarning(message) {
|
|
|
633
657
|
}
|
|
634
658
|
|
|
635
659
|
// src/utils/resolve-specs.ts
|
|
636
|
-
import { readFileSync as
|
|
660
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
637
661
|
import * as path7 from "node:path";
|
|
638
662
|
import { extract as extract2 } from "@openpkg-ts/sdk";
|
|
639
663
|
import { normalize as normalize2 } from "@openpkg-ts/spec";
|
|
640
664
|
|
|
641
665
|
// src/config/loader.ts
|
|
642
|
-
import { existsSync as existsSync4, readFileSync as
|
|
666
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
643
667
|
import * as path4 from "node:path";
|
|
644
668
|
|
|
645
669
|
// src/config/drift-config.ts
|
|
@@ -731,7 +755,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
731
755
|
if (!existsSync4(absPath)) {
|
|
732
756
|
throw new Error(`Config file not found: ${absPath}`);
|
|
733
757
|
}
|
|
734
|
-
const raw = JSON.parse(
|
|
758
|
+
const raw = JSON.parse(readFileSync3(absPath, "utf-8"));
|
|
735
759
|
const result = validateConfig(raw);
|
|
736
760
|
if (!result.ok) {
|
|
737
761
|
throw new Error(`Invalid config at ${absPath}: ${result.errors.join(", ")}`);
|
|
@@ -744,7 +768,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
744
768
|
const driftConfigPath = path4.join(current, "drift.config.json");
|
|
745
769
|
if (existsSync4(driftConfigPath)) {
|
|
746
770
|
try {
|
|
747
|
-
const raw = JSON.parse(
|
|
771
|
+
const raw = JSON.parse(readFileSync3(driftConfigPath, "utf-8"));
|
|
748
772
|
const result = validateConfig(raw);
|
|
749
773
|
if (!result.ok) {
|
|
750
774
|
throw new Error(`Invalid config at ${driftConfigPath}: ${result.errors.join(", ")}`);
|
|
@@ -760,7 +784,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
760
784
|
const pkgPath = path4.join(current, "package.json");
|
|
761
785
|
if (existsSync4(pkgPath)) {
|
|
762
786
|
try {
|
|
763
|
-
const pkg = JSON.parse(
|
|
787
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
764
788
|
if (pkg.drift && typeof pkg.drift === "object") {
|
|
765
789
|
const result = validateConfig(pkg.drift);
|
|
766
790
|
if (!result.ok) {
|
|
@@ -781,7 +805,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
781
805
|
const globalPath = getGlobalConfigPath();
|
|
782
806
|
if (existsSync4(globalPath)) {
|
|
783
807
|
try {
|
|
784
|
-
const raw = JSON.parse(
|
|
808
|
+
const raw = JSON.parse(readFileSync3(globalPath, "utf-8"));
|
|
785
809
|
const result = validateConfig(raw);
|
|
786
810
|
if (result.ok) {
|
|
787
811
|
return { config: result.config, configPath: globalPath };
|
|
@@ -792,18 +816,18 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
792
816
|
}
|
|
793
817
|
|
|
794
818
|
// src/utils/detect-entry.ts
|
|
795
|
-
import { existsSync as existsSync6, readFileSync as
|
|
819
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
796
820
|
import * as path6 from "node:path";
|
|
797
821
|
|
|
798
822
|
// src/utils/workspaces.ts
|
|
799
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as
|
|
823
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
800
824
|
import * as path5 from "node:path";
|
|
801
825
|
function detectWorkspaces(cwd) {
|
|
802
826
|
const pkgPath = path5.join(cwd, "package.json");
|
|
803
827
|
if (!existsSync5(pkgPath))
|
|
804
828
|
return null;
|
|
805
829
|
try {
|
|
806
|
-
const pkg = JSON.parse(
|
|
830
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
807
831
|
if (Array.isArray(pkg.workspaces))
|
|
808
832
|
return pkg.workspaces;
|
|
809
833
|
if (pkg.workspaces?.packages && Array.isArray(pkg.workspaces.packages))
|
|
@@ -847,7 +871,7 @@ function discoverPackages(cwd) {
|
|
|
847
871
|
const pkgPath = path5.join(absDir, "package.json");
|
|
848
872
|
if (existsSync5(pkgPath)) {
|
|
849
873
|
try {
|
|
850
|
-
const pkg = JSON.parse(
|
|
874
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
851
875
|
if (pkg.name)
|
|
852
876
|
name = pkg.name;
|
|
853
877
|
if (pkg.private === true)
|
|
@@ -878,7 +902,7 @@ function detectEntry(cwd = process.cwd()) {
|
|
|
878
902
|
}
|
|
879
903
|
if (existsSync6(pkgPath)) {
|
|
880
904
|
try {
|
|
881
|
-
const pkg = JSON.parse(
|
|
905
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
882
906
|
const typesField = pkg.types || pkg.typings;
|
|
883
907
|
if (typesField && typeof typesField === "string") {
|
|
884
908
|
const resolved = tryResolve("types", typesField);
|
|
@@ -1039,7 +1063,7 @@ function collectStringValues(value, depth = 0) {
|
|
|
1039
1063
|
|
|
1040
1064
|
// src/utils/resolve-specs.ts
|
|
1041
1065
|
function loadSpec(filePath) {
|
|
1042
|
-
return JSON.parse(
|
|
1066
|
+
return JSON.parse(readFileSync6(path7.resolve(process.cwd(), filePath), "utf-8"));
|
|
1043
1067
|
}
|
|
1044
1068
|
async function resolveSpecs(opts) {
|
|
1045
1069
|
const { config } = loadConfig();
|
|
@@ -1086,23 +1110,6 @@ async function resolveSpecs(opts) {
|
|
|
1086
1110
|
throw new Error("Provide two spec files, or use --base <ref>");
|
|
1087
1111
|
}
|
|
1088
1112
|
|
|
1089
|
-
// src/utils/version.ts
|
|
1090
|
-
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1091
|
-
import { dirname as dirname4, join as join7 } from "node:path";
|
|
1092
|
-
import { fileURLToPath } from "node:url";
|
|
1093
|
-
var __dirname2 = dirname4(fileURLToPath(import.meta.url));
|
|
1094
|
-
var cached;
|
|
1095
|
-
function getVersion() {
|
|
1096
|
-
if (cached)
|
|
1097
|
-
return cached;
|
|
1098
|
-
try {
|
|
1099
|
-
cached = JSON.parse(readFileSync6(join7(__dirname2, "../../package.json"), "utf-8")).version ?? "0.0.0";
|
|
1100
|
-
} catch {
|
|
1101
|
-
cached = "0.0.0";
|
|
1102
|
-
}
|
|
1103
|
-
return cached ?? "0.0.0";
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
1113
|
// src/commands/breaking.ts
|
|
1107
1114
|
function registerBreakingCommand(program) {
|
|
1108
1115
|
program.command("breaking [old] [new]").description("Detect breaking changes between two specs").option("--base <ref>", "Git ref for old spec").option("--head <ref>", "Git ref for new spec (default: working tree)").option("--entry <file>", "Entry file for git ref extraction").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").action(async (oldPath, newPath, options) => {
|
|
@@ -2651,9 +2658,358 @@ function registerDiffCommand(program) {
|
|
|
2651
2658
|
});
|
|
2652
2659
|
}
|
|
2653
2660
|
|
|
2654
|
-
// src/commands/
|
|
2655
|
-
import {
|
|
2661
|
+
// src/commands/docs-map.ts
|
|
2662
|
+
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
2663
|
+
import * as path21 from "node:path";
|
|
2664
|
+
import { collectTypeKeys, extractDocumentedKeys as extractDocumentedKeys2 } from "@driftdev/sdk";
|
|
2665
|
+
|
|
2666
|
+
// src/config/docs-map.ts
|
|
2667
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
|
|
2656
2668
|
import * as path18 from "node:path";
|
|
2669
|
+
var ANNOTATIONS = new Set(["prose-documented", "internal-by-convention", "ignore"]);
|
|
2670
|
+
function isStringArray(v) {
|
|
2671
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
2672
|
+
}
|
|
2673
|
+
function validateDocsMap(raw) {
|
|
2674
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2675
|
+
return { ok: false, errors: ["Docs map must be a JSON object"] };
|
|
2676
|
+
}
|
|
2677
|
+
const errors = [];
|
|
2678
|
+
const obj = raw;
|
|
2679
|
+
if (obj.version !== 1)
|
|
2680
|
+
errors.push('"version" must be 1');
|
|
2681
|
+
if (!Array.isArray(obj.pages)) {
|
|
2682
|
+
errors.push('"pages" must be an array');
|
|
2683
|
+
return { ok: false, errors };
|
|
2684
|
+
}
|
|
2685
|
+
obj.pages.forEach((p, i) => {
|
|
2686
|
+
const at = `"pages[${i}]"`;
|
|
2687
|
+
if (typeof p !== "object" || p === null) {
|
|
2688
|
+
errors.push(`${at} must be an object`);
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
const page = p;
|
|
2692
|
+
if (typeof page.page !== "string" || !page.page)
|
|
2693
|
+
errors.push(`${at}.page must be a string`);
|
|
2694
|
+
if (typeof page.type !== "string" || !page.type)
|
|
2695
|
+
errors.push(`${at}.type must be a string`);
|
|
2696
|
+
if (page.spec !== undefined && typeof page.spec !== "string")
|
|
2697
|
+
errors.push(`${at}.spec must be a string`);
|
|
2698
|
+
if (page.entry !== undefined && typeof page.entry !== "string")
|
|
2699
|
+
errors.push(`${at}.entry must be a string`);
|
|
2700
|
+
if (page.spec !== undefined && page.entry !== undefined)
|
|
2701
|
+
errors.push(`${at} must set at most one of "spec"/"entry" (omit both to use the scan target)`);
|
|
2702
|
+
if (page.sectionRe !== undefined) {
|
|
2703
|
+
if (typeof page.sectionRe !== "string") {
|
|
2704
|
+
errors.push(`${at}.sectionRe must be a string`);
|
|
2705
|
+
} else {
|
|
2706
|
+
try {
|
|
2707
|
+
new RegExp(page.sectionRe);
|
|
2708
|
+
} catch {
|
|
2709
|
+
errors.push(`${at}.sectionRe is not a valid regex: ${page.sectionRe}`);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
for (const field of ["extraPages", "internal", "deprecated"]) {
|
|
2714
|
+
if (page[field] !== undefined && !isStringArray(page[field]))
|
|
2715
|
+
errors.push(`${at}.${field} must be an array of strings`);
|
|
2716
|
+
}
|
|
2717
|
+
if (page.replacements !== undefined) {
|
|
2718
|
+
const r = page.replacements;
|
|
2719
|
+
if (typeof r !== "object" || r === null || Array.isArray(r)) {
|
|
2720
|
+
errors.push(`${at}.replacements must be an object of oldKey → newKey strings`);
|
|
2721
|
+
} else if (!Object.values(r).every((v) => typeof v === "string")) {
|
|
2722
|
+
errors.push(`${at}.replacements values must be strings`);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
if (page.annotations !== undefined) {
|
|
2726
|
+
const a = page.annotations;
|
|
2727
|
+
if (typeof a !== "object" || a === null || Array.isArray(a)) {
|
|
2728
|
+
errors.push(`${at}.annotations must be an object of key → annotation`);
|
|
2729
|
+
} else {
|
|
2730
|
+
for (const [k, v] of Object.entries(a)) {
|
|
2731
|
+
if (typeof v !== "string" || !ANNOTATIONS.has(v))
|
|
2732
|
+
errors.push(`${at}.annotations["${k}"] must be one of: ${[...ANNOTATIONS].join(", ")} (got ${JSON.stringify(v)})`);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (page.baselineGaps !== undefined && (typeof page.baselineGaps !== "number" || page.baselineGaps < 0 || !Number.isInteger(page.baselineGaps)))
|
|
2737
|
+
errors.push(`${at}.baselineGaps must be a non-negative integer`);
|
|
2738
|
+
});
|
|
2739
|
+
if (errors.length > 0)
|
|
2740
|
+
return { ok: false, errors };
|
|
2741
|
+
return { ok: true, map: obj };
|
|
2742
|
+
}
|
|
2743
|
+
function loadDocsMap(mapPath, cwd = process.cwd()) {
|
|
2744
|
+
const absPath = path18.resolve(cwd, mapPath);
|
|
2745
|
+
if (!existsSync13(absPath))
|
|
2746
|
+
throw new Error(`Docs map not found: ${absPath}`);
|
|
2747
|
+
let raw;
|
|
2748
|
+
try {
|
|
2749
|
+
raw = JSON.parse(readFileSync14(absPath, "utf-8"));
|
|
2750
|
+
} catch {
|
|
2751
|
+
throw new Error(`Invalid JSON in ${absPath}`);
|
|
2752
|
+
}
|
|
2753
|
+
const result = validateDocsMap(raw);
|
|
2754
|
+
if (!result.ok) {
|
|
2755
|
+
throw new Error(`Invalid docs map at ${absPath}: ${result.errors.join("; ")}`);
|
|
2756
|
+
}
|
|
2757
|
+
return { map: result.map, mapPath: absPath, dir: path18.dirname(absPath) };
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// src/utils/docs-corpus.ts
|
|
2761
|
+
import { readFileSync as readFileSync15, statSync as statSync2 } from "node:fs";
|
|
2762
|
+
import * as path19 from "node:path";
|
|
2763
|
+
import { discoverMarkdownFiles } from "@driftdev/sdk";
|
|
2764
|
+
function resolveDocsCorpus(cwd, docsPatterns, configDocs) {
|
|
2765
|
+
if (!docsPatterns || docsPatterns.length === 0) {
|
|
2766
|
+
return discoverMarkdownFiles(cwd, configDocs);
|
|
2767
|
+
}
|
|
2768
|
+
const include = [];
|
|
2769
|
+
for (const pattern of docsPatterns) {
|
|
2770
|
+
if (isDirectory(path19.resolve(cwd, pattern))) {
|
|
2771
|
+
include.push(path19.join(pattern, "**/*.md"), path19.join(pattern, "**/*.mdx"));
|
|
2772
|
+
} else {
|
|
2773
|
+
include.push(pattern);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
const files = discoverMarkdownFiles(cwd, { include });
|
|
2777
|
+
if (files.length === 0) {
|
|
2778
|
+
formatWarning(`--docs matched no markdown files: ${docsPatterns.join(", ")}`);
|
|
2779
|
+
}
|
|
2780
|
+
return files;
|
|
2781
|
+
}
|
|
2782
|
+
function isDirectory(p) {
|
|
2783
|
+
try {
|
|
2784
|
+
return statSync2(p).isDirectory();
|
|
2785
|
+
} catch {
|
|
2786
|
+
return false;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function readPackageName(cwd = process.cwd()) {
|
|
2790
|
+
try {
|
|
2791
|
+
const pkgJson = JSON.parse(readFileSync15(path19.resolve(cwd, "package.json"), "utf-8"));
|
|
2792
|
+
return typeof pkgJson.name === "string" ? pkgJson.name : undefined;
|
|
2793
|
+
} catch {
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
// src/utils/key-coverage-runner.ts
|
|
2799
|
+
import { globSync, readFileSync as readFileSync16 } from "node:fs";
|
|
2800
|
+
import * as path20 from "node:path";
|
|
2801
|
+
import {
|
|
2802
|
+
computeKeyCoverage,
|
|
2803
|
+
DEFAULT_SECTION_RE,
|
|
2804
|
+
extractDocumentedKeys
|
|
2805
|
+
} from "@driftdev/sdk";
|
|
2806
|
+
function resolvePages(page, dir) {
|
|
2807
|
+
const files = [path20.resolve(dir, page.page)];
|
|
2808
|
+
for (const pattern of page.extraPages ?? []) {
|
|
2809
|
+
for (const match of globSync(pattern, { cwd: dir })) {
|
|
2810
|
+
files.push(path20.resolve(dir, match));
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
return files.map((p) => ({ path: p, content: readFileSync16(p, "utf-8") }));
|
|
2814
|
+
}
|
|
2815
|
+
async function resolvePageSpec(page, dir, fallback) {
|
|
2816
|
+
if (page.spec) {
|
|
2817
|
+
const specPath = path20.resolve(dir, page.spec);
|
|
2818
|
+
const raw = JSON.parse(readFileSync16(specPath, "utf-8"));
|
|
2819
|
+
const spec = raw && typeof raw === "object" && "data" in raw && "ok" in raw ? raw.data : raw;
|
|
2820
|
+
if (!Array.isArray(spec?.exports) && !Array.isArray(spec?.types)) {
|
|
2821
|
+
throw new Error(`${page.spec}: not a spec file (no exports/types arrays)`);
|
|
2822
|
+
}
|
|
2823
|
+
return spec;
|
|
2824
|
+
}
|
|
2825
|
+
if (page.entry) {
|
|
2826
|
+
const { apiSpec } = await resolveTruth({ entry: path20.resolve(dir, page.entry) });
|
|
2827
|
+
return apiSpec;
|
|
2828
|
+
}
|
|
2829
|
+
if (!fallback) {
|
|
2830
|
+
throw new Error(`page "${page.page}": no "spec"/"entry" in map and no scan target to fall back to`);
|
|
2831
|
+
}
|
|
2832
|
+
return fallback;
|
|
2833
|
+
}
|
|
2834
|
+
async function runDocsCoverage(loaded, fallbackSpec) {
|
|
2835
|
+
const pages = [];
|
|
2836
|
+
const errors = [];
|
|
2837
|
+
const warnings = [];
|
|
2838
|
+
for (const page of loaded.map.pages) {
|
|
2839
|
+
const spec = await resolvePageSpec(page, loaded.dir, fallbackSpec);
|
|
2840
|
+
const corpus = extractDocumentedKeys(resolvePages(page, loaded.dir), page.sectionRe ? new RegExp(page.sectionRe, "i") : DEFAULT_SECTION_RE);
|
|
2841
|
+
const result = computeKeyCoverage(spec, page.type, corpus, {
|
|
2842
|
+
internal: page.internal,
|
|
2843
|
+
deprecated: page.deprecated,
|
|
2844
|
+
replacements: page.replacements,
|
|
2845
|
+
annotations: page.annotations
|
|
2846
|
+
});
|
|
2847
|
+
if (!result) {
|
|
2848
|
+
throw new Error(`page "${page.page}": type "${page.type}" not found in spec`);
|
|
2849
|
+
}
|
|
2850
|
+
const baseline = page.baselineGaps ?? 0;
|
|
2851
|
+
const failures = [];
|
|
2852
|
+
const pageWarnings = [];
|
|
2853
|
+
for (const ghost of result.ghosts) {
|
|
2854
|
+
failures.push(`ghost option \`${ghost.key}\` — documented but not in ${page.type}`);
|
|
2855
|
+
const loc = ghost.locations[0];
|
|
2856
|
+
errors.push({
|
|
2857
|
+
export: ghost.key,
|
|
2858
|
+
issue: `ghost option \`${ghost.key}\` — documented but does not exist on ${page.type} (or any spec type)`,
|
|
2859
|
+
filePath: loc?.file ?? path20.resolve(loaded.dir, page.page),
|
|
2860
|
+
line: loc?.line
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
const gapCount = result.counts.gapsUserFacing;
|
|
2864
|
+
if (gapCount > baseline) {
|
|
2865
|
+
const newGaps = result.gaps.userFacing.slice(0, 10).map((g) => g.key);
|
|
2866
|
+
failures.push(`${gapCount} undocumented options (baseline ${baseline}) — drift grew: ${newGaps.join(", ")}${gapCount > 10 ? "…" : ""}`);
|
|
2867
|
+
errors.push({
|
|
2868
|
+
export: page.type,
|
|
2869
|
+
issue: `${gapCount} undocumented ${page.type} options (baseline ${baseline})`,
|
|
2870
|
+
filePath: path20.resolve(loaded.dir, page.page)
|
|
2871
|
+
});
|
|
2872
|
+
} else if (gapCount > 0) {
|
|
2873
|
+
pageWarnings.push(`${gapCount} known undocumented options (baseline ${baseline})`);
|
|
2874
|
+
}
|
|
2875
|
+
for (const inv of result.inversions) {
|
|
2876
|
+
pageWarnings.push(`documents deprecated \`${inv.documented}\` but not its replacement \`${inv.replacement}\``);
|
|
2877
|
+
warnings.push({
|
|
2878
|
+
export: inv.documented,
|
|
2879
|
+
issue: `documents deprecated \`${inv.documented}\` but not its replacement \`${inv.replacement}\``,
|
|
2880
|
+
filePath: path20.resolve(loaded.dir, page.page)
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
pages.push({
|
|
2884
|
+
page: page.page,
|
|
2885
|
+
type: page.type,
|
|
2886
|
+
baselineGaps: baseline,
|
|
2887
|
+
status: failures.length > 0 ? "fail" : pageWarnings.length > 0 ? "warn" : "pass",
|
|
2888
|
+
failures,
|
|
2889
|
+
warnings: pageWarnings,
|
|
2890
|
+
result
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
return {
|
|
2894
|
+
pages,
|
|
2895
|
+
pass: pages.every((p) => p.status !== "fail"),
|
|
2896
|
+
annotations: { errors, warnings }
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
// src/commands/docs-map.ts
|
|
2901
|
+
var MATCH_ALL = /(?:)/;
|
|
2902
|
+
var MIN_PAGE_KEYS = 3;
|
|
2903
|
+
var MIN_OVERLAP = 3;
|
|
2904
|
+
function typeKeySets(spec) {
|
|
2905
|
+
const out = new Map;
|
|
2906
|
+
for (const entry of [...spec.exports, ...spec.types ?? []]) {
|
|
2907
|
+
const keys = new Set(collectTypeKeys(entry).keys());
|
|
2908
|
+
if (keys.size >= MIN_OVERLAP && !out.has(entry.name))
|
|
2909
|
+
out.set(entry.name, keys);
|
|
2910
|
+
}
|
|
2911
|
+
return out;
|
|
2912
|
+
}
|
|
2913
|
+
function registerDocsMapCommand(program) {
|
|
2914
|
+
const docsMap = program.command("docs-map").description("Docs-map lifecycle: scaffold and ratchet the page→type artifact");
|
|
2915
|
+
docsMap.command("stub").description("Scaffold a docs map: option-doc pages + type candidates ranked by key overlap").option("--docs <patterns...>", "Docs corpus: glob patterns or directories").option("--lang <language>", "Source language (inferred otherwise)").option("--abi <path>", "ABI JSON file (Clarity)").option("--spec <path>", "OpenAPI document path or URL").option("--out <file>", "Write the stub map to a file (default: stdout only)").action(async (options) => {
|
|
2916
|
+
const startTime = Date.now();
|
|
2917
|
+
const version = getVersion();
|
|
2918
|
+
try {
|
|
2919
|
+
const lang = resolveLang({ lang: options.lang, spec: options.spec, abi: options.abi });
|
|
2920
|
+
const { config } = loadConfig();
|
|
2921
|
+
const entryFile = lang === "typescript" ? config.entry ? path21.resolve(process.cwd(), config.entry) : detectEntry() : undefined;
|
|
2922
|
+
const { apiSpec } = await resolveTruth({
|
|
2923
|
+
entry: entryFile,
|
|
2924
|
+
lang,
|
|
2925
|
+
spec: options.spec,
|
|
2926
|
+
abi: options.abi
|
|
2927
|
+
});
|
|
2928
|
+
const types = typeKeySets(apiSpec);
|
|
2929
|
+
const corpus = resolveDocsCorpus(process.cwd(), options.docs, config.docs);
|
|
2930
|
+
const pages = [];
|
|
2931
|
+
for (const file of corpus) {
|
|
2932
|
+
const extraction = extractDocumentedKeys2([{ path: file.path, content: file.content ?? "" }], MATCH_ALL);
|
|
2933
|
+
const pageKeys = new Set(extraction.documented.keys());
|
|
2934
|
+
if (pageKeys.size < MIN_PAGE_KEYS)
|
|
2935
|
+
continue;
|
|
2936
|
+
const ranked = [...types.entries()].map(([name, keys]) => ({
|
|
2937
|
+
type: name,
|
|
2938
|
+
overlap: [...pageKeys].filter((k) => keys.has(k)).length,
|
|
2939
|
+
keys: keys.size
|
|
2940
|
+
})).filter((c2) => c2.overlap >= MIN_OVERLAP).sort((a, b) => b.overlap - a.overlap).slice(0, 3);
|
|
2941
|
+
if (ranked.length === 0)
|
|
2942
|
+
continue;
|
|
2943
|
+
pages.push({
|
|
2944
|
+
page: path21.relative(process.cwd(), file.path),
|
|
2945
|
+
keys: pageKeys.size,
|
|
2946
|
+
type: ranked[0].type,
|
|
2947
|
+
candidates: ranked
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
const stub = {
|
|
2951
|
+
$schema: "https://unpkg.com/@driftdev/cli/schemas/drift.docs-map.schema.json",
|
|
2952
|
+
version: 1,
|
|
2953
|
+
pages: pages.map((p) => ({
|
|
2954
|
+
page: p.page,
|
|
2955
|
+
type: p.type,
|
|
2956
|
+
baselineGaps: 0
|
|
2957
|
+
}))
|
|
2958
|
+
};
|
|
2959
|
+
if (options.out) {
|
|
2960
|
+
writeFileSync5(path21.resolve(process.cwd(), options.out), `${JSON.stringify(stub, null, 2)}
|
|
2961
|
+
`);
|
|
2962
|
+
}
|
|
2963
|
+
formatOutput("docs-map stub", { candidates: pages, stub, ...options.out ? { written: options.out } : {} }, startTime, version, undefined, {
|
|
2964
|
+
suggested: "drift-docs-map skill",
|
|
2965
|
+
reason: "review type mappings, add sectionRe/annotations, then set baselines"
|
|
2966
|
+
});
|
|
2967
|
+
} catch (err) {
|
|
2968
|
+
formatError("docs-map stub", err instanceof Error ? err.message : String(err), startTime, version);
|
|
2969
|
+
}
|
|
2970
|
+
});
|
|
2971
|
+
docsMap.command("baseline").description("Tighten baselineGaps to current gap counts (ratchet — never raises)").argument("<map>", "Docs map file").action(async (mapArg) => {
|
|
2972
|
+
const startTime = Date.now();
|
|
2973
|
+
const version = getVersion();
|
|
2974
|
+
try {
|
|
2975
|
+
const loaded = loadDocsMap(mapArg);
|
|
2976
|
+
let fallback;
|
|
2977
|
+
try {
|
|
2978
|
+
const { config } = loadConfig();
|
|
2979
|
+
const entryFile = config.entry ? path21.resolve(process.cwd(), config.entry) : detectEntry();
|
|
2980
|
+
fallback = (await resolveTruth({ entry: entryFile })).apiSpec;
|
|
2981
|
+
} catch {}
|
|
2982
|
+
const run = await runDocsCoverage(loaded, fallback);
|
|
2983
|
+
const changes = [];
|
|
2984
|
+
for (const result of run.pages) {
|
|
2985
|
+
const entry = loaded.map.pages.find((p) => p.page === result.page);
|
|
2986
|
+
if (!entry)
|
|
2987
|
+
continue;
|
|
2988
|
+
const current = result.result.counts.gapsUserFacing;
|
|
2989
|
+
const existing = entry.baselineGaps;
|
|
2990
|
+
if (existing === undefined || current < existing) {
|
|
2991
|
+
changes.push({ page: result.page, from: existing ?? current, to: current });
|
|
2992
|
+
entry.baselineGaps = current;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
if (changes.length > 0) {
|
|
2996
|
+
const out = {
|
|
2997
|
+
$schema: loaded.map.$schema,
|
|
2998
|
+
...loaded.map
|
|
2999
|
+
};
|
|
3000
|
+
writeFileSync5(loaded.mapPath, `${JSON.stringify(out, null, 2)}
|
|
3001
|
+
`);
|
|
3002
|
+
}
|
|
3003
|
+
formatOutput("docs-map baseline", { changes, map: loaded.mapPath }, startTime, version);
|
|
3004
|
+
} catch (err) {
|
|
3005
|
+
formatError("docs-map baseline", err instanceof Error ? err.message : String(err), startTime, version);
|
|
3006
|
+
}
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
// src/commands/examples.ts
|
|
3011
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
3012
|
+
import * as path22 from "node:path";
|
|
2657
3013
|
import { validateExamples } from "@driftdev/sdk";
|
|
2658
3014
|
|
|
2659
3015
|
// src/formatters/examples.ts
|
|
@@ -2732,16 +3088,16 @@ function renderExamples(data) {
|
|
|
2732
3088
|
|
|
2733
3089
|
// src/commands/examples.ts
|
|
2734
3090
|
function findPackagePath(entryFile) {
|
|
2735
|
-
let dir =
|
|
2736
|
-
while (dir !==
|
|
3091
|
+
let dir = path22.dirname(entryFile);
|
|
3092
|
+
while (dir !== path22.dirname(dir)) {
|
|
2737
3093
|
try {
|
|
2738
|
-
|
|
3094
|
+
readFileSync17(path22.join(dir, "package.json"), "utf-8");
|
|
2739
3095
|
return dir;
|
|
2740
3096
|
} catch {
|
|
2741
|
-
dir =
|
|
3097
|
+
dir = path22.dirname(dir);
|
|
2742
3098
|
}
|
|
2743
3099
|
}
|
|
2744
|
-
return
|
|
3100
|
+
return path22.dirname(entryFile);
|
|
2745
3101
|
}
|
|
2746
3102
|
function registerExamplesCommand(program) {
|
|
2747
3103
|
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) => {
|
|
@@ -2809,7 +3165,7 @@ function registerExamplesCommand(program) {
|
|
|
2809
3165
|
return;
|
|
2810
3166
|
}
|
|
2811
3167
|
const { config } = loadConfig();
|
|
2812
|
-
const entryFile = entry ?
|
|
3168
|
+
const entryFile = entry ? path22.resolve(process.cwd(), entry) : config.entry ? path22.resolve(process.cwd(), config.entry) : detectEntry();
|
|
2813
3169
|
const { spec } = await cachedExtract(entryFile);
|
|
2814
3170
|
const exports = spec.exports ?? [];
|
|
2815
3171
|
const packagePath = findPackagePath(entryFile);
|
|
@@ -2844,7 +3200,7 @@ function registerExamplesCommand(program) {
|
|
|
2844
3200
|
}
|
|
2845
3201
|
|
|
2846
3202
|
// src/commands/extract.ts
|
|
2847
|
-
import * as
|
|
3203
|
+
import * as path23 from "node:path";
|
|
2848
3204
|
import { Drift } from "@driftdev/sdk";
|
|
2849
3205
|
import { normalize as normalize6 } from "@openpkg-ts/spec";
|
|
2850
3206
|
|
|
@@ -2883,8 +3239,8 @@ function registerExtractCommand(program) {
|
|
|
2883
3239
|
abi: options.abi
|
|
2884
3240
|
});
|
|
2885
3241
|
if (options.output) {
|
|
2886
|
-
const { writeFileSync:
|
|
2887
|
-
|
|
3242
|
+
const { writeFileSync: writeFileSync6 } = await import("node:fs");
|
|
3243
|
+
writeFileSync6(options.output, JSON.stringify(apiSpec, null, 2));
|
|
2888
3244
|
process.stderr.write(`drift extract: wrote ${options.output}
|
|
2889
3245
|
`);
|
|
2890
3246
|
} else {
|
|
@@ -2912,7 +3268,7 @@ function registerExtractCommand(program) {
|
|
|
2912
3268
|
formatOutput("extract", { packages: specs, ...skipped.length > 0 ? { skipped } : {} }, startTime, version);
|
|
2913
3269
|
return;
|
|
2914
3270
|
}
|
|
2915
|
-
const entryFile = entry ?
|
|
3271
|
+
const entryFile = entry ? path23.resolve(process.cwd(), entry) : detectEntry();
|
|
2916
3272
|
const hasFilters = !!(options.only || options.ignore);
|
|
2917
3273
|
let spec;
|
|
2918
3274
|
if (hasFilters) {
|
|
@@ -2937,8 +3293,8 @@ function registerExtractCommand(program) {
|
|
|
2937
3293
|
spec = result.spec;
|
|
2938
3294
|
}
|
|
2939
3295
|
if (options.output) {
|
|
2940
|
-
const { writeFileSync:
|
|
2941
|
-
|
|
3296
|
+
const { writeFileSync: writeFileSync6 } = await import("node:fs");
|
|
3297
|
+
writeFileSync6(options.output, JSON.stringify(spec, null, 2));
|
|
2942
3298
|
process.stderr.write(`drift extract: wrote ${options.output}
|
|
2943
3299
|
`);
|
|
2944
3300
|
} else {
|
|
@@ -2951,8 +3307,8 @@ function registerExtractCommand(program) {
|
|
|
2951
3307
|
}
|
|
2952
3308
|
|
|
2953
3309
|
// src/commands/filter.ts
|
|
2954
|
-
import { readFileSync as
|
|
2955
|
-
import * as
|
|
3310
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
3311
|
+
import * as path24 from "node:path";
|
|
2956
3312
|
import { filterSpec } from "@openpkg-ts/sdk";
|
|
2957
3313
|
|
|
2958
3314
|
// src/formatters/filter.ts
|
|
@@ -2983,8 +3339,8 @@ function registerFilterCommand(program) {
|
|
|
2983
3339
|
const startTime = Date.now();
|
|
2984
3340
|
const version = getVersion();
|
|
2985
3341
|
try {
|
|
2986
|
-
const filePath =
|
|
2987
|
-
const content =
|
|
3342
|
+
const filePath = path24.resolve(process.cwd(), file);
|
|
3343
|
+
const content = readFileSync18(filePath, "utf-8");
|
|
2988
3344
|
const spec = JSON.parse(content);
|
|
2989
3345
|
const criteria = {};
|
|
2990
3346
|
if (options.kind) {
|
|
@@ -3010,7 +3366,7 @@ function registerFilterCommand(program) {
|
|
|
3010
3366
|
}
|
|
3011
3367
|
|
|
3012
3368
|
// src/commands/get.ts
|
|
3013
|
-
import * as
|
|
3369
|
+
import * as path25 from "node:path";
|
|
3014
3370
|
import { getExport, listExports } from "@openpkg-ts/sdk";
|
|
3015
3371
|
|
|
3016
3372
|
// src/formatters/get.ts
|
|
@@ -3261,7 +3617,7 @@ function registerGetCommand(program) {
|
|
|
3261
3617
|
let entryFile;
|
|
3262
3618
|
let exportName;
|
|
3263
3619
|
if (name) {
|
|
3264
|
-
entryFile =
|
|
3620
|
+
entryFile = path25.resolve(process.cwd(), nameOrEntry);
|
|
3265
3621
|
exportName = name;
|
|
3266
3622
|
} else {
|
|
3267
3623
|
entryFile = detectEntry();
|
|
@@ -3334,7 +3690,7 @@ function renderNotFound(exportName, suggestions, startTime, version) {
|
|
|
3334
3690
|
}
|
|
3335
3691
|
|
|
3336
3692
|
// src/commands/health.ts
|
|
3337
|
-
import * as
|
|
3693
|
+
import * as path26 from "node:path";
|
|
3338
3694
|
import { computeDrift as computeDrift3, isExternalExport as isExternalExport2 } from "@driftdev/sdk";
|
|
3339
3695
|
|
|
3340
3696
|
// src/formatters/health.ts
|
|
@@ -3455,9 +3811,9 @@ function registerHealthCommand(program) {
|
|
|
3455
3811
|
return;
|
|
3456
3812
|
}
|
|
3457
3813
|
const { config } = loadConfig();
|
|
3458
|
-
let entryFile = entry ?
|
|
3814
|
+
let entryFile = entry ? path26.resolve(process.cwd(), entry) : undefined;
|
|
3459
3815
|
if (lang === "typescript" && !entryFile) {
|
|
3460
|
-
entryFile = config.entry ?
|
|
3816
|
+
entryFile = config.entry ? path26.resolve(process.cwd(), config.entry) : detectEntry();
|
|
3461
3817
|
}
|
|
3462
3818
|
const {
|
|
3463
3819
|
apiSpec: spec,
|
|
@@ -3522,8 +3878,8 @@ function registerHealthCommand(program) {
|
|
|
3522
3878
|
|
|
3523
3879
|
// src/commands/init.ts
|
|
3524
3880
|
init_global();
|
|
3525
|
-
import { existsSync as
|
|
3526
|
-
import * as
|
|
3881
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3882
|
+
import * as path27 from "node:path";
|
|
3527
3883
|
import { extract as extract6 } from "@openpkg-ts/sdk";
|
|
3528
3884
|
import { normalize as normalize7 } from "@openpkg-ts/spec";
|
|
3529
3885
|
|
|
@@ -3829,14 +4185,14 @@ ${detailLine}`);
|
|
|
3829
4185
|
|
|
3830
4186
|
// src/commands/init.ts
|
|
3831
4187
|
async function scanPackage(cwd, pkgDir) {
|
|
3832
|
-
const absDir =
|
|
3833
|
-
if (!
|
|
4188
|
+
const absDir = path27.join(cwd, pkgDir);
|
|
4189
|
+
if (!existsSync14(absDir))
|
|
3834
4190
|
return null;
|
|
3835
|
-
const pkgPath =
|
|
4191
|
+
const pkgPath = path27.join(absDir, "package.json");
|
|
3836
4192
|
let name = pkgDir;
|
|
3837
|
-
if (
|
|
4193
|
+
if (existsSync14(pkgPath)) {
|
|
3838
4194
|
try {
|
|
3839
|
-
const pkg = JSON.parse(
|
|
4195
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
3840
4196
|
if (pkg.name)
|
|
3841
4197
|
name = pkg.name;
|
|
3842
4198
|
} catch {}
|
|
@@ -3854,7 +4210,7 @@ async function scanPackage(cwd, pkgDir) {
|
|
|
3854
4210
|
}
|
|
3855
4211
|
const coverage = total > 0 ? Math.round(documented / total * 100) : 100;
|
|
3856
4212
|
const health = Math.round(coverage * 0.5 + 100 * 0.5);
|
|
3857
|
-
return { name, entry:
|
|
4213
|
+
return { name, entry: path27.relative(cwd, entryFile), exports: total, coverage, health };
|
|
3858
4214
|
} catch {
|
|
3859
4215
|
return null;
|
|
3860
4216
|
}
|
|
@@ -3891,13 +4247,13 @@ function registerInitCommand(program) {
|
|
|
3891
4247
|
return;
|
|
3892
4248
|
}
|
|
3893
4249
|
const config = generateConfig(packages);
|
|
3894
|
-
const configPath = opts.project ?
|
|
4250
|
+
const configPath = opts.project ? path27.resolve(cwd, "drift.config.json") : getGlobalConfigPath();
|
|
3895
4251
|
if (!opts.project) {
|
|
3896
4252
|
const globalDir = getGlobalDir();
|
|
3897
|
-
if (!
|
|
4253
|
+
if (!existsSync14(globalDir))
|
|
3898
4254
|
mkdirSync6(globalDir, { recursive: true });
|
|
3899
4255
|
}
|
|
3900
|
-
|
|
4256
|
+
writeFileSync6(configPath, `${JSON.stringify(config, null, 2)}
|
|
3901
4257
|
`);
|
|
3902
4258
|
ensureProjectDir(cwd);
|
|
3903
4259
|
const data = {
|
|
@@ -3915,7 +4271,7 @@ function registerInitCommand(program) {
|
|
|
3915
4271
|
}
|
|
3916
4272
|
|
|
3917
4273
|
// src/commands/lint.ts
|
|
3918
|
-
import * as
|
|
4274
|
+
import * as path28 from "node:path";
|
|
3919
4275
|
import { buildExportRegistry, computeDrift as computeDrift4, detectProseDrift } from "@driftdev/sdk";
|
|
3920
4276
|
|
|
3921
4277
|
// src/formatters/lint.ts
|
|
@@ -3972,44 +4328,6 @@ function emitAnnotations(issues, level = "error") {
|
|
|
3972
4328
|
}
|
|
3973
4329
|
}
|
|
3974
4330
|
|
|
3975
|
-
// src/utils/docs-corpus.ts
|
|
3976
|
-
import { readFileSync as readFileSync17, statSync as statSync2 } from "node:fs";
|
|
3977
|
-
import * as path24 from "node:path";
|
|
3978
|
-
import { discoverMarkdownFiles } from "@driftdev/sdk";
|
|
3979
|
-
function resolveDocsCorpus(cwd, docsPatterns, configDocs) {
|
|
3980
|
-
if (!docsPatterns || docsPatterns.length === 0) {
|
|
3981
|
-
return discoverMarkdownFiles(cwd, configDocs);
|
|
3982
|
-
}
|
|
3983
|
-
const include = [];
|
|
3984
|
-
for (const pattern of docsPatterns) {
|
|
3985
|
-
if (isDirectory(path24.resolve(cwd, pattern))) {
|
|
3986
|
-
include.push(path24.join(pattern, "**/*.md"), path24.join(pattern, "**/*.mdx"));
|
|
3987
|
-
} else {
|
|
3988
|
-
include.push(pattern);
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
const files = discoverMarkdownFiles(cwd, { include });
|
|
3992
|
-
if (files.length === 0) {
|
|
3993
|
-
formatWarning(`--docs matched no markdown files: ${docsPatterns.join(", ")}`);
|
|
3994
|
-
}
|
|
3995
|
-
return files;
|
|
3996
|
-
}
|
|
3997
|
-
function isDirectory(p) {
|
|
3998
|
-
try {
|
|
3999
|
-
return statSync2(p).isDirectory();
|
|
4000
|
-
} catch {
|
|
4001
|
-
return false;
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
function readPackageName(cwd = process.cwd()) {
|
|
4005
|
-
try {
|
|
4006
|
-
const pkgJson = JSON.parse(readFileSync17(path24.resolve(cwd, "package.json"), "utf-8"));
|
|
4007
|
-
return typeof pkgJson.name === "string" ? pkgJson.name : undefined;
|
|
4008
|
-
} catch {
|
|
4009
|
-
return;
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
|
|
4013
4331
|
// src/commands/lint.ts
|
|
4014
4332
|
function registerLintCommand(program) {
|
|
4015
4333
|
program.command("lint [entry]").description("Cross-reference docs against the API surface for accuracy issues").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").option("--annotations", "Emit GitHub Actions ::error annotations for findings").action(async (entry, options) => {
|
|
@@ -4064,9 +4382,9 @@ function registerLintCommand(program) {
|
|
|
4064
4382
|
formatOutput("lint", { issues: [], count: 0 }, startTime, version, renderLint);
|
|
4065
4383
|
return;
|
|
4066
4384
|
}
|
|
4067
|
-
let entryFile = entry ?
|
|
4385
|
+
let entryFile = entry ? path28.resolve(process.cwd(), entry) : undefined;
|
|
4068
4386
|
if (lang === "typescript" && !entryFile) {
|
|
4069
|
-
entryFile = config.entry ?
|
|
4387
|
+
entryFile = config.entry ? path28.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4070
4388
|
}
|
|
4071
4389
|
const { apiSpec: spec } = await resolveTruth({
|
|
4072
4390
|
entry: entryFile,
|
|
@@ -4132,7 +4450,7 @@ function registerLintCommand(program) {
|
|
|
4132
4450
|
}
|
|
4133
4451
|
|
|
4134
4452
|
// src/commands/list.ts
|
|
4135
|
-
import * as
|
|
4453
|
+
import * as path29 from "node:path";
|
|
4136
4454
|
import { computeDrift as computeDrift5 } from "@driftdev/sdk";
|
|
4137
4455
|
import { listExports as listExports2 } from "@openpkg-ts/sdk";
|
|
4138
4456
|
|
|
@@ -4247,7 +4565,7 @@ function registerListCommand(program) {
|
|
|
4247
4565
|
} else {
|
|
4248
4566
|
let entryFile;
|
|
4249
4567
|
if (searchOrEntry && looksLikeFilePath(searchOrEntry)) {
|
|
4250
|
-
entryFile =
|
|
4568
|
+
entryFile = path29.resolve(process.cwd(), searchOrEntry);
|
|
4251
4569
|
} else if (searchOrEntry) {
|
|
4252
4570
|
entryFile = detectEntry();
|
|
4253
4571
|
searchTerm = searchOrEntry;
|
|
@@ -4323,7 +4641,7 @@ function truthFlags(args) {
|
|
|
4323
4641
|
return out;
|
|
4324
4642
|
}
|
|
4325
4643
|
function runDrift(cliArgs, cwd) {
|
|
4326
|
-
return new Promise((
|
|
4644
|
+
return new Promise((resolve22) => {
|
|
4327
4645
|
const child = spawn(process.execPath, [process.argv[1], ...cliArgs, "--json"], {
|
|
4328
4646
|
cwd: cwd ?? process.cwd(),
|
|
4329
4647
|
env: { ...process.env, NO_COLOR: "1" },
|
|
@@ -4343,9 +4661,9 @@ function runDrift(cliArgs, cwd) {
|
|
|
4343
4661
|
try {
|
|
4344
4662
|
ok = JSON.parse(stdout).ok === true;
|
|
4345
4663
|
} catch {}
|
|
4346
|
-
|
|
4664
|
+
resolve22({ text, ok });
|
|
4347
4665
|
});
|
|
4348
|
-
child.on("error", (err) =>
|
|
4666
|
+
child.on("error", (err) => resolve22({ text: `Failed to run drift: ${err.message}`, ok: false }));
|
|
4349
4667
|
});
|
|
4350
4668
|
}
|
|
4351
4669
|
function toResult({ text, ok }) {
|
|
@@ -4503,8 +4821,8 @@ function diffArgs(command, args) {
|
|
|
4503
4821
|
|
|
4504
4822
|
// src/commands/release.ts
|
|
4505
4823
|
import { execSync as execSync4 } from "node:child_process";
|
|
4506
|
-
import { existsSync as
|
|
4507
|
-
import * as
|
|
4824
|
+
import { existsSync as existsSync15, readFileSync as readFileSync20 } from "node:fs";
|
|
4825
|
+
import * as path30 from "node:path";
|
|
4508
4826
|
import { computeDrift as computeDrift6 } from "@driftdev/sdk";
|
|
4509
4827
|
|
|
4510
4828
|
// src/formatters/release.ts
|
|
@@ -4552,11 +4870,11 @@ function getLastTag() {
|
|
|
4552
4870
|
}
|
|
4553
4871
|
}
|
|
4554
4872
|
function getPackageVersion(cwd) {
|
|
4555
|
-
const pkgPath =
|
|
4556
|
-
if (!
|
|
4873
|
+
const pkgPath = path30.join(cwd, "package.json");
|
|
4874
|
+
if (!existsSync15(pkgPath))
|
|
4557
4875
|
return null;
|
|
4558
4876
|
try {
|
|
4559
|
-
return JSON.parse(
|
|
4877
|
+
return JSON.parse(readFileSync20(pkgPath, "utf-8")).version ?? null;
|
|
4560
4878
|
} catch {
|
|
4561
4879
|
return null;
|
|
4562
4880
|
}
|
|
@@ -4568,7 +4886,7 @@ function registerReleaseCommand(program) {
|
|
|
4568
4886
|
const cwd = process.cwd();
|
|
4569
4887
|
try {
|
|
4570
4888
|
const { config } = loadConfig();
|
|
4571
|
-
const entryFile = entry ?
|
|
4889
|
+
const entryFile = entry ? path30.resolve(cwd, entry) : config.entry ? path30.resolve(cwd, config.entry) : detectEntry();
|
|
4572
4890
|
const { spec } = await cachedExtract(entryFile);
|
|
4573
4891
|
const exports = spec.exports ?? [];
|
|
4574
4892
|
const total = exports.length;
|
|
@@ -4692,8 +5010,8 @@ function renderReport(data) {
|
|
|
4692
5010
|
|
|
4693
5011
|
// src/utils/scan-packages.ts
|
|
4694
5012
|
import { execSync as execSync5 } from "node:child_process";
|
|
4695
|
-
import { existsSync as
|
|
4696
|
-
import * as
|
|
5013
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "node:fs";
|
|
5014
|
+
import * as path31 from "node:path";
|
|
4697
5015
|
import { computeDrift as computeDrift7 } from "@driftdev/sdk";
|
|
4698
5016
|
function detectPackageDirs2(cwd) {
|
|
4699
5017
|
const workspaces = detectWorkspaces(cwd);
|
|
@@ -4712,14 +5030,14 @@ async function scanAllPackages(cwd) {
|
|
|
4712
5030
|
const packageDirs = detectPackageDirs2(cwd);
|
|
4713
5031
|
const results = [];
|
|
4714
5032
|
for (const dir of packageDirs) {
|
|
4715
|
-
const absDir = dir === "." ? cwd :
|
|
4716
|
-
if (!
|
|
5033
|
+
const absDir = dir === "." ? cwd : path31.join(cwd, dir);
|
|
5034
|
+
if (!existsSync16(absDir))
|
|
4717
5035
|
continue;
|
|
4718
5036
|
let name = dir;
|
|
4719
|
-
const pkgPath =
|
|
4720
|
-
if (
|
|
5037
|
+
const pkgPath = path31.join(absDir, "package.json");
|
|
5038
|
+
if (existsSync16(pkgPath)) {
|
|
4721
5039
|
try {
|
|
4722
|
-
const pkg = JSON.parse(
|
|
5040
|
+
const pkg = JSON.parse(readFileSync21(pkgPath, "utf-8"));
|
|
4723
5041
|
if (pkg.name)
|
|
4724
5042
|
name = pkg.name;
|
|
4725
5043
|
} catch {}
|
|
@@ -4815,7 +5133,7 @@ function registerReportCommand(program) {
|
|
|
4815
5133
|
}
|
|
4816
5134
|
|
|
4817
5135
|
// src/commands/scan.ts
|
|
4818
|
-
import * as
|
|
5136
|
+
import * as path32 from "node:path";
|
|
4819
5137
|
import {
|
|
4820
5138
|
buildExportRegistry as buildExportRegistry2,
|
|
4821
5139
|
computeDrift as computeDrift8,
|
|
@@ -4851,6 +5169,20 @@ function renderScan(data, next) {
|
|
|
4851
5169
|
}
|
|
4852
5170
|
lines.push("");
|
|
4853
5171
|
}
|
|
5172
|
+
if (data.docsCoverage) {
|
|
5173
|
+
lines.push(indent("Docs coverage"));
|
|
5174
|
+
lines.push(indent(c.gray(separator())));
|
|
5175
|
+
for (const page of data.docsCoverage.pages) {
|
|
5176
|
+
const mark = page.status === "fail" ? c.red(sym.x) : page.status === "warn" ? c.yellow("!") : c.green(sym.ok);
|
|
5177
|
+
const cts = page.counts;
|
|
5178
|
+
lines.push(indent(`${mark} ${page.page} ${c.dim(`${page.type}: ${cts.documented} documented, ${cts.code} in code, ${cts.gapsUserFacing} gaps, ${cts.ghosts} ghosts, ${cts.inversions} inversions`)}`));
|
|
5179
|
+
for (const f of page.failures)
|
|
5180
|
+
lines.push(indent(` ${c.red(f)}`));
|
|
5181
|
+
for (const w of page.warnings)
|
|
5182
|
+
lines.push(indent(` ${c.yellow(w)}`));
|
|
5183
|
+
}
|
|
5184
|
+
lines.push("");
|
|
5185
|
+
}
|
|
4854
5186
|
if (data.pass) {
|
|
4855
5187
|
lines.push(indent(`${c.green(sym.ok)} Scan passed`));
|
|
4856
5188
|
} else {
|
|
@@ -4884,7 +5216,7 @@ function renderBatchScan(data) {
|
|
|
4884
5216
|
|
|
4885
5217
|
// src/commands/scan.ts
|
|
4886
5218
|
function registerScanCommand(program) {
|
|
4887
|
-
program.command("scan [entry]").description("Run coverage + lint + prose drift in one pass").option("--min <n>", "Minimum health threshold (exit 1 if below)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").action(async (entry, options) => {
|
|
5219
|
+
program.command("scan [entry]").description("Run coverage + lint + prose drift in one pass").option("--min <n>", "Minimum health threshold (exit 1 if below)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--lang <language>", "Source language (inferred from --spec/--abi/.clar; default typescript)").option("--abi <path>", "ABI JSON file (required for --lang clarity)").option("--spec <path>", "OpenAPI document: path or URL (implies --lang openapi)").option("--docs <patterns...>", "Markdown corpus for prose drift: glob patterns or directories (overrides repo-local defaults)").option("--docs-map <file>", "Docs map (page→type) activating key-coverage mode: gaps/ghosts/inversions per page").option("--annotations", "Emit GitHub Actions ::error/::warning annotations for findings").action(async (entry, options) => {
|
|
4888
5220
|
const startTime = Date.now();
|
|
4889
5221
|
const version = getVersion();
|
|
4890
5222
|
try {
|
|
@@ -4898,6 +5230,10 @@ function registerScanCommand(program) {
|
|
|
4898
5230
|
formatError("scan", `Batch mode (--all) not yet supported for ${lang}`, startTime, version);
|
|
4899
5231
|
return;
|
|
4900
5232
|
}
|
|
5233
|
+
if (options.all && options.docsMap) {
|
|
5234
|
+
formatError("scan", "--docs-map is not supported with --all", startTime, version);
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
4901
5237
|
if (lang === "clarity" && !options.abi) {
|
|
4902
5238
|
formatError("scan", "--abi is required when --lang clarity", startTime, version);
|
|
4903
5239
|
return;
|
|
@@ -4959,9 +5295,9 @@ function registerScanCommand(program) {
|
|
|
4959
5295
|
return;
|
|
4960
5296
|
}
|
|
4961
5297
|
const { config } = loadConfig();
|
|
4962
|
-
let entryFile = entry ?
|
|
5298
|
+
let entryFile = entry ? path32.resolve(process.cwd(), entry) : undefined;
|
|
4963
5299
|
if (lang === "typescript" && !entryFile) {
|
|
4964
|
-
entryFile = config.entry ?
|
|
5300
|
+
entryFile = config.entry ? path32.resolve(process.cwd(), config.entry) : detectEntry();
|
|
4965
5301
|
}
|
|
4966
5302
|
const { apiSpec, packageName, packageVersion } = await resolveTruth({
|
|
4967
5303
|
entry: entryFile,
|
|
@@ -5017,6 +5353,11 @@ function registerScanCommand(program) {
|
|
|
5017
5353
|
formatWarning(`Prose drift skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
5018
5354
|
}
|
|
5019
5355
|
}
|
|
5356
|
+
let docsCoverage;
|
|
5357
|
+
if (options.docsMap) {
|
|
5358
|
+
const loaded = loadDocsMap(options.docsMap);
|
|
5359
|
+
docsCoverage = await runDocsCoverage(loaded, apiSpec);
|
|
5360
|
+
}
|
|
5020
5361
|
const healthIssues = issues.map((i) => ({ export: i.export, issue: i.issue }));
|
|
5021
5362
|
const h = computeHealth(total, documented, healthIssues);
|
|
5022
5363
|
let min = options.min ? parseInt(options.min, 10) : config.coverage?.min;
|
|
@@ -5024,7 +5365,7 @@ function registerScanCommand(program) {
|
|
|
5024
5365
|
const ratchet = computeRatchetMin(min);
|
|
5025
5366
|
min = ratchet.effectiveMin;
|
|
5026
5367
|
}
|
|
5027
|
-
const pass = min === undefined || h.health >= min;
|
|
5368
|
+
const pass = (min === undefined || h.health >= min) && (docsCoverage?.pass ?? true);
|
|
5028
5369
|
const data = {
|
|
5029
5370
|
coverage: {
|
|
5030
5371
|
score: coverageScore,
|
|
@@ -5037,7 +5378,26 @@ function registerScanCommand(program) {
|
|
|
5037
5378
|
health: h.health,
|
|
5038
5379
|
pass,
|
|
5039
5380
|
packageName,
|
|
5040
|
-
packageVersion
|
|
5381
|
+
packageVersion,
|
|
5382
|
+
...docsCoverage ? {
|
|
5383
|
+
docsCoverage: {
|
|
5384
|
+
pass: docsCoverage.pass,
|
|
5385
|
+
pages: docsCoverage.pages.map((p) => ({
|
|
5386
|
+
page: p.page,
|
|
5387
|
+
type: p.type,
|
|
5388
|
+
status: p.status,
|
|
5389
|
+
baselineGaps: p.baselineGaps,
|
|
5390
|
+
counts: p.result.counts,
|
|
5391
|
+
failures: p.failures,
|
|
5392
|
+
warnings: p.warnings,
|
|
5393
|
+
gaps: p.result.gaps,
|
|
5394
|
+
ghosts: p.result.ghosts,
|
|
5395
|
+
inversions: p.result.inversions,
|
|
5396
|
+
documentedKeysFromOtherTypes: p.result.documentedKeysFromOtherTypes,
|
|
5397
|
+
annotated: p.result.annotated
|
|
5398
|
+
}))
|
|
5399
|
+
}
|
|
5400
|
+
} : {}
|
|
5041
5401
|
};
|
|
5042
5402
|
let next;
|
|
5043
5403
|
if (issues.length > 0) {
|
|
@@ -5052,9 +5412,16 @@ function registerScanCommand(program) {
|
|
|
5052
5412
|
};
|
|
5053
5413
|
}
|
|
5054
5414
|
formatOutput("scan", data, startTime, version, renderScan, next);
|
|
5415
|
+
if (options.annotations && docsCoverage) {
|
|
5416
|
+
emitAnnotations(docsCoverage.annotations.errors, "error");
|
|
5417
|
+
emitAnnotations(docsCoverage.annotations.warnings, "warning");
|
|
5418
|
+
}
|
|
5419
|
+
if (options.annotations && issues.length > 0)
|
|
5420
|
+
emitAnnotations(issues);
|
|
5055
5421
|
if (!pass) {
|
|
5056
5422
|
if (!shouldRenderHuman()) {
|
|
5057
|
-
|
|
5423
|
+
const covFails = docsCoverage ? docsCoverage.pages.flatMap((p) => p.failures.map((f) => `${p.page}: ${f}`)) : [];
|
|
5424
|
+
process.stderr.write(`scan failed: health ${h.health}%${min !== undefined ? ` (need ${min}%)` : ""}, ${issues.length} issues${covFails.length > 0 ? `; docs coverage: ${covFails.join(" | ")}` : ""}
|
|
5058
5425
|
`);
|
|
5059
5426
|
}
|
|
5060
5427
|
process.exitCode = 1;
|
|
@@ -5105,8 +5472,8 @@ function registerSemverCommand(program) {
|
|
|
5105
5472
|
}
|
|
5106
5473
|
|
|
5107
5474
|
// src/commands/validate.ts
|
|
5108
|
-
import { readFileSync as
|
|
5109
|
-
import * as
|
|
5475
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
5476
|
+
import * as path33 from "node:path";
|
|
5110
5477
|
import { validateSpec } from "@openpkg-ts/spec";
|
|
5111
5478
|
|
|
5112
5479
|
// src/formatters/validate.ts
|
|
@@ -5132,8 +5499,8 @@ function registerValidateCommand(program) {
|
|
|
5132
5499
|
const startTime = Date.now();
|
|
5133
5500
|
const version = getVersion();
|
|
5134
5501
|
try {
|
|
5135
|
-
const filePath =
|
|
5136
|
-
const content =
|
|
5502
|
+
const filePath = path33.resolve(process.cwd(), file);
|
|
5503
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
5137
5504
|
const spec = JSON.parse(content);
|
|
5138
5505
|
const result = validateSpec(spec);
|
|
5139
5506
|
const data = {
|
|
@@ -5184,7 +5551,11 @@ var COMMAND_EXAMPLES = {
|
|
|
5184
5551
|
validate: ["drift validate spec.json --json"],
|
|
5185
5552
|
filter: ["drift filter spec.json --kind function --json"],
|
|
5186
5553
|
report: ["drift report --json"],
|
|
5187
|
-
cache: ["drift cache status", "drift cache clear"]
|
|
5554
|
+
cache: ["drift cache status", "drift cache clear"],
|
|
5555
|
+
"docs-map": [
|
|
5556
|
+
"drift docs-map stub --docs docs/ --out drift.docs-map.json",
|
|
5557
|
+
"drift docs-map baseline drift.docs-map.json"
|
|
5558
|
+
]
|
|
5188
5559
|
};
|
|
5189
5560
|
function extractCapabilities(program) {
|
|
5190
5561
|
const commands = [];
|
|
@@ -5235,6 +5606,15 @@ function extractCapabilities(program) {
|
|
|
5235
5606
|
name: "examples",
|
|
5236
5607
|
description: "Example validation results",
|
|
5237
5608
|
operations: { read: "examples" }
|
|
5609
|
+
},
|
|
5610
|
+
{
|
|
5611
|
+
name: "docs-map",
|
|
5612
|
+
description: "Committed page→type map for docs key-coverage mode",
|
|
5613
|
+
operations: {
|
|
5614
|
+
create: "docs-map stub",
|
|
5615
|
+
update: "docs-map baseline",
|
|
5616
|
+
read: "scan --docs-map <file>"
|
|
5617
|
+
}
|
|
5238
5618
|
}
|
|
5239
5619
|
],
|
|
5240
5620
|
workflows: {
|
|
@@ -5251,6 +5631,10 @@ function extractCapabilities(program) {
|
|
|
5251
5631
|
"pre-release": {
|
|
5252
5632
|
steps: ["scan", "breaking", "release"],
|
|
5253
5633
|
description: "Full pre-release quality gate"
|
|
5634
|
+
},
|
|
5635
|
+
"docs-key-coverage": {
|
|
5636
|
+
steps: ["docs-map stub", "scan --docs-map", "docs-map baseline"],
|
|
5637
|
+
description: "Gap/ghost/inversion gate: docs pages vs spec type keys"
|
|
5254
5638
|
}
|
|
5255
5639
|
}
|
|
5256
5640
|
};
|
|
@@ -5258,13 +5642,13 @@ function extractCapabilities(program) {
|
|
|
5258
5642
|
|
|
5259
5643
|
// src/drift.ts
|
|
5260
5644
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
5261
|
-
var __dirname3 =
|
|
5262
|
-
var packageJson = JSON.parse(
|
|
5645
|
+
var __dirname3 = path34.dirname(__filename2);
|
|
5646
|
+
var packageJson = JSON.parse(readFileSync23(path34.join(__dirname3, "../package.json"), "utf-8"));
|
|
5263
5647
|
var program = new Command;
|
|
5264
5648
|
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) => {
|
|
5265
5649
|
const opts = program.opts();
|
|
5266
5650
|
if (opts.cwd) {
|
|
5267
|
-
process.chdir(
|
|
5651
|
+
process.chdir(path34.resolve(opts.cwd));
|
|
5268
5652
|
}
|
|
5269
5653
|
setOutputMode({ json: opts.json, human: opts.human });
|
|
5270
5654
|
setConfigPath(opts.config);
|
|
@@ -5276,6 +5660,7 @@ registerExtractCommand(program);
|
|
|
5276
5660
|
registerListCommand(program);
|
|
5277
5661
|
registerGetCommand(program);
|
|
5278
5662
|
registerValidateCommand(program);
|
|
5663
|
+
registerDocsMapCommand(program);
|
|
5279
5664
|
registerFilterCommand(program);
|
|
5280
5665
|
registerCoverageCommand(program);
|
|
5281
5666
|
registerExamplesCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftdev/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
4
4
|
"description": "Drift CLI - detect when your docs drift from your code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@driftdev/clarity-adapter": "^1.0.1",
|
|
49
49
|
"@driftdev/openapi-adapter": "^1.0.1",
|
|
50
|
-
"@driftdev/sdk": "^1.
|
|
50
|
+
"@driftdev/sdk": "^1.12.0",
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
52
|
"@openpkg-ts/sdk": "^0.43.0",
|
|
53
53
|
"@openpkg-ts/spec": "^0.43.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://unpkg.com/@driftdev/cli/schemas/drift.docs-map.schema.json",
|
|
4
|
+
"title": "Drift docs map",
|
|
5
|
+
"description": "Page→type mapping for docs key-coverage mode (drift scan --docs-map). The committed artifact of the LLM-writes-the-map / machine-runs-the-map split: an agent may propose it, a human commits it, CI runs it deterministically.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "pages"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"version": { "const": 1 },
|
|
11
|
+
"pages": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["page", "type"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"page": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Docs page path, relative to this file's directory"
|
|
20
|
+
},
|
|
21
|
+
"extraPages": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": { "type": "string" },
|
|
24
|
+
"description": "Additional pages/globs merged into the same corpus (e.g. _snippets/*.mdx)"
|
|
25
|
+
},
|
|
26
|
+
"type": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Spec type whose property keys this page documents"
|
|
29
|
+
},
|
|
30
|
+
"spec": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Committed spec file to diff against (relative to this file). Mutually exclusive with entry; omit both to use the scan target."
|
|
33
|
+
},
|
|
34
|
+
"entry": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Entry file to extract the spec from. Mutually exclusive with spec."
|
|
37
|
+
},
|
|
38
|
+
"sectionRe": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Case-insensitive heading regex opening an options section (default: option|config)"
|
|
41
|
+
},
|
|
42
|
+
"internal": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"items": { "type": "string" },
|
|
45
|
+
"description": "Internal keys beyond the _-prefix convention (excluded from user-facing gaps)"
|
|
46
|
+
},
|
|
47
|
+
"deprecated": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string" },
|
|
50
|
+
"description": "Deprecated override — auto-derived from spec metadata when omitted"
|
|
51
|
+
},
|
|
52
|
+
"replacements": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": { "type": "string" },
|
|
55
|
+
"description": "deprecatedKey → replacementKey override — auto-derived from spec deprecation reasons when omitted"
|
|
56
|
+
},
|
|
57
|
+
"annotations": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"additionalProperties": {
|
|
60
|
+
"enum": ["prose-documented", "internal-by-convention", "ignore"]
|
|
61
|
+
},
|
|
62
|
+
"description": "Agent-proposed, human-committed key classifications. prose-documented: genuinely documented in prose, excluded from gap FAIL; internal-by-convention: treated as internal; ignore: excluded entirely (state the reason in a nearby comment key)."
|
|
63
|
+
},
|
|
64
|
+
"baselineGaps": {
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"minimum": 0,
|
|
67
|
+
"description": "Gap ratchet: CI fails when user-facing gaps exceed this committed count. Drift shrinks, never grows — use `drift docs-map baseline` to tighten."
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"additionalProperties": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": true
|
|
75
|
+
}
|