@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
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { runShellCommand } from '@facetlayer/subprocess-wrapper';
|
|
5
|
+
import { DocFilesHelper } from './index.ts';
|
|
6
|
+
|
|
7
|
+
export interface LibraryLocation {
|
|
8
|
+
libraryPath: string;
|
|
9
|
+
libraryName: string;
|
|
10
|
+
matchType: 'exact' | 'partial';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NpmLibraryDocs {
|
|
14
|
+
libraryName: string;
|
|
15
|
+
libraryPath: string;
|
|
16
|
+
helper: DocFilesHelper;
|
|
17
|
+
hasReadme: boolean;
|
|
18
|
+
hasDocsFolder: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if a directory contains a package that matches the given name exactly
|
|
23
|
+
*/
|
|
24
|
+
function findExactMatch(nodeModulesPath: string, libraryName: string): string | null {
|
|
25
|
+
// Handle scoped packages like @scope/package
|
|
26
|
+
if (libraryName.startsWith('@')) {
|
|
27
|
+
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'))) {
|
|
32
|
+
return fullPath;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Regular package
|
|
39
|
+
const fullPath = join(nodeModulesPath, libraryName);
|
|
40
|
+
if (existsSync(fullPath) && existsSync(join(fullPath, 'package.json'))) {
|
|
41
|
+
return fullPath;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Find packages that partially match the given name
|
|
48
|
+
*/
|
|
49
|
+
function findPartialMatches(nodeModulesPath: string, partialName: string): LibraryLocation[] {
|
|
50
|
+
const matches: LibraryLocation[] = [];
|
|
51
|
+
const lowerPartial = partialName.toLowerCase();
|
|
52
|
+
|
|
53
|
+
if (!existsSync(nodeModulesPath)) {
|
|
54
|
+
return matches;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
|
|
58
|
+
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (!entry.isDirectory()) continue;
|
|
61
|
+
|
|
62
|
+
// Handle scoped packages
|
|
63
|
+
if (entry.name.startsWith('@')) {
|
|
64
|
+
const scopePath = join(nodeModulesPath, entry.name);
|
|
65
|
+
const scopedEntries = readdirSync(scopePath, { withFileTypes: true });
|
|
66
|
+
for (const scopedEntry of scopedEntries) {
|
|
67
|
+
if (!scopedEntry.isDirectory()) continue;
|
|
68
|
+
const fullName = `${entry.name}/${scopedEntry.name}`;
|
|
69
|
+
if (fullName.toLowerCase().includes(lowerPartial)) {
|
|
70
|
+
const fullPath = join(scopePath, scopedEntry.name);
|
|
71
|
+
if (existsSync(join(fullPath, 'package.json'))) {
|
|
72
|
+
matches.push({
|
|
73
|
+
libraryPath: fullPath,
|
|
74
|
+
libraryName: fullName,
|
|
75
|
+
matchType: 'partial',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Regular package
|
|
82
|
+
if (entry.name.toLowerCase().includes(lowerPartial)) {
|
|
83
|
+
const fullPath = join(nodeModulesPath, entry.name);
|
|
84
|
+
if (existsSync(join(fullPath, 'package.json'))) {
|
|
85
|
+
matches.push({
|
|
86
|
+
libraryPath: fullPath,
|
|
87
|
+
libraryName: entry.name,
|
|
88
|
+
matchType: 'partial',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return matches;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get all node_modules directories from current directory up to root
|
|
100
|
+
*/
|
|
101
|
+
function getNodeModulesPaths(startDir: string): string[] {
|
|
102
|
+
const paths: string[] = [];
|
|
103
|
+
let currentDir = startDir;
|
|
104
|
+
|
|
105
|
+
while (true) {
|
|
106
|
+
const nodeModulesPath = join(currentDir, 'node_modules');
|
|
107
|
+
if (existsSync(nodeModulesPath)) {
|
|
108
|
+
paths.push(nodeModulesPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parentDir = dirname(currentDir);
|
|
112
|
+
if (parentDir === currentDir) {
|
|
113
|
+
// Reached root
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
currentDir = parentDir;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return paths;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Find a library by name in node_modules directories.
|
|
124
|
+
*/
|
|
125
|
+
export function findLibraryInNodeModules(libraryName: string, startDir?: string): LibraryLocation | null {
|
|
126
|
+
const cwd = startDir || process.cwd();
|
|
127
|
+
const nodeModulesPaths = getNodeModulesPaths(cwd);
|
|
128
|
+
|
|
129
|
+
// Phase 1: Try exact match in all directories first
|
|
130
|
+
for (const nodeModulesPath of nodeModulesPaths) {
|
|
131
|
+
const exactPath = findExactMatch(nodeModulesPath, libraryName);
|
|
132
|
+
if (exactPath) {
|
|
133
|
+
return {
|
|
134
|
+
libraryPath: exactPath,
|
|
135
|
+
libraryName: libraryName,
|
|
136
|
+
matchType: 'exact',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Phase 2: Try partial match in all directories
|
|
142
|
+
for (const nodeModulesPath of nodeModulesPaths) {
|
|
143
|
+
const partialMatches = findPartialMatches(nodeModulesPath, libraryName);
|
|
144
|
+
if (partialMatches.length === 1) {
|
|
145
|
+
return partialMatches[0];
|
|
146
|
+
}
|
|
147
|
+
if (partialMatches.length > 1) {
|
|
148
|
+
console.warn(`Multiple partial matches found for "${libraryName}":`);
|
|
149
|
+
for (const match of partialMatches) {
|
|
150
|
+
console.warn(` - ${match.libraryName}`);
|
|
151
|
+
}
|
|
152
|
+
console.warn(`Using: ${partialMatches[0].libraryName}`);
|
|
153
|
+
return partialMatches[0];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get the installation directory for libraries that aren't found in node_modules
|
|
162
|
+
*/
|
|
163
|
+
export function getInstallationDirectory(): string {
|
|
164
|
+
const stateDir = join(homedir(), '.cache', 'docs-tool');
|
|
165
|
+
const installDir = join(stateDir, 'installed-packages');
|
|
166
|
+
|
|
167
|
+
if (!existsSync(installDir)) {
|
|
168
|
+
mkdirSync(installDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return installDir;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Initialize the installation directory with a package.json if needed
|
|
176
|
+
*/
|
|
177
|
+
function ensureInstallDirInitialized(installDir: string): void {
|
|
178
|
+
const packageJsonPath = join(installDir, 'package.json');
|
|
179
|
+
|
|
180
|
+
if (!existsSync(packageJsonPath)) {
|
|
181
|
+
const packageJson = {
|
|
182
|
+
name: 'docs-tool-installed-packages',
|
|
183
|
+
version: '1.0.0',
|
|
184
|
+
private: true,
|
|
185
|
+
description: 'Packages installed by docs-tool for documentation viewing',
|
|
186
|
+
};
|
|
187
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if a library is installed in our installation directory
|
|
193
|
+
*/
|
|
194
|
+
function findInInstallDir(installDir: string, libraryName: string): LibraryLocation | null {
|
|
195
|
+
const nodeModulesPath = join(installDir, 'node_modules');
|
|
196
|
+
|
|
197
|
+
if (!existsSync(nodeModulesPath)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const exactPath = findExactMatch(nodeModulesPath, libraryName);
|
|
202
|
+
if (exactPath) {
|
|
203
|
+
return {
|
|
204
|
+
libraryPath: exactPath,
|
|
205
|
+
libraryName: libraryName,
|
|
206
|
+
matchType: 'exact',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const partialMatches = findPartialMatches(nodeModulesPath, libraryName);
|
|
211
|
+
if (partialMatches.length >= 1) {
|
|
212
|
+
return partialMatches[0];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get the latest version of a package from npm registry
|
|
220
|
+
*/
|
|
221
|
+
async function getLatestVersion(libraryName: string): Promise<string | null> {
|
|
222
|
+
try {
|
|
223
|
+
const result = await runShellCommand('npm', ['view', libraryName, 'version']);
|
|
224
|
+
if (result.failed() || !result.stdout) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return result.stdout[0]?.trim() || null;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the installed version of a package
|
|
235
|
+
*/
|
|
236
|
+
function getInstalledVersion(libraryPath: string): string | null {
|
|
237
|
+
try {
|
|
238
|
+
const packageJsonPath = join(libraryPath, 'package.json');
|
|
239
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
240
|
+
return packageJson.version || null;
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Install a library using npm (without running install scripts)
|
|
248
|
+
*/
|
|
249
|
+
async function installLibrary(installDir: string, libraryName: string): Promise<void> {
|
|
250
|
+
ensureInstallDirInitialized(installDir);
|
|
251
|
+
|
|
252
|
+
console.log(`Installing ${libraryName}...`);
|
|
253
|
+
|
|
254
|
+
const result = await runShellCommand('npm', ['install', libraryName, '--ignore-scripts'], {
|
|
255
|
+
cwd: installDir,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (result.failed()) {
|
|
259
|
+
throw new Error(`Failed to install ${libraryName}: ${result.stderrAsString()}`);
|
|
260
|
+
}
|
|
261
|
+
console.log(`Successfully installed ${libraryName}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Update a library to the latest version
|
|
266
|
+
*/
|
|
267
|
+
async function updateLibrary(installDir: string, libraryName: string): Promise<void> {
|
|
268
|
+
console.log(`Updating ${libraryName} to latest version...`);
|
|
269
|
+
|
|
270
|
+
const result = await runShellCommand('npm', ['update', libraryName, '--ignore-scripts'], {
|
|
271
|
+
cwd: installDir,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (result.failed()) {
|
|
275
|
+
console.warn(`Warning: Failed to update ${libraryName}: ${result.stderrAsString()}`);
|
|
276
|
+
} else {
|
|
277
|
+
console.log(`Successfully updated ${libraryName}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Find a library, installing it if necessary
|
|
283
|
+
*/
|
|
284
|
+
export async function findLibrary(libraryName: string, options?: { skipInstall?: boolean }): Promise<LibraryLocation | null> {
|
|
285
|
+
// First, try to find in local node_modules
|
|
286
|
+
const localResult = findLibraryInNodeModules(libraryName);
|
|
287
|
+
if (localResult) {
|
|
288
|
+
return localResult;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (options?.skipInstall) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check our installation directory
|
|
296
|
+
const installDir = getInstallationDirectory();
|
|
297
|
+
let installedResult = findInInstallDir(installDir, libraryName);
|
|
298
|
+
|
|
299
|
+
if (installedResult) {
|
|
300
|
+
// Check if we need to update to a newer version
|
|
301
|
+
const installedVersion = getInstalledVersion(installedResult.libraryPath);
|
|
302
|
+
const latestVersion = await getLatestVersion(installedResult.libraryName);
|
|
303
|
+
|
|
304
|
+
if (installedVersion && latestVersion && installedVersion !== latestVersion) {
|
|
305
|
+
console.log(`Found ${installedResult.libraryName}@${installedVersion}, latest is ${latestVersion}`);
|
|
306
|
+
await updateLibrary(installDir, installedResult.libraryName);
|
|
307
|
+
installedResult = findInInstallDir(installDir, libraryName);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return installedResult;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Not found anywhere - install it
|
|
314
|
+
await installLibrary(installDir, libraryName);
|
|
315
|
+
|
|
316
|
+
return findInInstallDir(installDir, libraryName);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Create a DocFilesHelper for a library's documentation
|
|
321
|
+
*/
|
|
322
|
+
export function getLibraryDocs(libraryPath: string, libraryName: string): NpmLibraryDocs {
|
|
323
|
+
const dirs: string[] = [];
|
|
324
|
+
const files: string[] = [];
|
|
325
|
+
|
|
326
|
+
const readmePath = join(libraryPath, 'README.md');
|
|
327
|
+
const docsPath = join(libraryPath, 'docs');
|
|
328
|
+
|
|
329
|
+
const hasReadme = existsSync(readmePath);
|
|
330
|
+
const hasDocsFolder = existsSync(docsPath);
|
|
331
|
+
|
|
332
|
+
if (hasReadme) {
|
|
333
|
+
files.push(readmePath);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (hasDocsFolder) {
|
|
337
|
+
dirs.push(docsPath);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const helper = new DocFilesHelper({
|
|
341
|
+
dirs,
|
|
342
|
+
files,
|
|
343
|
+
overrideGetSubcommand: `show ${libraryName}`,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
libraryName,
|
|
348
|
+
libraryPath,
|
|
349
|
+
helper,
|
|
350
|
+
hasReadme,
|
|
351
|
+
hasDocsFolder,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Browse an NPM library's documentation.
|
|
357
|
+
* First checks local node_modules, then installs from npm if not found.
|
|
358
|
+
*/
|
|
359
|
+
export async function browseNpmLibrary(libraryName: string, options?: { skipInstall?: boolean }): Promise<NpmLibraryDocs | null> {
|
|
360
|
+
const location = await findLibrary(libraryName, options);
|
|
361
|
+
|
|
362
|
+
if (!location) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return getLibraryDocs(location.libraryPath, location.libraryName);
|
|
367
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { parseTarget, browseLocalLibrary, browseNpmLibrary } from './index.ts';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
const packageJson = JSON.parse(
|
|
13
|
+
readFileSync(join(__dirname, '../package.json'), 'utf-8')
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
await yargs(hideBin(process.argv))
|
|
18
|
+
.command(
|
|
19
|
+
'list <target>',
|
|
20
|
+
'List available doc files in a directory or NPM package',
|
|
21
|
+
(yargs) => {
|
|
22
|
+
return yargs.positional('target', {
|
|
23
|
+
type: 'string',
|
|
24
|
+
describe: 'Directory path (starts with . or /) or NPM package name',
|
|
25
|
+
demandOption: true,
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
async (argv) => {
|
|
29
|
+
const target = argv.target as string;
|
|
30
|
+
const parsed = parseTarget(target);
|
|
31
|
+
|
|
32
|
+
if (parsed.type === 'directory') {
|
|
33
|
+
const local = browseLocalLibrary(parsed.value);
|
|
34
|
+
local.helper.printDocFileList();
|
|
35
|
+
} else {
|
|
36
|
+
const docs = await browseNpmLibrary(parsed.value);
|
|
37
|
+
if (!docs) {
|
|
38
|
+
console.error(`Could not find library: ${parsed.value}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`\nLibrary: ${docs.libraryName}`);
|
|
43
|
+
console.log(`Path: ${docs.libraryPath}\n`);
|
|
44
|
+
|
|
45
|
+
if (!docs.hasReadme && !docs.hasDocsFolder) {
|
|
46
|
+
console.log('No documentation found for this library.');
|
|
47
|
+
console.log('(No README.md or docs/ folder exists)');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
docs.helper.printDocFileList();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
.command(
|
|
56
|
+
'show <target> [name]',
|
|
57
|
+
'Get the contents of one doc file',
|
|
58
|
+
(yargs) => {
|
|
59
|
+
return yargs
|
|
60
|
+
.positional('target', {
|
|
61
|
+
type: 'string',
|
|
62
|
+
describe: 'Directory path (starts with . or /) or NPM package name',
|
|
63
|
+
demandOption: true,
|
|
64
|
+
})
|
|
65
|
+
.positional('name', {
|
|
66
|
+
type: 'string',
|
|
67
|
+
describe: 'Name of the doc file (defaults to README)',
|
|
68
|
+
default: 'README',
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
async (argv) => {
|
|
72
|
+
const target = argv.target as string;
|
|
73
|
+
const name = argv.name as string;
|
|
74
|
+
const parsed = parseTarget(target);
|
|
75
|
+
|
|
76
|
+
if (parsed.type === 'directory') {
|
|
77
|
+
const local = browseLocalLibrary(parsed.value);
|
|
78
|
+
local.helper.printDocFileContents(name);
|
|
79
|
+
} else {
|
|
80
|
+
const docs = await browseNpmLibrary(parsed.value);
|
|
81
|
+
if (!docs) {
|
|
82
|
+
console.error(`Could not find library: ${parsed.value}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
docs.helper.printDocFileContents(name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
.strictCommands()
|
|
91
|
+
.demandCommand(1, 'You must specify a command')
|
|
92
|
+
.help()
|
|
93
|
+
.alias('help', 'h')
|
|
94
|
+
.version(packageJson.version)
|
|
95
|
+
.alias('version', 'v')
|
|
96
|
+
.example([
|
|
97
|
+
['$0 list ./docs', 'List all doc files in ./docs directory'],
|
|
98
|
+
['$0 list lodash', 'List all doc files for the lodash NPM package'],
|
|
99
|
+
['$0 show ./docs project-setup', 'Display the project-setup doc from ./docs'],
|
|
100
|
+
['$0 show lodash', 'Display the README for the lodash NPM package'],
|
|
101
|
+
])
|
|
102
|
+
.parse();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main();
|