driftfence 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/{chunk-WK3NGHEY.js → chunk-T6PL4YLE.js} +98 -2
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# DriftFence
|
|
2
2
|
|
|
3
3
|
[](https://github.com/CHAPAPOPA/driftfence/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/driftfence)
|
|
4
5
|
|
|
5
6
|
Make sure your README doesn't lie.
|
|
6
7
|
|
|
@@ -70,13 +71,13 @@ Env vars:
|
|
|
70
71
|
|
|
71
72
|
## MVP Checks
|
|
72
73
|
|
|
73
|
-
DriftFence checks `README.md` and `docs/**/*.md` for documentation drift.
|
|
74
|
+
DriftFence checks `README.md` and `docs/**/*.{md,mdx}` for documentation drift.
|
|
74
75
|
|
|
75
76
|
Current checks:
|
|
76
77
|
|
|
77
78
|
- package script references
|
|
78
79
|
- file path references
|
|
79
|
-
- env var references in Markdown docs
|
|
80
|
+
- env var references in Markdown and MDX docs
|
|
80
81
|
- env var usage in source files
|
|
81
82
|
|
|
82
83
|
Package script references include commands like:
|
|
@@ -124,9 +125,27 @@ DriftFence uses stable CLI exit codes:
|
|
|
124
125
|
- `1` — documentation drift found
|
|
125
126
|
- `2` — invalid project directory or CLI usage error
|
|
126
127
|
|
|
128
|
+
## Ignoring intentional examples
|
|
129
|
+
|
|
130
|
+
Use ignore blocks when docs intentionally show fake paths, broken commands, fake env vars, or sample DriftFence output.
|
|
131
|
+
|
|
132
|
+
````md
|
|
133
|
+
<!-- driftfence-ignore-start -->
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npm run missing-script
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
See `docs/missing.md`.
|
|
140
|
+
Set `DATABASE_URL`.
|
|
141
|
+
|
|
142
|
+
<!-- driftfence-ignore-end -->
|
|
143
|
+
````
|
|
144
|
+
|
|
145
|
+
Keep ignore blocks narrow so real setup instructions are still checked.
|
|
146
|
+
|
|
127
147
|
## Roadmap
|
|
128
148
|
|
|
129
|
-
- MDX docs
|
|
130
149
|
- GitHub Action
|
|
131
150
|
- changed-files mode
|
|
132
151
|
- configurable ignore rules
|
|
@@ -170,6 +170,30 @@ function getErrorMessage(error) {
|
|
|
170
170
|
import { access } from "fs/promises";
|
|
171
171
|
import { resolve } from "path";
|
|
172
172
|
var pathTokenPattern = /(?:^|[\s"'`(])((?:\.{1,2}[\\/][^\s"'`()<>[\]{}]+|[A-Za-z0-9_.-]+[\\/][^\s"'`()<>[\]{}]+|\.[A-Za-z0-9_-][A-Za-z0-9_.-]*|[A-Za-z0-9_-]+\.[A-Za-z][A-Za-z0-9]{1,7})(?:[?#][A-Za-z0-9_.:/#?=&-]+)?)/g;
|
|
173
|
+
var javascriptDottedIdentifierRoots = /* @__PURE__ */ new Set([
|
|
174
|
+
"Array",
|
|
175
|
+
"Boolean",
|
|
176
|
+
"Buffer",
|
|
177
|
+
"Date",
|
|
178
|
+
"JSON",
|
|
179
|
+
"Map",
|
|
180
|
+
"Math",
|
|
181
|
+
"Number",
|
|
182
|
+
"Object",
|
|
183
|
+
"Promise",
|
|
184
|
+
"Reflect",
|
|
185
|
+
"RegExp",
|
|
186
|
+
"Set",
|
|
187
|
+
"String",
|
|
188
|
+
"Symbol",
|
|
189
|
+
"WeakMap",
|
|
190
|
+
"WeakSet",
|
|
191
|
+
"console",
|
|
192
|
+
"globalThis",
|
|
193
|
+
"import",
|
|
194
|
+
"module",
|
|
195
|
+
"process"
|
|
196
|
+
]);
|
|
173
197
|
async function checkFilePaths(projectRoot, references) {
|
|
174
198
|
const pathReferences = findFilePathReferences(references);
|
|
175
199
|
const issues = [];
|
|
@@ -199,6 +223,9 @@ function findFilePathReferences(references) {
|
|
|
199
223
|
function findFilePathReferencesInText(text, markdownPath) {
|
|
200
224
|
const references = [];
|
|
201
225
|
for (const match of text.matchAll(pathTokenPattern)) {
|
|
226
|
+
if (isPartialDottedToken(text, match)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
202
229
|
const path = cleanPathCandidate(match[1]);
|
|
203
230
|
if (!isLikelyFilePath(path)) {
|
|
204
231
|
continue;
|
|
@@ -213,17 +240,83 @@ function cleanPathCandidate(candidate) {
|
|
|
213
240
|
return withoutUrlSuffix.replace(/:\d+(?::\d+)?$/g, "").replace(/^\.\//, "");
|
|
214
241
|
}
|
|
215
242
|
function isLikelyFilePath(path) {
|
|
216
|
-
if (path.length === 0 || path.startsWith("@") || path.startsWith("-") || path.includes("://") || path.includes("*") || path.includes("$")) {
|
|
243
|
+
if (path.length === 0 || path.startsWith("@") || path.startsWith("-") || path.includes("://") || path.includes("*") || path.includes("$") || isUrlOrDomainLikeReference(path)) {
|
|
217
244
|
return false;
|
|
218
245
|
}
|
|
219
246
|
if (!path.includes("/") && /^[A-Z][a-z]+(?:\.[a-z0-9]+)+$/.test(path)) {
|
|
220
247
|
return false;
|
|
221
248
|
}
|
|
249
|
+
if (isJavaScriptDottedIdentifier(path)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
222
252
|
return path.includes("/") || path.startsWith(".") || hasExtension(path);
|
|
223
253
|
}
|
|
224
254
|
function hasExtension(path) {
|
|
225
255
|
return /(^|[/\\])[^/\\]+\.[A-Za-z][A-Za-z0-9]{1,7}$/.test(path);
|
|
226
256
|
}
|
|
257
|
+
function isJavaScriptDottedIdentifier(path) {
|
|
258
|
+
if (path.startsWith(".") || path.includes("/") || path.includes("\\")) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
const parts = path.split(".");
|
|
262
|
+
return parts.length > 1 && javascriptDottedIdentifierRoots.has(parts[0] ?? "") && parts.every((part) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(part));
|
|
263
|
+
}
|
|
264
|
+
function isPartialDottedToken(text, match) {
|
|
265
|
+
const candidate = match[1] ?? "";
|
|
266
|
+
const matchText = match[0] ?? "";
|
|
267
|
+
const candidateStart = (match.index ?? 0) + Math.max(matchText.lastIndexOf(candidate), 0);
|
|
268
|
+
const candidateEnd = candidateStart + candidate.length;
|
|
269
|
+
return text[candidateEnd] === "." && /^[A-Za-z0-9_-]$/.test(text[candidateEnd + 1] ?? "");
|
|
270
|
+
}
|
|
271
|
+
function isUrlOrDomainLikeReference(path) {
|
|
272
|
+
return isUrlReference(path) || isEmailLikeReference(path) || isLocalhostReference(path) || isIpv4AddressReference(path) || isBareDomainReference(path);
|
|
273
|
+
}
|
|
274
|
+
function isUrlReference(path) {
|
|
275
|
+
return /^https?:\/\//i.test(path);
|
|
276
|
+
}
|
|
277
|
+
function isEmailLikeReference(path) {
|
|
278
|
+
return /^[^\s@/\\]+@[^\s@/\\]+\.[^\s@/\\]+$/.test(path);
|
|
279
|
+
}
|
|
280
|
+
function isLocalhostReference(path) {
|
|
281
|
+
return /^localhost(?::\d+)?(?:\/.*)?$/i.test(path);
|
|
282
|
+
}
|
|
283
|
+
function isIpv4AddressReference(path) {
|
|
284
|
+
return /^(?:\d{1,3}\.){3}\d{1,3}(?::\d+)?(?:\/.*)?$/.test(path);
|
|
285
|
+
}
|
|
286
|
+
function isBareDomainReference(path) {
|
|
287
|
+
if (path.startsWith(".") || path.includes("\\") || path.includes("@")) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const host = path.split("/")[0] ?? "";
|
|
291
|
+
return isDomainHost(host);
|
|
292
|
+
}
|
|
293
|
+
var commonDomainSuffixes = /* @__PURE__ */ new Set([
|
|
294
|
+
"ai",
|
|
295
|
+
"app",
|
|
296
|
+
"biz",
|
|
297
|
+
"co",
|
|
298
|
+
"com",
|
|
299
|
+
"dev",
|
|
300
|
+
"edu",
|
|
301
|
+
"gov",
|
|
302
|
+
"info",
|
|
303
|
+
"io",
|
|
304
|
+
"me",
|
|
305
|
+
"net",
|
|
306
|
+
"online",
|
|
307
|
+
"org",
|
|
308
|
+
"site",
|
|
309
|
+
"tv",
|
|
310
|
+
"xyz"
|
|
311
|
+
]);
|
|
312
|
+
function isDomainHost(host) {
|
|
313
|
+
const withoutPort = host.replace(/:\d+$/, "");
|
|
314
|
+
const parts = withoutPort.split(".");
|
|
315
|
+
const suffix = parts.at(-1)?.toLowerCase();
|
|
316
|
+
return parts.length > 1 && suffix !== void 0 && commonDomainSuffixes.has(suffix) && parts.every(
|
|
317
|
+
(part) => /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(part)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
227
320
|
function uniquePathReferences(references) {
|
|
228
321
|
const seen = /* @__PURE__ */ new Set();
|
|
229
322
|
const uniqueReferences = [];
|
|
@@ -471,12 +564,15 @@ async function findMarkdownPathsInDirectory(directory) {
|
|
|
471
564
|
paths.push(...await findMarkdownPathsInDirectory(path));
|
|
472
565
|
continue;
|
|
473
566
|
}
|
|
474
|
-
if (entry.isFile() && entry.name
|
|
567
|
+
if (entry.isFile() && isMarkdownDocument(entry.name)) {
|
|
475
568
|
paths.push(path);
|
|
476
569
|
}
|
|
477
570
|
}
|
|
478
571
|
return paths;
|
|
479
572
|
}
|
|
573
|
+
function isMarkdownDocument(fileName) {
|
|
574
|
+
return fileName.endsWith(".md") || fileName.endsWith(".mdx");
|
|
575
|
+
}
|
|
480
576
|
async function fileExists(path) {
|
|
481
577
|
try {
|
|
482
578
|
await readFile3(path, "utf8");
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driftfence",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"dev": "tsx src/cli.ts",
|
|
42
42
|
"pack:dry": "npm pack --dry-run",
|
|
43
43
|
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
44
|
+
"self:check": "node ./dist/cli.js check .",
|
|
44
45
|
"test": "vitest run --pool=threads",
|
|
45
46
|
"typecheck": "tsc --noEmit"
|
|
46
47
|
},
|