@facetlayer/docs-tool 0.1.0 → 0.2.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.
@@ -0,0 +1,15 @@
1
+ export interface GlobalLibraryLocation {
2
+ libraryPath: string;
3
+ libraryName: string;
4
+ }
5
+ /**
6
+ * Get the global npm prefix directory.
7
+ * Returns null if it cannot be determined.
8
+ */
9
+ export declare function getGlobalNpmPrefix(): Promise<string | null>;
10
+ /**
11
+ * Find a library in the global npm installation directory.
12
+ * This is where `npm install -g` puts packages.
13
+ */
14
+ export declare function findInGlobalNpmInstall(libraryName: string): Promise<GlobalLibraryLocation | null>;
15
+ //# sourceMappingURL=browseGlobalNpmInstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browseGlobalNpmInstall.d.ts","sourceRoot":"","sources":["../src/browseGlobalNpmInstall.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBjE;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAoCvG"}
@@ -11,6 +11,10 @@ export interface NpmLibraryDocs {
11
11
  hasReadme: boolean;
12
12
  hasDocsFolder: boolean;
13
13
  }
14
+ /**
15
+ * Check if a package exists at the given path with a package.json
16
+ */
17
+ export declare function packageExistsAt(packagePath: string): boolean;
14
18
  /**
15
19
  * Find a library by name in node_modules directories.
16
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"browseNpmLibrary.d.ts","sourceRoot":"","sources":["../src/browseNpmLibrary.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;CACxB;AAuGD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAiCvG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AA6GD;;GAEG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiC3H;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CA+BvF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAQ/H"}
1
+ {"version":3,"file":"browseNpmLibrary.d.ts","sourceRoot":"","sources":["../src/browseNpmLibrary.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE5D;AAsGD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAiCvG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AA6GD;;GAEG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA0C3H;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CA+BvF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAQ/H"}
package/dist/cli.js CHANGED
@@ -4,12 +4,12 @@
4
4
  import yargs from "yargs";
5
5
  import { hideBin } from "yargs/helpers";
6
6
  import { readFileSync as readFileSync3 } from "fs";
7
- import { join as join4, dirname as dirname2 } from "path";
7
+ import { join as join5, dirname as dirname2 } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // src/index.ts
11
11
  import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
12
- import { join as join3, basename, relative } from "path";
12
+ import { join as join4, basename, relative } from "path";
13
13
 
14
14
  // src/browseLocalLibrary.ts
15
15
  import { resolve, join } from "path";
@@ -34,24 +34,83 @@ function browseLocalLibrary(targetPath) {
34
34
  }
35
35
 
36
36
  // src/browseNpmLibrary.ts
37
- import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
38
- import { join as join2, dirname } from "path";
37
+ import { readFileSync, readdirSync, existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
38
+ import { join as join3, dirname } from "path";
39
39
  import { homedir } from "os";
40
+ import { runShellCommand as runShellCommand2 } from "@facetlayer/subprocess-wrapper";
41
+
42
+ // src/browseGlobalNpmInstall.ts
43
+ import { existsSync as existsSync2 } from "fs";
44
+ import { join as join2 } from "path";
40
45
  import { runShellCommand } from "@facetlayer/subprocess-wrapper";
46
+ var cachedGlobalPrefix = void 0;
47
+ async function getGlobalNpmPrefix() {
48
+ var _a;
49
+ if (cachedGlobalPrefix !== void 0) {
50
+ return cachedGlobalPrefix;
51
+ }
52
+ try {
53
+ const result = await runShellCommand("npm", ["prefix", "-g"]);
54
+ if (result.failed() || !result.stdout) {
55
+ cachedGlobalPrefix = null;
56
+ return null;
57
+ }
58
+ cachedGlobalPrefix = ((_a = result.stdout[0]) == null ? void 0 : _a.trim()) || null;
59
+ return cachedGlobalPrefix;
60
+ } catch {
61
+ cachedGlobalPrefix = null;
62
+ return null;
63
+ }
64
+ }
65
+ async function findInGlobalNpmInstall(libraryName) {
66
+ const globalPrefix = await getGlobalNpmPrefix();
67
+ if (!globalPrefix) {
68
+ return null;
69
+ }
70
+ const globalNodeModules = join2(globalPrefix, "lib", "node_modules");
71
+ if (!existsSync2(globalNodeModules)) {
72
+ return null;
73
+ }
74
+ if (libraryName.startsWith("@")) {
75
+ const [scope, pkgName] = libraryName.split("/");
76
+ if (pkgName) {
77
+ const fullPath2 = join2(globalNodeModules, scope, pkgName);
78
+ if (packageExistsAt(fullPath2)) {
79
+ return {
80
+ libraryPath: fullPath2,
81
+ libraryName
82
+ };
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ const fullPath = join2(globalNodeModules, libraryName);
88
+ if (packageExistsAt(fullPath)) {
89
+ return {
90
+ libraryPath: fullPath,
91
+ libraryName
92
+ };
93
+ }
94
+ return null;
95
+ }
96
+
97
+ // src/browseNpmLibrary.ts
98
+ function packageExistsAt(packagePath) {
99
+ return existsSync3(packagePath) && existsSync3(join3(packagePath, "package.json"));
100
+ }
41
101
  function findExactMatch(nodeModulesPath, libraryName) {
42
102
  if (libraryName.startsWith("@")) {
43
103
  const [scope, pkgName] = libraryName.split("/");
44
- const scopePath = join2(nodeModulesPath, scope);
45
- if (pkgName && existsSync2(scopePath)) {
46
- const fullPath2 = join2(scopePath, pkgName);
47
- if (existsSync2(fullPath2) && existsSync2(join2(fullPath2, "package.json"))) {
104
+ if (pkgName) {
105
+ const fullPath2 = join3(nodeModulesPath, scope, pkgName);
106
+ if (packageExistsAt(fullPath2)) {
48
107
  return fullPath2;
49
108
  }
50
109
  }
51
110
  return null;
52
111
  }
53
- const fullPath = join2(nodeModulesPath, libraryName);
54
- if (existsSync2(fullPath) && existsSync2(join2(fullPath, "package.json"))) {
112
+ const fullPath = join3(nodeModulesPath, libraryName);
113
+ if (packageExistsAt(fullPath)) {
55
114
  return fullPath;
56
115
  }
57
116
  return null;
@@ -59,21 +118,21 @@ function findExactMatch(nodeModulesPath, libraryName) {
59
118
  function findPartialMatches(nodeModulesPath, partialName) {
60
119
  const matches = [];
61
120
  const lowerPartial = partialName.toLowerCase();
62
- if (!existsSync2(nodeModulesPath)) {
121
+ if (!existsSync3(nodeModulesPath)) {
63
122
  return matches;
64
123
  }
65
124
  const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
66
125
  for (const entry of entries) {
67
126
  if (!entry.isDirectory()) continue;
68
127
  if (entry.name.startsWith("@")) {
69
- const scopePath = join2(nodeModulesPath, entry.name);
128
+ const scopePath = join3(nodeModulesPath, entry.name);
70
129
  const scopedEntries = readdirSync(scopePath, { withFileTypes: true });
71
130
  for (const scopedEntry of scopedEntries) {
72
131
  if (!scopedEntry.isDirectory()) continue;
73
132
  const fullName = `${entry.name}/${scopedEntry.name}`;
74
133
  if (fullName.toLowerCase().includes(lowerPartial)) {
75
- const fullPath = join2(scopePath, scopedEntry.name);
76
- if (existsSync2(join2(fullPath, "package.json"))) {
134
+ const fullPath = join3(scopePath, scopedEntry.name);
135
+ if (existsSync3(join3(fullPath, "package.json"))) {
77
136
  matches.push({
78
137
  libraryPath: fullPath,
79
138
  libraryName: fullName,
@@ -84,8 +143,8 @@ function findPartialMatches(nodeModulesPath, partialName) {
84
143
  }
85
144
  } else {
86
145
  if (entry.name.toLowerCase().includes(lowerPartial)) {
87
- const fullPath = join2(nodeModulesPath, entry.name);
88
- if (existsSync2(join2(fullPath, "package.json"))) {
146
+ const fullPath = join3(nodeModulesPath, entry.name);
147
+ if (existsSync3(join3(fullPath, "package.json"))) {
89
148
  matches.push({
90
149
  libraryPath: fullPath,
91
150
  libraryName: entry.name,
@@ -101,8 +160,8 @@ function getNodeModulesPaths(startDir) {
101
160
  const paths = [];
102
161
  let currentDir = startDir;
103
162
  while (true) {
104
- const nodeModulesPath = join2(currentDir, "node_modules");
105
- if (existsSync2(nodeModulesPath)) {
163
+ const nodeModulesPath = join3(currentDir, "node_modules");
164
+ if (existsSync3(nodeModulesPath)) {
106
165
  paths.push(nodeModulesPath);
107
166
  }
108
167
  const parentDir = dirname(currentDir);
@@ -143,16 +202,16 @@ function findLibraryInNodeModules(libraryName, startDir) {
143
202
  return null;
144
203
  }
145
204
  function getInstallationDirectory() {
146
- const stateDir = join2(homedir(), ".cache", "docs-tool");
147
- const installDir = join2(stateDir, "installed-packages");
148
- if (!existsSync2(installDir)) {
205
+ const stateDir = join3(homedir(), ".cache", "docs-tool");
206
+ const installDir = join3(stateDir, "installed-packages");
207
+ if (!existsSync3(installDir)) {
149
208
  mkdirSync(installDir, { recursive: true });
150
209
  }
151
210
  return installDir;
152
211
  }
153
212
  function ensureInstallDirInitialized(installDir) {
154
- const packageJsonPath = join2(installDir, "package.json");
155
- if (!existsSync2(packageJsonPath)) {
213
+ const packageJsonPath = join3(installDir, "package.json");
214
+ if (!existsSync3(packageJsonPath)) {
156
215
  const packageJson2 = {
157
216
  name: "docs-tool-installed-packages",
158
217
  version: "1.0.0",
@@ -163,8 +222,8 @@ function ensureInstallDirInitialized(installDir) {
163
222
  }
164
223
  }
165
224
  function findInInstallDir(installDir, libraryName) {
166
- const nodeModulesPath = join2(installDir, "node_modules");
167
- if (!existsSync2(nodeModulesPath)) {
225
+ const nodeModulesPath = join3(installDir, "node_modules");
226
+ if (!existsSync3(nodeModulesPath)) {
168
227
  return null;
169
228
  }
170
229
  const exactPath = findExactMatch(nodeModulesPath, libraryName);
@@ -184,7 +243,7 @@ function findInInstallDir(installDir, libraryName) {
184
243
  async function getLatestVersion(libraryName) {
185
244
  var _a;
186
245
  try {
187
- const result = await runShellCommand("npm", ["view", libraryName, "version"]);
246
+ const result = await runShellCommand2("npm", ["view", libraryName, "version"]);
188
247
  if (result.failed() || !result.stdout) {
189
248
  return null;
190
249
  }
@@ -195,7 +254,7 @@ async function getLatestVersion(libraryName) {
195
254
  }
196
255
  function getInstalledVersion(libraryPath) {
197
256
  try {
198
- const packageJsonPath = join2(libraryPath, "package.json");
257
+ const packageJsonPath = join3(libraryPath, "package.json");
199
258
  const packageJson2 = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
200
259
  return packageJson2.version || null;
201
260
  } catch {
@@ -205,7 +264,7 @@ function getInstalledVersion(libraryPath) {
205
264
  async function installLibrary(installDir, libraryName) {
206
265
  ensureInstallDirInitialized(installDir);
207
266
  console.log(`Installing ${libraryName}...`);
208
- const result = await runShellCommand("npm", ["install", libraryName, "--ignore-scripts"], {
267
+ const result = await runShellCommand2("npm", ["install", libraryName, "--ignore-scripts"], {
209
268
  cwd: installDir
210
269
  });
211
270
  if (result.failed()) {
@@ -215,7 +274,7 @@ async function installLibrary(installDir, libraryName) {
215
274
  }
216
275
  async function updateLibrary(installDir, libraryName) {
217
276
  console.log(`Updating ${libraryName} to latest version...`);
218
- const result = await runShellCommand("npm", ["update", libraryName, "--ignore-scripts"], {
277
+ const result = await runShellCommand2("npm", ["update", libraryName, "--ignore-scripts"], {
219
278
  cwd: installDir
220
279
  });
221
280
  if (result.failed()) {
@@ -229,6 +288,13 @@ async function findLibrary(libraryName, options) {
229
288
  if (localResult) {
230
289
  return localResult;
231
290
  }
291
+ const globalResult = await findInGlobalNpmInstall(libraryName);
292
+ if (globalResult) {
293
+ return {
294
+ ...globalResult,
295
+ matchType: "exact"
296
+ };
297
+ }
232
298
  if (options == null ? void 0 : options.skipInstall) {
233
299
  return null;
234
300
  }
@@ -250,10 +316,10 @@ async function findLibrary(libraryName, options) {
250
316
  function getLibraryDocs(libraryPath, libraryName) {
251
317
  const dirs = [];
252
318
  const files = [];
253
- const readmePath = join2(libraryPath, "README.md");
254
- const docsPath = join2(libraryPath, "docs");
255
- const hasReadme = existsSync2(readmePath);
256
- const hasDocsFolder = existsSync2(docsPath);
319
+ const readmePath = join3(libraryPath, "README.md");
320
+ const docsPath = join3(libraryPath, "docs");
321
+ const hasReadme = existsSync3(readmePath);
322
+ const hasDocsFolder = existsSync3(docsPath);
257
323
  if (hasReadme) {
258
324
  files.push(readmePath);
259
325
  }
@@ -314,7 +380,7 @@ var DocFilesHelper = class {
314
380
  const files = readdirSync2(dir);
315
381
  for (const file of files) {
316
382
  if (!file.endsWith(".md")) continue;
317
- this.fileMap.set(file, join3(dir, file));
383
+ this.fileMap.set(file, join4(dir, file));
318
384
  }
319
385
  }
320
386
  }
@@ -472,7 +538,7 @@ function parseTarget(target) {
472
538
  var __filename = fileURLToPath(import.meta.url);
473
539
  var __dirname = dirname2(__filename);
474
540
  var packageJson = JSON.parse(
475
- readFileSync3(join4(__dirname, "../package.json"), "utf-8")
541
+ readFileSync3(join5(__dirname, "../package.json"), "utf-8")
476
542
  );
477
543
  async function main() {
478
544
  await yargs(hideBin(process.argv)).command(
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
3
- import { join as join3, basename, relative } from "path";
3
+ import { join as join4, basename, relative } from "path";
4
4
 
5
5
  // src/browseLocalLibrary.ts
6
6
  import { resolve, join } from "path";
@@ -25,24 +25,83 @@ function browseLocalLibrary(targetPath) {
25
25
  }
26
26
 
27
27
  // src/browseNpmLibrary.ts
28
- import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
29
- import { join as join2, dirname } from "path";
28
+ import { readFileSync, readdirSync, existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
29
+ import { join as join3, dirname } from "path";
30
30
  import { homedir } from "os";
31
+ import { runShellCommand as runShellCommand2 } from "@facetlayer/subprocess-wrapper";
32
+
33
+ // src/browseGlobalNpmInstall.ts
34
+ import { existsSync as existsSync2 } from "fs";
35
+ import { join as join2 } from "path";
31
36
  import { runShellCommand } from "@facetlayer/subprocess-wrapper";
37
+ var cachedGlobalPrefix = void 0;
38
+ async function getGlobalNpmPrefix() {
39
+ var _a;
40
+ if (cachedGlobalPrefix !== void 0) {
41
+ return cachedGlobalPrefix;
42
+ }
43
+ try {
44
+ const result = await runShellCommand("npm", ["prefix", "-g"]);
45
+ if (result.failed() || !result.stdout) {
46
+ cachedGlobalPrefix = null;
47
+ return null;
48
+ }
49
+ cachedGlobalPrefix = ((_a = result.stdout[0]) == null ? void 0 : _a.trim()) || null;
50
+ return cachedGlobalPrefix;
51
+ } catch {
52
+ cachedGlobalPrefix = null;
53
+ return null;
54
+ }
55
+ }
56
+ async function findInGlobalNpmInstall(libraryName) {
57
+ const globalPrefix = await getGlobalNpmPrefix();
58
+ if (!globalPrefix) {
59
+ return null;
60
+ }
61
+ const globalNodeModules = join2(globalPrefix, "lib", "node_modules");
62
+ if (!existsSync2(globalNodeModules)) {
63
+ return null;
64
+ }
65
+ if (libraryName.startsWith("@")) {
66
+ const [scope, pkgName] = libraryName.split("/");
67
+ if (pkgName) {
68
+ const fullPath2 = join2(globalNodeModules, scope, pkgName);
69
+ if (packageExistsAt(fullPath2)) {
70
+ return {
71
+ libraryPath: fullPath2,
72
+ libraryName
73
+ };
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ const fullPath = join2(globalNodeModules, libraryName);
79
+ if (packageExistsAt(fullPath)) {
80
+ return {
81
+ libraryPath: fullPath,
82
+ libraryName
83
+ };
84
+ }
85
+ return null;
86
+ }
87
+
88
+ // src/browseNpmLibrary.ts
89
+ function packageExistsAt(packagePath) {
90
+ return existsSync3(packagePath) && existsSync3(join3(packagePath, "package.json"));
91
+ }
32
92
  function findExactMatch(nodeModulesPath, libraryName) {
33
93
  if (libraryName.startsWith("@")) {
34
94
  const [scope, pkgName] = libraryName.split("/");
35
- const scopePath = join2(nodeModulesPath, scope);
36
- if (pkgName && existsSync2(scopePath)) {
37
- const fullPath2 = join2(scopePath, pkgName);
38
- if (existsSync2(fullPath2) && existsSync2(join2(fullPath2, "package.json"))) {
95
+ if (pkgName) {
96
+ const fullPath2 = join3(nodeModulesPath, scope, pkgName);
97
+ if (packageExistsAt(fullPath2)) {
39
98
  return fullPath2;
40
99
  }
41
100
  }
42
101
  return null;
43
102
  }
44
- const fullPath = join2(nodeModulesPath, libraryName);
45
- if (existsSync2(fullPath) && existsSync2(join2(fullPath, "package.json"))) {
103
+ const fullPath = join3(nodeModulesPath, libraryName);
104
+ if (packageExistsAt(fullPath)) {
46
105
  return fullPath;
47
106
  }
48
107
  return null;
@@ -50,21 +109,21 @@ function findExactMatch(nodeModulesPath, libraryName) {
50
109
  function findPartialMatches(nodeModulesPath, partialName) {
51
110
  const matches = [];
52
111
  const lowerPartial = partialName.toLowerCase();
53
- if (!existsSync2(nodeModulesPath)) {
112
+ if (!existsSync3(nodeModulesPath)) {
54
113
  return matches;
55
114
  }
56
115
  const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
57
116
  for (const entry of entries) {
58
117
  if (!entry.isDirectory()) continue;
59
118
  if (entry.name.startsWith("@")) {
60
- const scopePath = join2(nodeModulesPath, entry.name);
119
+ const scopePath = join3(nodeModulesPath, entry.name);
61
120
  const scopedEntries = readdirSync(scopePath, { withFileTypes: true });
62
121
  for (const scopedEntry of scopedEntries) {
63
122
  if (!scopedEntry.isDirectory()) continue;
64
123
  const fullName = `${entry.name}/${scopedEntry.name}`;
65
124
  if (fullName.toLowerCase().includes(lowerPartial)) {
66
- const fullPath = join2(scopePath, scopedEntry.name);
67
- if (existsSync2(join2(fullPath, "package.json"))) {
125
+ const fullPath = join3(scopePath, scopedEntry.name);
126
+ if (existsSync3(join3(fullPath, "package.json"))) {
68
127
  matches.push({
69
128
  libraryPath: fullPath,
70
129
  libraryName: fullName,
@@ -75,8 +134,8 @@ function findPartialMatches(nodeModulesPath, partialName) {
75
134
  }
76
135
  } else {
77
136
  if (entry.name.toLowerCase().includes(lowerPartial)) {
78
- const fullPath = join2(nodeModulesPath, entry.name);
79
- if (existsSync2(join2(fullPath, "package.json"))) {
137
+ const fullPath = join3(nodeModulesPath, entry.name);
138
+ if (existsSync3(join3(fullPath, "package.json"))) {
80
139
  matches.push({
81
140
  libraryPath: fullPath,
82
141
  libraryName: entry.name,
@@ -92,8 +151,8 @@ function getNodeModulesPaths(startDir) {
92
151
  const paths = [];
93
152
  let currentDir = startDir;
94
153
  while (true) {
95
- const nodeModulesPath = join2(currentDir, "node_modules");
96
- if (existsSync2(nodeModulesPath)) {
154
+ const nodeModulesPath = join3(currentDir, "node_modules");
155
+ if (existsSync3(nodeModulesPath)) {
97
156
  paths.push(nodeModulesPath);
98
157
  }
99
158
  const parentDir = dirname(currentDir);
@@ -134,16 +193,16 @@ function findLibraryInNodeModules(libraryName, startDir) {
134
193
  return null;
135
194
  }
136
195
  function getInstallationDirectory() {
137
- const stateDir = join2(homedir(), ".cache", "docs-tool");
138
- const installDir = join2(stateDir, "installed-packages");
139
- if (!existsSync2(installDir)) {
196
+ const stateDir = join3(homedir(), ".cache", "docs-tool");
197
+ const installDir = join3(stateDir, "installed-packages");
198
+ if (!existsSync3(installDir)) {
140
199
  mkdirSync(installDir, { recursive: true });
141
200
  }
142
201
  return installDir;
143
202
  }
144
203
  function ensureInstallDirInitialized(installDir) {
145
- const packageJsonPath = join2(installDir, "package.json");
146
- if (!existsSync2(packageJsonPath)) {
204
+ const packageJsonPath = join3(installDir, "package.json");
205
+ if (!existsSync3(packageJsonPath)) {
147
206
  const packageJson = {
148
207
  name: "docs-tool-installed-packages",
149
208
  version: "1.0.0",
@@ -154,8 +213,8 @@ function ensureInstallDirInitialized(installDir) {
154
213
  }
155
214
  }
156
215
  function findInInstallDir(installDir, libraryName) {
157
- const nodeModulesPath = join2(installDir, "node_modules");
158
- if (!existsSync2(nodeModulesPath)) {
216
+ const nodeModulesPath = join3(installDir, "node_modules");
217
+ if (!existsSync3(nodeModulesPath)) {
159
218
  return null;
160
219
  }
161
220
  const exactPath = findExactMatch(nodeModulesPath, libraryName);
@@ -175,7 +234,7 @@ function findInInstallDir(installDir, libraryName) {
175
234
  async function getLatestVersion(libraryName) {
176
235
  var _a;
177
236
  try {
178
- const result = await runShellCommand("npm", ["view", libraryName, "version"]);
237
+ const result = await runShellCommand2("npm", ["view", libraryName, "version"]);
179
238
  if (result.failed() || !result.stdout) {
180
239
  return null;
181
240
  }
@@ -186,7 +245,7 @@ async function getLatestVersion(libraryName) {
186
245
  }
187
246
  function getInstalledVersion(libraryPath) {
188
247
  try {
189
- const packageJsonPath = join2(libraryPath, "package.json");
248
+ const packageJsonPath = join3(libraryPath, "package.json");
190
249
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
191
250
  return packageJson.version || null;
192
251
  } catch {
@@ -196,7 +255,7 @@ function getInstalledVersion(libraryPath) {
196
255
  async function installLibrary(installDir, libraryName) {
197
256
  ensureInstallDirInitialized(installDir);
198
257
  console.log(`Installing ${libraryName}...`);
199
- const result = await runShellCommand("npm", ["install", libraryName, "--ignore-scripts"], {
258
+ const result = await runShellCommand2("npm", ["install", libraryName, "--ignore-scripts"], {
200
259
  cwd: installDir
201
260
  });
202
261
  if (result.failed()) {
@@ -206,7 +265,7 @@ async function installLibrary(installDir, libraryName) {
206
265
  }
207
266
  async function updateLibrary(installDir, libraryName) {
208
267
  console.log(`Updating ${libraryName} to latest version...`);
209
- const result = await runShellCommand("npm", ["update", libraryName, "--ignore-scripts"], {
268
+ const result = await runShellCommand2("npm", ["update", libraryName, "--ignore-scripts"], {
210
269
  cwd: installDir
211
270
  });
212
271
  if (result.failed()) {
@@ -220,6 +279,13 @@ async function findLibrary(libraryName, options) {
220
279
  if (localResult) {
221
280
  return localResult;
222
281
  }
282
+ const globalResult = await findInGlobalNpmInstall(libraryName);
283
+ if (globalResult) {
284
+ return {
285
+ ...globalResult,
286
+ matchType: "exact"
287
+ };
288
+ }
223
289
  if (options == null ? void 0 : options.skipInstall) {
224
290
  return null;
225
291
  }
@@ -241,10 +307,10 @@ async function findLibrary(libraryName, options) {
241
307
  function getLibraryDocs(libraryPath, libraryName) {
242
308
  const dirs = [];
243
309
  const files = [];
244
- const readmePath = join2(libraryPath, "README.md");
245
- const docsPath = join2(libraryPath, "docs");
246
- const hasReadme = existsSync2(readmePath);
247
- const hasDocsFolder = existsSync2(docsPath);
310
+ const readmePath = join3(libraryPath, "README.md");
311
+ const docsPath = join3(libraryPath, "docs");
312
+ const hasReadme = existsSync3(readmePath);
313
+ const hasDocsFolder = existsSync3(docsPath);
248
314
  if (hasReadme) {
249
315
  files.push(readmePath);
250
316
  }
@@ -305,7 +371,7 @@ var DocFilesHelper = class {
305
371
  const files = readdirSync2(dir);
306
372
  for (const file of files) {
307
373
  if (!file.endsWith(".md")) continue;
308
- this.fileMap.set(file, join3(dir, file));
374
+ this.fileMap.set(file, join4(dir, file));
309
375
  }
310
376
  }
311
377
  }
@@ -0,0 +1,27 @@
1
+ # CLAUDE.md Setup
2
+
3
+ Add the following to your project's `CLAUDE.md` file to enable AI assistants to use the `docs` tool for looking up NPM library documentation.
4
+
5
+ ## Recommended Section
6
+
7
+ ```markdown
8
+ ### docs
9
+
10
+ Generic CLI tool to fetch documentation for a target NPM library. This will
11
+ search local node_modules first and will search NPM if necessary.
12
+
13
+ Syntax:
14
+
15
+ docs list <library-name> # List the documentation files for <library-name>
16
+ docs show <library-name> <file> # Show a documentation file
17
+
18
+ Example:
19
+
20
+ docs list yargs
21
+ docs show yargs README.md
22
+ ```
23
+
24
+ ## Notes
25
+
26
+ - The `docs` tool must be installed globally (`npm install -g @facetlayer/docs-tool`)
27
+ - Place this section under a heading like `## Helpful development tools` or similar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@facetlayer/docs-tool",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Library and CLI tool for browsing docs in NPM packages",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,8 @@
12
12
  "build": "node build.mts build",
13
13
  "prepublishOnly": "node build.mts validate && node build.mts build",
14
14
  "typecheck": "tsc -p .",
15
- "test": "vitest run --testTimeout=30000"
15
+ "test": "vitest run --testTimeout=30000",
16
+ "local:install": "npm run build && npm i -g ."
16
17
  },
17
18
  "keywords": [
18
19
  "doc",
@@ -0,0 +1,79 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { runShellCommand } from '@facetlayer/subprocess-wrapper';
4
+ import { packageExistsAt } from './browseNpmLibrary.ts';
5
+
6
+ export interface GlobalLibraryLocation {
7
+ libraryPath: string;
8
+ libraryName: string;
9
+ }
10
+
11
+ /**
12
+ * Cached global npm prefix to avoid repeated shell calls
13
+ */
14
+ let cachedGlobalPrefix: string | null | undefined = undefined;
15
+
16
+ /**
17
+ * Get the global npm prefix directory.
18
+ * Returns null if it cannot be determined.
19
+ */
20
+ export async function getGlobalNpmPrefix(): Promise<string | null> {
21
+ if (cachedGlobalPrefix !== undefined) {
22
+ return cachedGlobalPrefix;
23
+ }
24
+
25
+ try {
26
+ const result = await runShellCommand('npm', ['prefix', '-g']);
27
+ if (result.failed() || !result.stdout) {
28
+ cachedGlobalPrefix = null;
29
+ return null;
30
+ }
31
+ cachedGlobalPrefix = result.stdout[0]?.trim() || null;
32
+ return cachedGlobalPrefix;
33
+ } catch {
34
+ cachedGlobalPrefix = null;
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Find a library in the global npm installation directory.
41
+ * This is where `npm install -g` puts packages.
42
+ */
43
+ export async function findInGlobalNpmInstall(libraryName: string): Promise<GlobalLibraryLocation | null> {
44
+ const globalPrefix = await getGlobalNpmPrefix();
45
+ if (!globalPrefix) {
46
+ return null;
47
+ }
48
+
49
+ const globalNodeModules = join(globalPrefix, 'lib', 'node_modules');
50
+ if (!existsSync(globalNodeModules)) {
51
+ return null;
52
+ }
53
+
54
+ // Handle scoped packages like @scope/package
55
+ if (libraryName.startsWith('@')) {
56
+ const [scope, pkgName] = libraryName.split('/');
57
+ if (pkgName) {
58
+ const fullPath = join(globalNodeModules, scope, pkgName);
59
+ if (packageExistsAt(fullPath)) {
60
+ return {
61
+ libraryPath: fullPath,
62
+ libraryName: libraryName,
63
+ };
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+
69
+ // Regular package
70
+ const fullPath = join(globalNodeModules, libraryName);
71
+ if (packageExistsAt(fullPath)) {
72
+ return {
73
+ libraryPath: fullPath,
74
+ libraryName: libraryName,
75
+ };
76
+ }
77
+
78
+ return null;
79
+ }
@@ -3,6 +3,7 @@ import { join, dirname } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { runShellCommand } from '@facetlayer/subprocess-wrapper';
5
5
  import { DocFilesHelper } from './index.ts';
6
+ import { findInGlobalNpmInstall } from './browseGlobalNpmInstall.ts';
6
7
 
7
8
  export interface LibraryLocation {
8
9
  libraryPath: string;
@@ -18,6 +19,13 @@ export interface NpmLibraryDocs {
18
19
  hasDocsFolder: boolean;
19
20
  }
20
21
 
22
+ /**
23
+ * Check if a package exists at the given path with a package.json
24
+ */
25
+ export function packageExistsAt(packagePath: string): boolean {
26
+ return existsSync(packagePath) && existsSync(join(packagePath, 'package.json'));
27
+ }
28
+
21
29
  /**
22
30
  * Check if a directory contains a package that matches the given name exactly
23
31
  */
@@ -25,10 +33,9 @@ function findExactMatch(nodeModulesPath: string, libraryName: string): string |
25
33
  // Handle scoped packages like @scope/package
26
34
  if (libraryName.startsWith('@')) {
27
35
  const [scope, pkgName] = libraryName.split('/');
28
- const scopePath = join(nodeModulesPath, scope);
29
- if (pkgName && existsSync(scopePath)) {
30
- const fullPath = join(scopePath, pkgName);
31
- if (existsSync(fullPath) && existsSync(join(fullPath, 'package.json'))) {
36
+ if (pkgName) {
37
+ const fullPath = join(nodeModulesPath, scope, pkgName);
38
+ if (packageExistsAt(fullPath)) {
32
39
  return fullPath;
33
40
  }
34
41
  }
@@ -37,7 +44,7 @@ function findExactMatch(nodeModulesPath: string, libraryName: string): string |
37
44
 
38
45
  // Regular package
39
46
  const fullPath = join(nodeModulesPath, libraryName);
40
- if (existsSync(fullPath) && existsSync(join(fullPath, 'package.json'))) {
47
+ if (packageExistsAt(fullPath)) {
41
48
  return fullPath;
42
49
  }
43
50
  return null;
@@ -288,11 +295,20 @@ export async function findLibrary(libraryName: string, options?: { skipInstall?:
288
295
  return localResult;
289
296
  }
290
297
 
298
+ // Second, check global npm installation (where `npm install -g` puts packages)
299
+ const globalResult = await findInGlobalNpmInstall(libraryName);
300
+ if (globalResult) {
301
+ return {
302
+ ...globalResult,
303
+ matchType: 'exact',
304
+ };
305
+ }
306
+
291
307
  if (options?.skipInstall) {
292
308
  return null;
293
309
  }
294
310
 
295
- // Check our installation directory
311
+ // Third, check our installation directory
296
312
  const installDir = getInstallationDirectory();
297
313
  let installedResult = findInInstallDir(installDir, libraryName);
298
314