@facetlayer/docs-tool 0.1.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/README.md +80 -0
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/browseLocalLibrary.d.ts +12 -0
- package/dist/browseLocalLibrary.d.ts.map +1 -0
- package/dist/browseNpmLibrary.d.ts +39 -0
- package/dist/browseNpmLibrary.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +549 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +471 -0
- package/docs/project-setup.md +188 -0
- package/docs/writing-doc-files.md +156 -0
- package/package.json +44 -0
- package/src/__tests__/cli.test.ts +80 -0
- package/src/__tests__/index.test.ts +333 -0
- package/src/browseLocalLibrary.ts +35 -0
- package/src/browseNpmLibrary.ts +367 -0
- package/src/cli.ts +105 -0
- package/src/index.ts +288 -0
- package/tsconfig.json +21 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { hideBin } from "yargs/helpers";
|
|
6
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
7
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
12
|
+
import { join as join3, basename, relative } from "path";
|
|
13
|
+
|
|
14
|
+
// src/browseLocalLibrary.ts
|
|
15
|
+
import { resolve, join } from "path";
|
|
16
|
+
import { existsSync } from "fs";
|
|
17
|
+
function browseLocalLibrary(targetPath) {
|
|
18
|
+
const resolvedPath = resolve(targetPath);
|
|
19
|
+
const docsPath = join(resolvedPath, "docs");
|
|
20
|
+
const hasDocsFolder = existsSync(docsPath);
|
|
21
|
+
const dirs = [resolvedPath];
|
|
22
|
+
if (hasDocsFolder) {
|
|
23
|
+
dirs.push(docsPath);
|
|
24
|
+
}
|
|
25
|
+
const helper = new DocFilesHelper({
|
|
26
|
+
dirs,
|
|
27
|
+
overrideGetSubcommand: `show ${targetPath}`
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
libraryPath: resolvedPath,
|
|
31
|
+
helper,
|
|
32
|
+
hasDocsFolder
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/browseNpmLibrary.ts
|
|
37
|
+
import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
38
|
+
import { join as join2, dirname } from "path";
|
|
39
|
+
import { homedir } from "os";
|
|
40
|
+
import { runShellCommand } from "@facetlayer/subprocess-wrapper";
|
|
41
|
+
function findExactMatch(nodeModulesPath, libraryName) {
|
|
42
|
+
if (libraryName.startsWith("@")) {
|
|
43
|
+
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"))) {
|
|
48
|
+
return fullPath2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const fullPath = join2(nodeModulesPath, libraryName);
|
|
54
|
+
if (existsSync2(fullPath) && existsSync2(join2(fullPath, "package.json"))) {
|
|
55
|
+
return fullPath;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function findPartialMatches(nodeModulesPath, partialName) {
|
|
60
|
+
const matches = [];
|
|
61
|
+
const lowerPartial = partialName.toLowerCase();
|
|
62
|
+
if (!existsSync2(nodeModulesPath)) {
|
|
63
|
+
return matches;
|
|
64
|
+
}
|
|
65
|
+
const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (!entry.isDirectory()) continue;
|
|
68
|
+
if (entry.name.startsWith("@")) {
|
|
69
|
+
const scopePath = join2(nodeModulesPath, entry.name);
|
|
70
|
+
const scopedEntries = readdirSync(scopePath, { withFileTypes: true });
|
|
71
|
+
for (const scopedEntry of scopedEntries) {
|
|
72
|
+
if (!scopedEntry.isDirectory()) continue;
|
|
73
|
+
const fullName = `${entry.name}/${scopedEntry.name}`;
|
|
74
|
+
if (fullName.toLowerCase().includes(lowerPartial)) {
|
|
75
|
+
const fullPath = join2(scopePath, scopedEntry.name);
|
|
76
|
+
if (existsSync2(join2(fullPath, "package.json"))) {
|
|
77
|
+
matches.push({
|
|
78
|
+
libraryPath: fullPath,
|
|
79
|
+
libraryName: fullName,
|
|
80
|
+
matchType: "partial"
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
if (entry.name.toLowerCase().includes(lowerPartial)) {
|
|
87
|
+
const fullPath = join2(nodeModulesPath, entry.name);
|
|
88
|
+
if (existsSync2(join2(fullPath, "package.json"))) {
|
|
89
|
+
matches.push({
|
|
90
|
+
libraryPath: fullPath,
|
|
91
|
+
libraryName: entry.name,
|
|
92
|
+
matchType: "partial"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return matches;
|
|
99
|
+
}
|
|
100
|
+
function getNodeModulesPaths(startDir) {
|
|
101
|
+
const paths = [];
|
|
102
|
+
let currentDir = startDir;
|
|
103
|
+
while (true) {
|
|
104
|
+
const nodeModulesPath = join2(currentDir, "node_modules");
|
|
105
|
+
if (existsSync2(nodeModulesPath)) {
|
|
106
|
+
paths.push(nodeModulesPath);
|
|
107
|
+
}
|
|
108
|
+
const parentDir = dirname(currentDir);
|
|
109
|
+
if (parentDir === currentDir) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
currentDir = parentDir;
|
|
113
|
+
}
|
|
114
|
+
return paths;
|
|
115
|
+
}
|
|
116
|
+
function findLibraryInNodeModules(libraryName, startDir) {
|
|
117
|
+
const cwd = startDir || process.cwd();
|
|
118
|
+
const nodeModulesPaths = getNodeModulesPaths(cwd);
|
|
119
|
+
for (const nodeModulesPath of nodeModulesPaths) {
|
|
120
|
+
const exactPath = findExactMatch(nodeModulesPath, libraryName);
|
|
121
|
+
if (exactPath) {
|
|
122
|
+
return {
|
|
123
|
+
libraryPath: exactPath,
|
|
124
|
+
libraryName,
|
|
125
|
+
matchType: "exact"
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const nodeModulesPath of nodeModulesPaths) {
|
|
130
|
+
const partialMatches = findPartialMatches(nodeModulesPath, libraryName);
|
|
131
|
+
if (partialMatches.length === 1) {
|
|
132
|
+
return partialMatches[0];
|
|
133
|
+
}
|
|
134
|
+
if (partialMatches.length > 1) {
|
|
135
|
+
console.warn(`Multiple partial matches found for "${libraryName}":`);
|
|
136
|
+
for (const match of partialMatches) {
|
|
137
|
+
console.warn(` - ${match.libraryName}`);
|
|
138
|
+
}
|
|
139
|
+
console.warn(`Using: ${partialMatches[0].libraryName}`);
|
|
140
|
+
return partialMatches[0];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function getInstallationDirectory() {
|
|
146
|
+
const stateDir = join2(homedir(), ".cache", "docs-tool");
|
|
147
|
+
const installDir = join2(stateDir, "installed-packages");
|
|
148
|
+
if (!existsSync2(installDir)) {
|
|
149
|
+
mkdirSync(installDir, { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
return installDir;
|
|
152
|
+
}
|
|
153
|
+
function ensureInstallDirInitialized(installDir) {
|
|
154
|
+
const packageJsonPath = join2(installDir, "package.json");
|
|
155
|
+
if (!existsSync2(packageJsonPath)) {
|
|
156
|
+
const packageJson2 = {
|
|
157
|
+
name: "docs-tool-installed-packages",
|
|
158
|
+
version: "1.0.0",
|
|
159
|
+
private: true,
|
|
160
|
+
description: "Packages installed by docs-tool for documentation viewing"
|
|
161
|
+
};
|
|
162
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson2, null, 2));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function findInInstallDir(installDir, libraryName) {
|
|
166
|
+
const nodeModulesPath = join2(installDir, "node_modules");
|
|
167
|
+
if (!existsSync2(nodeModulesPath)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const exactPath = findExactMatch(nodeModulesPath, libraryName);
|
|
171
|
+
if (exactPath) {
|
|
172
|
+
return {
|
|
173
|
+
libraryPath: exactPath,
|
|
174
|
+
libraryName,
|
|
175
|
+
matchType: "exact"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const partialMatches = findPartialMatches(nodeModulesPath, libraryName);
|
|
179
|
+
if (partialMatches.length >= 1) {
|
|
180
|
+
return partialMatches[0];
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
async function getLatestVersion(libraryName) {
|
|
185
|
+
var _a;
|
|
186
|
+
try {
|
|
187
|
+
const result = await runShellCommand("npm", ["view", libraryName, "version"]);
|
|
188
|
+
if (result.failed() || !result.stdout) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return ((_a = result.stdout[0]) == null ? void 0 : _a.trim()) || null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function getInstalledVersion(libraryPath) {
|
|
197
|
+
try {
|
|
198
|
+
const packageJsonPath = join2(libraryPath, "package.json");
|
|
199
|
+
const packageJson2 = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
200
|
+
return packageJson2.version || null;
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function installLibrary(installDir, libraryName) {
|
|
206
|
+
ensureInstallDirInitialized(installDir);
|
|
207
|
+
console.log(`Installing ${libraryName}...`);
|
|
208
|
+
const result = await runShellCommand("npm", ["install", libraryName, "--ignore-scripts"], {
|
|
209
|
+
cwd: installDir
|
|
210
|
+
});
|
|
211
|
+
if (result.failed()) {
|
|
212
|
+
throw new Error(`Failed to install ${libraryName}: ${result.stderrAsString()}`);
|
|
213
|
+
}
|
|
214
|
+
console.log(`Successfully installed ${libraryName}`);
|
|
215
|
+
}
|
|
216
|
+
async function updateLibrary(installDir, libraryName) {
|
|
217
|
+
console.log(`Updating ${libraryName} to latest version...`);
|
|
218
|
+
const result = await runShellCommand("npm", ["update", libraryName, "--ignore-scripts"], {
|
|
219
|
+
cwd: installDir
|
|
220
|
+
});
|
|
221
|
+
if (result.failed()) {
|
|
222
|
+
console.warn(`Warning: Failed to update ${libraryName}: ${result.stderrAsString()}`);
|
|
223
|
+
} else {
|
|
224
|
+
console.log(`Successfully updated ${libraryName}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function findLibrary(libraryName, options) {
|
|
228
|
+
const localResult = findLibraryInNodeModules(libraryName);
|
|
229
|
+
if (localResult) {
|
|
230
|
+
return localResult;
|
|
231
|
+
}
|
|
232
|
+
if (options == null ? void 0 : options.skipInstall) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const installDir = getInstallationDirectory();
|
|
236
|
+
let installedResult = findInInstallDir(installDir, libraryName);
|
|
237
|
+
if (installedResult) {
|
|
238
|
+
const installedVersion = getInstalledVersion(installedResult.libraryPath);
|
|
239
|
+
const latestVersion = await getLatestVersion(installedResult.libraryName);
|
|
240
|
+
if (installedVersion && latestVersion && installedVersion !== latestVersion) {
|
|
241
|
+
console.log(`Found ${installedResult.libraryName}@${installedVersion}, latest is ${latestVersion}`);
|
|
242
|
+
await updateLibrary(installDir, installedResult.libraryName);
|
|
243
|
+
installedResult = findInInstallDir(installDir, libraryName);
|
|
244
|
+
}
|
|
245
|
+
return installedResult;
|
|
246
|
+
}
|
|
247
|
+
await installLibrary(installDir, libraryName);
|
|
248
|
+
return findInInstallDir(installDir, libraryName);
|
|
249
|
+
}
|
|
250
|
+
function getLibraryDocs(libraryPath, libraryName) {
|
|
251
|
+
const dirs = [];
|
|
252
|
+
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);
|
|
257
|
+
if (hasReadme) {
|
|
258
|
+
files.push(readmePath);
|
|
259
|
+
}
|
|
260
|
+
if (hasDocsFolder) {
|
|
261
|
+
dirs.push(docsPath);
|
|
262
|
+
}
|
|
263
|
+
const helper = new DocFilesHelper({
|
|
264
|
+
dirs,
|
|
265
|
+
files,
|
|
266
|
+
overrideGetSubcommand: `show ${libraryName}`
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
libraryName,
|
|
270
|
+
libraryPath,
|
|
271
|
+
helper,
|
|
272
|
+
hasReadme,
|
|
273
|
+
hasDocsFolder
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function browseNpmLibrary(libraryName, options) {
|
|
277
|
+
const location = await findLibrary(libraryName, options);
|
|
278
|
+
if (!location) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return getLibraryDocs(location.libraryPath, location.libraryName);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/index.ts
|
|
285
|
+
function parseFrontmatter(text) {
|
|
286
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
|
|
287
|
+
const match = text.match(frontmatterRegex);
|
|
288
|
+
if (!match) {
|
|
289
|
+
return {
|
|
290
|
+
frontmatter: {},
|
|
291
|
+
content: text
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const [, frontmatterBlock, content] = match;
|
|
295
|
+
const frontmatter = {};
|
|
296
|
+
for (const line of frontmatterBlock.split("\n")) {
|
|
297
|
+
const colonIndex = line.indexOf(":");
|
|
298
|
+
if (colonIndex === -1) continue;
|
|
299
|
+
const key = line.slice(0, colonIndex).trim();
|
|
300
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
301
|
+
frontmatter[key] = value;
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
frontmatter,
|
|
305
|
+
content: content.trim()
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
var DocFilesHelper = class {
|
|
309
|
+
constructor(options) {
|
|
310
|
+
this.options = options;
|
|
311
|
+
this.fileMap = /* @__PURE__ */ new Map();
|
|
312
|
+
if (options.dirs) {
|
|
313
|
+
for (const dir of options.dirs) {
|
|
314
|
+
const files = readdirSync2(dir);
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
if (!file.endsWith(".md")) continue;
|
|
317
|
+
this.fileMap.set(file, join3(dir, file));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (options.files) {
|
|
322
|
+
for (const filePath of options.files) {
|
|
323
|
+
const baseFilename = basename(filePath);
|
|
324
|
+
this.fileMap.set(baseFilename, filePath);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
formatGetDocCommand(filename) {
|
|
329
|
+
const script = relative(process.cwd(), process.argv[1]);
|
|
330
|
+
const binName = basename(script);
|
|
331
|
+
const subcommand = this.options.overrideGetSubcommand || "show";
|
|
332
|
+
if (binName === "." || binName.endsWith(".js") || binName.endsWith(".mjs") || binName.endsWith(".ts")) {
|
|
333
|
+
return `node ${script} ${subcommand} ${filename}`;
|
|
334
|
+
}
|
|
335
|
+
return `${binName} ${subcommand} ${filename}`;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* List all doc files, returning their metadata from frontmatter.
|
|
339
|
+
* Files that don't exist are silently skipped.
|
|
340
|
+
*/
|
|
341
|
+
listDocs() {
|
|
342
|
+
const docs = [];
|
|
343
|
+
for (const [baseFilename, fullPath] of this.fileMap) {
|
|
344
|
+
let rawContent;
|
|
345
|
+
try {
|
|
346
|
+
rawContent = readFileSync2(fullPath, "utf-8");
|
|
347
|
+
} catch (err) {
|
|
348
|
+
if (err.code === "ENOENT") {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
throw err;
|
|
352
|
+
}
|
|
353
|
+
const { frontmatter } = parseFrontmatter(rawContent);
|
|
354
|
+
docs.push({
|
|
355
|
+
name: frontmatter.name || basename(baseFilename, ".md"),
|
|
356
|
+
description: frontmatter.description || "",
|
|
357
|
+
filename: baseFilename
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return docs;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get the contents of a specific doc file by name.
|
|
364
|
+
* If the exact filename doesn't exist, looks for a partial match.
|
|
365
|
+
* Throws an error if the doc file is not found or if multiple matches are found.
|
|
366
|
+
*/
|
|
367
|
+
getDoc(name) {
|
|
368
|
+
const baseName = name.endsWith(".md") ? name.slice(0, -3) : name;
|
|
369
|
+
const filename = `${baseName}.md`;
|
|
370
|
+
const fullPath = this.fileMap.get(filename);
|
|
371
|
+
if (fullPath) {
|
|
372
|
+
const rawContent2 = readFileSync2(fullPath, "utf-8");
|
|
373
|
+
const { frontmatter: frontmatter2, content: content2 } = parseFrontmatter(rawContent2);
|
|
374
|
+
return {
|
|
375
|
+
name: frontmatter2.name || baseName,
|
|
376
|
+
description: frontmatter2.description || "",
|
|
377
|
+
filename,
|
|
378
|
+
content: content2,
|
|
379
|
+
rawContent: rawContent2,
|
|
380
|
+
fullPath
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const docs = this.listDocs();
|
|
384
|
+
const matches = docs.filter(
|
|
385
|
+
(doc) => doc.filename.toLowerCase().includes(baseName.toLowerCase()) || doc.name.toLowerCase().includes(baseName.toLowerCase())
|
|
386
|
+
);
|
|
387
|
+
if (matches.length === 0) {
|
|
388
|
+
throw new Error(`Doc file not found: ${baseName}`);
|
|
389
|
+
}
|
|
390
|
+
if (matches.length > 1) {
|
|
391
|
+
const matchNames = matches.map((m) => m.filename).join(", ");
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Multiple docs match "${baseName}": ${matchNames}. Please be more specific.`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const matchedFilename = matches[0].filename;
|
|
397
|
+
const matchedPath = this.fileMap.get(matchedFilename);
|
|
398
|
+
const rawContent = readFileSync2(matchedPath, "utf-8");
|
|
399
|
+
const { frontmatter, content } = parseFrontmatter(rawContent);
|
|
400
|
+
return {
|
|
401
|
+
name: frontmatter.name || basename(matchedFilename, ".md"),
|
|
402
|
+
description: frontmatter.description || "",
|
|
403
|
+
filename: matchedFilename,
|
|
404
|
+
content,
|
|
405
|
+
rawContent,
|
|
406
|
+
fullPath: matchedPath
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Print a formatted list of all doc files to stdout.
|
|
411
|
+
* Used by the 'list-docs' command.
|
|
412
|
+
*/
|
|
413
|
+
printDocFileList() {
|
|
414
|
+
const docs = this.listDocs();
|
|
415
|
+
console.log("Available doc files:\n");
|
|
416
|
+
for (const doc of docs) {
|
|
417
|
+
if (doc.description) {
|
|
418
|
+
console.log(` ${doc.name} (${this.formatGetDocCommand(doc.filename)}):`);
|
|
419
|
+
console.log(` ${doc.description}
|
|
420
|
+
`);
|
|
421
|
+
} else {
|
|
422
|
+
console.log(` ${doc.name} (${this.formatGetDocCommand(doc.filename)})
|
|
423
|
+
`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Print the raw contents of a specific doc file to stdout.
|
|
429
|
+
*
|
|
430
|
+
* Used by the 'get-doc' command.
|
|
431
|
+
*/
|
|
432
|
+
printDocFileContents(name) {
|
|
433
|
+
try {
|
|
434
|
+
const doc = this.getDoc(name);
|
|
435
|
+
console.log(doc.rawContent);
|
|
436
|
+
console.log(`
|
|
437
|
+
(File source: ${doc.fullPath})`);
|
|
438
|
+
} catch {
|
|
439
|
+
console.error(`Doc file not found: ${name}`);
|
|
440
|
+
console.error('Run with "list-docs" or "list" command to see available docs.');
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
yargsSetup(yargs2) {
|
|
445
|
+
yargs2.command(
|
|
446
|
+
"list-docs",
|
|
447
|
+
"List available documentation files",
|
|
448
|
+
{},
|
|
449
|
+
async () => this.printDocFileList()
|
|
450
|
+
).command(
|
|
451
|
+
"get-doc <name>",
|
|
452
|
+
"Display the contents of a documentation file",
|
|
453
|
+
(yargs3) => {
|
|
454
|
+
return yargs3.positional("name", {
|
|
455
|
+
type: "string",
|
|
456
|
+
describe: "Name of the doc file",
|
|
457
|
+
demandOption: true
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
async (argv) => this.printDocFileContents(argv.name)
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function parseTarget(target) {
|
|
465
|
+
if (target.startsWith(".") || target.startsWith("/")) {
|
|
466
|
+
return { type: "directory", value: target };
|
|
467
|
+
}
|
|
468
|
+
return { type: "npm", value: target };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/cli.ts
|
|
472
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
473
|
+
var __dirname = dirname2(__filename);
|
|
474
|
+
var packageJson = JSON.parse(
|
|
475
|
+
readFileSync3(join4(__dirname, "../package.json"), "utf-8")
|
|
476
|
+
);
|
|
477
|
+
async function main() {
|
|
478
|
+
await yargs(hideBin(process.argv)).command(
|
|
479
|
+
"list <target>",
|
|
480
|
+
"List available doc files in a directory or NPM package",
|
|
481
|
+
(yargs2) => {
|
|
482
|
+
return yargs2.positional("target", {
|
|
483
|
+
type: "string",
|
|
484
|
+
describe: "Directory path (starts with . or /) or NPM package name",
|
|
485
|
+
demandOption: true
|
|
486
|
+
});
|
|
487
|
+
},
|
|
488
|
+
async (argv) => {
|
|
489
|
+
const target = argv.target;
|
|
490
|
+
const parsed = parseTarget(target);
|
|
491
|
+
if (parsed.type === "directory") {
|
|
492
|
+
const local = browseLocalLibrary(parsed.value);
|
|
493
|
+
local.helper.printDocFileList();
|
|
494
|
+
} else {
|
|
495
|
+
const docs = await browseNpmLibrary(parsed.value);
|
|
496
|
+
if (!docs) {
|
|
497
|
+
console.error(`Could not find library: ${parsed.value}`);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
console.log(`
|
|
501
|
+
Library: ${docs.libraryName}`);
|
|
502
|
+
console.log(`Path: ${docs.libraryPath}
|
|
503
|
+
`);
|
|
504
|
+
if (!docs.hasReadme && !docs.hasDocsFolder) {
|
|
505
|
+
console.log("No documentation found for this library.");
|
|
506
|
+
console.log("(No README.md or docs/ folder exists)");
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
docs.helper.printDocFileList();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
).command(
|
|
513
|
+
"show <target> [name]",
|
|
514
|
+
"Get the contents of one doc file",
|
|
515
|
+
(yargs2) => {
|
|
516
|
+
return yargs2.positional("target", {
|
|
517
|
+
type: "string",
|
|
518
|
+
describe: "Directory path (starts with . or /) or NPM package name",
|
|
519
|
+
demandOption: true
|
|
520
|
+
}).positional("name", {
|
|
521
|
+
type: "string",
|
|
522
|
+
describe: "Name of the doc file (defaults to README)",
|
|
523
|
+
default: "README"
|
|
524
|
+
});
|
|
525
|
+
},
|
|
526
|
+
async (argv) => {
|
|
527
|
+
const target = argv.target;
|
|
528
|
+
const name = argv.name;
|
|
529
|
+
const parsed = parseTarget(target);
|
|
530
|
+
if (parsed.type === "directory") {
|
|
531
|
+
const local = browseLocalLibrary(parsed.value);
|
|
532
|
+
local.helper.printDocFileContents(name);
|
|
533
|
+
} else {
|
|
534
|
+
const docs = await browseNpmLibrary(parsed.value);
|
|
535
|
+
if (!docs) {
|
|
536
|
+
console.error(`Could not find library: ${parsed.value}`);
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
docs.helper.printDocFileContents(name);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
).strictCommands().demandCommand(1, "You must specify a command").help().alias("help", "h").version(packageJson.version).alias("version", "v").example([
|
|
543
|
+
["$0 list ./docs", "List all doc files in ./docs directory"],
|
|
544
|
+
["$0 list lodash", "List all doc files for the lodash NPM package"],
|
|
545
|
+
["$0 show ./docs project-setup", "Display the project-setup doc from ./docs"],
|
|
546
|
+
["$0 show lodash", "Display the README for the lodash NPM package"]
|
|
547
|
+
]).parse();
|
|
548
|
+
}
|
|
549
|
+
main();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export { browseLocalLibrary, type LocalLibraryDocs } from './browseLocalLibrary.ts';
|
|
2
|
+
export { browseNpmLibrary, findLibrary, findLibraryInNodeModules, getInstallationDirectory, getLibraryDocs, type LibraryLocation, type NpmLibraryDocs, } from './browseNpmLibrary.ts';
|
|
3
|
+
export interface Frontmatter {
|
|
4
|
+
name?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
[key: string]: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface ParsedDocument {
|
|
9
|
+
frontmatter: Frontmatter;
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DocInfo {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
filename: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DocContent extends DocInfo {
|
|
18
|
+
content: string;
|
|
19
|
+
rawContent: string;
|
|
20
|
+
fullPath: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DocFilesHelperOptions {
|
|
23
|
+
dirs?: string[];
|
|
24
|
+
files?: string[];
|
|
25
|
+
overrideGetSubcommand?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse YAML frontmatter from a markdown document.
|
|
29
|
+
* Frontmatter is delimited by --- at the start of the file.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseFrontmatter(text: string): ParsedDocument;
|
|
32
|
+
/**
|
|
33
|
+
* Helper class for working with doc files.
|
|
34
|
+
*/
|
|
35
|
+
export declare class DocFilesHelper {
|
|
36
|
+
options: DocFilesHelperOptions;
|
|
37
|
+
private fileMap;
|
|
38
|
+
constructor(options: DocFilesHelperOptions);
|
|
39
|
+
formatGetDocCommand(filename: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* List all doc files, returning their metadata from frontmatter.
|
|
42
|
+
* Files that don't exist are silently skipped.
|
|
43
|
+
*/
|
|
44
|
+
listDocs(): DocInfo[];
|
|
45
|
+
/**
|
|
46
|
+
* Get the contents of a specific doc file by name.
|
|
47
|
+
* If the exact filename doesn't exist, looks for a partial match.
|
|
48
|
+
* Throws an error if the doc file is not found or if multiple matches are found.
|
|
49
|
+
*/
|
|
50
|
+
getDoc(name: string): DocContent;
|
|
51
|
+
/**
|
|
52
|
+
* Print a formatted list of all doc files to stdout.
|
|
53
|
+
* Used by the 'list-docs' command.
|
|
54
|
+
*/
|
|
55
|
+
printDocFileList(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Print the raw contents of a specific doc file to stdout.
|
|
58
|
+
*
|
|
59
|
+
* Used by the 'get-doc' command.
|
|
60
|
+
*/
|
|
61
|
+
printDocFileContents(name: string): void;
|
|
62
|
+
yargsSetup(yargs: any): void;
|
|
63
|
+
}
|
|
64
|
+
export type ParsedTarget = {
|
|
65
|
+
type: 'directory';
|
|
66
|
+
value: string;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'npm';
|
|
69
|
+
value: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Determine if a target string is a directory path or an NPM package name.
|
|
73
|
+
* Returns 'directory' if it starts with '.' or '/', otherwise 'npm'.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseTarget(target: string): ParsedTarget;
|
|
76
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAW,SAAQ,OAAO;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IAEpC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAGhB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAIjB,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CA2B7D;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,EAAE,qBAAqB,CAAA;IAG9B,OAAO,CAAC,OAAO,CAAsB;gBAEzB,OAAO,EAAE,qBAAqB;IAwB1C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAW7C;;;OAGG;IACH,QAAQ,IAAI,OAAO,EAAE;IA0BrB;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU;IAsDhC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAaxB;;;;OAIG;IACH,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAYxC,UAAU,CAAC,KAAK,EAAE,GAAG;CAsBtB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAKxD"}
|