doc-detective 4.29.1 → 4.30.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/bin/doc-detective-lsp.js +6 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/common/src/validate.d.ts +16 -1
- package/dist/common/src/validate.d.ts.map +1 -1
- package/dist/common/src/validate.js +18 -1
- package/dist/common/src/validate.js.map +1 -1
- package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
- package/dist/core/tests/saveScreenshot.js +101 -59
- package/dist/core/tests/saveScreenshot.js.map +1 -1
- package/dist/index.cjs +55 -36
- package/dist/lsp/command.d.ts +12 -0
- package/dist/lsp/command.d.ts.map +1 -0
- package/dist/lsp/command.js +24 -0
- package/dist/lsp/command.js.map +1 -0
- package/dist/lsp/completion.d.ts +27 -0
- package/dist/lsp/completion.d.ts.map +1 -0
- package/dist/lsp/completion.js +114 -0
- package/dist/lsp/completion.js.map +1 -0
- package/dist/lsp/diagnostics.d.ts +21 -0
- package/dist/lsp/diagnostics.d.ts.map +1 -0
- package/dist/lsp/diagnostics.js +137 -0
- package/dist/lsp/diagnostics.js.map +1 -0
- package/dist/lsp/gate.d.ts +30 -0
- package/dist/lsp/gate.d.ts.map +1 -0
- package/dist/lsp/gate.js +80 -0
- package/dist/lsp/gate.js.map +1 -0
- package/dist/lsp/hover.d.ts +9 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/lsp/hover.js +56 -0
- package/dist/lsp/hover.js.map +1 -0
- package/dist/lsp/inline.d.ts +25 -0
- package/dist/lsp/inline.d.ts.map +1 -0
- package/dist/lsp/inline.js +208 -0
- package/dist/lsp/inline.js.map +1 -0
- package/dist/lsp/json/positions.d.ts +51 -0
- package/dist/lsp/json/positions.d.ts.map +1 -0
- package/dist/lsp/json/positions.js +101 -0
- package/dist/lsp/json/positions.js.map +1 -0
- package/dist/lsp/messages.d.ts +27 -0
- package/dist/lsp/messages.d.ts.map +1 -0
- package/dist/lsp/messages.js +33 -0
- package/dist/lsp/messages.js.map +1 -0
- package/dist/lsp/model.d.ts +27 -0
- package/dist/lsp/model.d.ts.map +1 -0
- package/dist/lsp/model.js +49 -0
- package/dist/lsp/model.js.map +1 -0
- package/dist/lsp/registry.d.ts +61 -0
- package/dist/lsp/registry.d.ts.map +1 -0
- package/dist/lsp/registry.js +124 -0
- package/dist/lsp/registry.js.map +1 -0
- package/dist/lsp/server.d.ts +40 -0
- package/dist/lsp/server.d.ts.map +1 -0
- package/dist/lsp/server.js +64 -0
- package/dist/lsp/server.js.map +1 -0
- package/dist/lsp/yaml/positions.d.ts +32 -0
- package/dist/lsp/yaml/positions.d.ts.map +1 -0
- package/dist/lsp/yaml/positions.js +92 -0
- package/dist/lsp/yaml/positions.js.map +1 -0
- package/package.json +6 -2
- package/scripts/postinstall.js +144 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
import { instancePathToSegments, pointerFromPath } from "../json/positions.js";
|
|
3
|
+
/** Parse YAML into a position-preserving document plus its plain JS value. */
|
|
4
|
+
export function parseYamlTree(text) {
|
|
5
|
+
const doc = YAML.parseDocument(text, { keepSourceTokens: true });
|
|
6
|
+
// `toJS()` resolves aliases and can throw on a pathological document (the
|
|
7
|
+
// "billion laughs" alias bomb the yaml lib guards with maxAliasCount). Catch
|
|
8
|
+
// it so a malicious/degenerate buffer can't crash the server — surface it as
|
|
9
|
+
// a document-level error instead.
|
|
10
|
+
try {
|
|
11
|
+
return { doc, value: doc.toJS() };
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
/* c8 ignore next - the yaml lib always throws an Error with a message; the String() fallback guards a hypothetical non-Error throw */
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
return { doc, value: undefined, valueError: message };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Collect YAML syntax errors as offset spans (empty when the doc is clean). */
|
|
20
|
+
export function yamlSyntaxErrors(doc) {
|
|
21
|
+
return doc.errors.map((error) => {
|
|
22
|
+
const [start, end] = error.pos;
|
|
23
|
+
return { range: { start, end }, message: error.message };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Node range → OffsetRange, using [start, valueEnd] for a tight span. */
|
|
27
|
+
function nodeRange(node) {
|
|
28
|
+
const range = node?.range;
|
|
29
|
+
if (!Array.isArray(range))
|
|
30
|
+
return null;
|
|
31
|
+
return { start: range[0], end: range[1] };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the source span for an AJV `instancePath` in a YAML document. Walks
|
|
35
|
+
* up to the nearest present ancestor (like the JSON resolver) so a
|
|
36
|
+
* `required`-style error still anchors somewhere. Returns null only when even
|
|
37
|
+
* the document contents can't be located.
|
|
38
|
+
*/
|
|
39
|
+
export function rangeForInstancePathYaml(doc, instancePath) {
|
|
40
|
+
const segments = instancePathToSegments(instancePath);
|
|
41
|
+
for (let end = segments.length; end >= 0; end--) {
|
|
42
|
+
const node = end === 0 ? doc.contents : doc.getIn(segments.slice(0, end), true);
|
|
43
|
+
const range = nodeRange(node);
|
|
44
|
+
if (range)
|
|
45
|
+
return range;
|
|
46
|
+
}
|
|
47
|
+
// Reached only for an empty document (no contents node has a range).
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Find every step written as a map carrying an `action` key inside a `steps`
|
|
52
|
+
* sequence — the YAML twin of the JSON detector. Returns the offending `action`
|
|
53
|
+
* key span and the step's JSON-Pointer.
|
|
54
|
+
*/
|
|
55
|
+
export function findActionKeyedStepsYaml(doc) {
|
|
56
|
+
const found = [];
|
|
57
|
+
const visit = (node, path) => {
|
|
58
|
+
if (YAML.isMap(node)) {
|
|
59
|
+
for (const pair of node.items) {
|
|
60
|
+
const key = pair.key?.value;
|
|
61
|
+
if (key === "steps" && YAML.isSeq(pair.value)) {
|
|
62
|
+
collectSteps(pair.value, [...path, "steps"]);
|
|
63
|
+
}
|
|
64
|
+
if (pair.value)
|
|
65
|
+
visit(pair.value, [...path, key]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (YAML.isSeq(node)) {
|
|
69
|
+
node.items.forEach((item, index) => visit(item, [...path, index]));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const collectSteps = (seq, seqPath) => {
|
|
73
|
+
seq.items.forEach((item, index) => {
|
|
74
|
+
if (!YAML.isMap(item))
|
|
75
|
+
return;
|
|
76
|
+
const actionPair = item.items.find((p) => p.key?.value === "action");
|
|
77
|
+
if (actionPair) {
|
|
78
|
+
const range = nodeRange(actionPair.key);
|
|
79
|
+
if (range) {
|
|
80
|
+
found.push({
|
|
81
|
+
keyRange: range,
|
|
82
|
+
pointer: pointerFromPath([...seqPath, index]),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
if (doc.contents)
|
|
89
|
+
visit(doc.contents, []);
|
|
90
|
+
return found;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=positions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"positions.js","sourceRoot":"","sources":["../../../src/lsp/yaml/positions.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAU/E,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,kCAAkC;IAClC,IAAI,CAAC;QACH,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IACpC,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,sIAAsI;QACtI,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IACxD,CAAC;AACH,CAAC;AAQD,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAAC,GAAyB;IACxD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,SAAS,SAAS,CAAC,IAAS;IAC1B,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACtC,GAAyB,EACzB,YAAoB;IAEpB,MAAM,QAAQ,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;IACtD,KAAK,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC;QAChD,MAAM,IAAI,GACR,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,qEAAqE;IACrE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,GAAyB;IAEzB,MAAM,KAAK,GAAsB,EAAE,CAAC;IAEpC,MAAM,KAAK,GAAG,CAAC,IAAS,EAAE,IAA4B,EAAQ,EAAE;QAC9D,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAc,EAAE,CAAC;gBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC;gBAC5B,IAAI,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC9C,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;gBAC/C,CAAC;gBACD,IAAI,IAAI,CAAC,KAAK;oBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAS,EAAE,KAAa,EAAE,EAAE,CAC9C,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC,CAC9B,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,GAAQ,EAAE,OAA+B,EAAQ,EAAE;QACvE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAS,EAAE,KAAa,EAAE,EAAE;YAC7C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC9B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC;YAC1E,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;gBACxC,IAAI,KAAK,EAAE,CAAC;oBACV,KAAK,CAAC,IAAI,CAAC;wBACT,QAAQ,EAAE,KAAK;wBACf,OAAO,EAAE,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC;qBAC9C,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,GAAG,CAAC,QAAQ;QAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doc-detective",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.30.0",
|
|
4
4
|
"description": "Treat doc content as testable assertions to validate doc accuracy and product UX.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"doc-detective": "bin/doc-detective.js",
|
|
7
|
-
"doc-detective-runner": "bin/runner-entrypoint.js"
|
|
7
|
+
"doc-detective-runner": "bin/runner-entrypoint.js",
|
|
8
|
+
"doc-detective-lsp": "bin/doc-detective-lsp.js"
|
|
8
9
|
},
|
|
9
10
|
"type": "module",
|
|
10
11
|
"engines": {
|
|
@@ -86,8 +87,11 @@
|
|
|
86
87
|
"fast-xml-parser": "^5.9.3",
|
|
87
88
|
"jq-web": "^0.6.2",
|
|
88
89
|
"json-schema-faker": "^0.6.2",
|
|
90
|
+
"jsonc-parser": "^3.3.1",
|
|
89
91
|
"posthog-node": "^5.38.6",
|
|
90
92
|
"tree-kill": "^1.2.2",
|
|
93
|
+
"vscode-languageserver": "^9.0.1",
|
|
94
|
+
"vscode-languageserver-textdocument": "^1.0.12",
|
|
91
95
|
"yaml": "^2.9.0",
|
|
92
96
|
"yargs": "^18.0.0"
|
|
93
97
|
},
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
3
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -16,11 +16,154 @@ const CLI_PATH = path.join(__dirname, "..", "bin", "doc-detective.js");
|
|
|
16
16
|
// DOC_DETECTIVE_AUTOINSTALL=0.
|
|
17
17
|
// 2. maybePromptInstallAgents(): the optional agent-tools install prompt —
|
|
18
18
|
// lightweight (TTY-gated, time-bounded, skipped in CI).
|
|
19
|
+
//
|
|
20
|
+
// Both are skipped for a first-time install triggered solely to run the
|
|
21
|
+
// language server (`npx doc-detective lsp`): the LSP needs none of the heavy
|
|
22
|
+
// runtime, so pre-warming browsers/drivers there is pure waste. The runtime
|
|
23
|
+
// still lazy-installs on the first actual test run if one ever happens.
|
|
19
24
|
async function main() {
|
|
25
|
+
if (isLspInvocation()) return;
|
|
20
26
|
await maybeInstallRuntime();
|
|
21
27
|
await maybePromptInstallAgents();
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
// --- LSP-invocation detection -----------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Does this command line invoke `doc-detective lsp`? Matches the package name
|
|
34
|
+
* plus a standalone `lsp` argument, covering `npx doc-detective lsp`,
|
|
35
|
+
* `npx --yes doc-detective lsp --stdio`, and `node …/bin/doc-detective.js lsp`.
|
|
36
|
+
* The `\blsp\b`-as-a-word check avoids false hits like a `lsp-…` path segment.
|
|
37
|
+
* Exported for tests.
|
|
38
|
+
*/
|
|
39
|
+
export function isDocDetectiveLspCommand(cmdline) {
|
|
40
|
+
if (typeof cmdline !== "string") return false;
|
|
41
|
+
return /doc-detective/i.test(cmdline) && /(^|\s)lsp(\s|$)/.test(cmdline);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read the process table (pid → { ppid, cmd }) for the current platform. On
|
|
46
|
+
* Linux this reads `/proc` directly (no child process); elsewhere it makes a
|
|
47
|
+
* single `powershell`/`ps` call. Best-effort — the caller wraps it in a guard.
|
|
48
|
+
* `deps.readProcessTable` overrides the real reader for tests.
|
|
49
|
+
*/
|
|
50
|
+
function buildProcessTable(platform, deps = {}) {
|
|
51
|
+
if (deps.readProcessTable) return deps.readProcessTable();
|
|
52
|
+
|
|
53
|
+
if (platform === "win32") {
|
|
54
|
+
const json = execFileSync(
|
|
55
|
+
"powershell",
|
|
56
|
+
[
|
|
57
|
+
"-NoProfile",
|
|
58
|
+
"-NonInteractive",
|
|
59
|
+
"-Command",
|
|
60
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine | ConvertTo-Json -Compress",
|
|
61
|
+
],
|
|
62
|
+
{
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
// A busy host's full process list (with command lines) can be large;
|
|
66
|
+
// raise the buffer well past the 1 MB default so the read doesn't abort.
|
|
67
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
68
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
const parsed = JSON.parse(json);
|
|
72
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
73
|
+
const table = new Map();
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
table.set(Number(row.ProcessId), {
|
|
76
|
+
ppid: Number(row.ParentProcessId),
|
|
77
|
+
cmd: row.CommandLine || "",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return table;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (platform === "linux") {
|
|
84
|
+
const table = new Map();
|
|
85
|
+
for (const name of fs.readdirSync("/proc")) {
|
|
86
|
+
if (!/^\d+$/.test(name)) continue;
|
|
87
|
+
try {
|
|
88
|
+
// `/proc/<pid>/stat`: the ppid is the 2nd field AFTER the `(comm)`
|
|
89
|
+
// parenthesized name, which itself can contain spaces/parens.
|
|
90
|
+
const stat = fs.readFileSync(`/proc/${name}/stat`, "utf8");
|
|
91
|
+
const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
92
|
+
const ppid = Number(afterComm[1]);
|
|
93
|
+
let cmd = "";
|
|
94
|
+
try {
|
|
95
|
+
cmd = fs
|
|
96
|
+
.readFileSync(`/proc/${name}/cmdline`)
|
|
97
|
+
.toString()
|
|
98
|
+
.replace(/\0/g, " ")
|
|
99
|
+
.trim();
|
|
100
|
+
} catch {
|
|
101
|
+
/* process vanished / no cmdline (kernel thread) */
|
|
102
|
+
}
|
|
103
|
+
table.set(Number(name), { ppid, cmd });
|
|
104
|
+
} catch {
|
|
105
|
+
/* process vanished between readdir and read */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return table;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// macOS and other Unixes. `-ww` disables ps's default command-line
|
|
112
|
+
// truncation (otherwise a long path can cut off the `lsp` token before the
|
|
113
|
+
// matcher ever sees it); maxBuffer guards a busy host's large process list.
|
|
114
|
+
const out = execFileSync("ps", ["-A", "-ww", "-o", "pid=,ppid=,command="], {
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
timeout: 5000,
|
|
117
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
118
|
+
});
|
|
119
|
+
const table = new Map();
|
|
120
|
+
for (const line of out.split(/\r?\n/)) {
|
|
121
|
+
const m = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
122
|
+
if (m) table.set(Number(m[1]), { ppid: Number(m[2]), cmd: m[3] });
|
|
123
|
+
}
|
|
124
|
+
return table;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Walk the ancestry from `deps.pid` (default this process) up the parent chain
|
|
129
|
+
* and return each ancestor's command line. Bounded and cycle-guarded. Never
|
|
130
|
+
* throws — returns `[]` on any failure so callers fall back to default
|
|
131
|
+
* behavior. Exported for tests.
|
|
132
|
+
*/
|
|
133
|
+
export function readAncestorCommandLines(deps = {}) {
|
|
134
|
+
const platform = deps.platform || process.platform;
|
|
135
|
+
let pid = deps.pid || process.pid;
|
|
136
|
+
try {
|
|
137
|
+
const table = buildProcessTable(platform, deps);
|
|
138
|
+
const lines = [];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
for (let hops = 0; hops < 12 && pid && !seen.has(pid); hops++) {
|
|
141
|
+
seen.add(pid);
|
|
142
|
+
const entry = table.get(pid);
|
|
143
|
+
if (!entry) break;
|
|
144
|
+
if (entry.cmd) lines.push(entry.cmd);
|
|
145
|
+
pid = entry.ppid;
|
|
146
|
+
}
|
|
147
|
+
return lines;
|
|
148
|
+
} catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Is this postinstall running as part of `npx doc-detective lsp`? Only an
|
|
155
|
+
* `npm exec` (npx) invocation can be an lsp-first-use, so a plain
|
|
156
|
+
* `npm install` / Docker build returns `false` immediately without walking the
|
|
157
|
+
* process tree (they always pre-warm). For npx, inspect the ancestry for a
|
|
158
|
+
* `doc-detective lsp` command. `ancestorLines` is injectable for tests.
|
|
159
|
+
* Exported for tests.
|
|
160
|
+
*/
|
|
161
|
+
export function isLspInvocation(env = process.env, ancestorLines) {
|
|
162
|
+
if (env.npm_command !== "exec") return false;
|
|
163
|
+
const lines = ancestorLines || readAncestorCommandLines();
|
|
164
|
+
return lines.some(isDocDetectiveLspCommand);
|
|
165
|
+
}
|
|
166
|
+
|
|
24
167
|
// Only run the install steps when executed as the npm lifecycle script, not
|
|
25
168
|
// when a test imports this module for the exported pure helpers below.
|
|
26
169
|
function isInvokedDirectly() {
|