@metamask-previews/messenger-cli 0.0.0-preview-4a2a8e8f6 → 0.1.0-preview-e19d3725e
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/CHANGELOG.md +8 -1
- package/dist/docs/cli.cjs +199 -0
- package/dist/docs/cli.cjs.map +1 -0
- package/dist/docs/cli.d.cts +3 -0
- package/dist/docs/cli.d.cts.map +1 -0
- package/dist/docs/cli.d.mts +3 -0
- package/dist/docs/cli.d.mts.map +1 -0
- package/dist/docs/cli.mjs +198 -0
- package/dist/docs/cli.mjs.map +1 -0
- package/dist/docs/discovery.cjs +46 -0
- package/dist/docs/discovery.cjs.map +1 -0
- package/dist/docs/discovery.d.cts +17 -0
- package/dist/docs/discovery.d.cts.map +1 -0
- package/dist/docs/discovery.d.mts +17 -0
- package/dist/docs/discovery.d.mts.map +1 -0
- package/dist/docs/discovery.mjs +41 -0
- package/dist/docs/discovery.mjs.map +1 -0
- package/dist/docs/extraction.cjs +569 -0
- package/dist/docs/extraction.cjs.map +1 -0
- package/dist/docs/extraction.d.cts +10 -0
- package/dist/docs/extraction.d.cts.map +1 -0
- package/dist/docs/extraction.d.mts +10 -0
- package/dist/docs/extraction.d.mts.map +1 -0
- package/dist/docs/extraction.mjs +543 -0
- package/dist/docs/extraction.mjs.map +1 -0
- package/dist/docs/generate.cjs +254 -0
- package/dist/docs/generate.cjs.map +1 -0
- package/dist/docs/generate.d.cts +27 -0
- package/dist/docs/generate.d.cts.map +1 -0
- package/dist/docs/generate.d.mts +27 -0
- package/dist/docs/generate.d.mts.map +1 -0
- package/dist/docs/generate.mjs +227 -0
- package/dist/docs/generate.mjs.map +1 -0
- package/dist/docs/markdown.cjs +210 -0
- package/dist/docs/markdown.cjs.map +1 -0
- package/dist/docs/markdown.d.cts +35 -0
- package/dist/docs/markdown.d.cts.map +1 -0
- package/dist/docs/markdown.d.mts +35 -0
- package/dist/docs/markdown.d.mts.map +1 -0
- package/dist/docs/markdown.mjs +203 -0
- package/dist/docs/markdown.mjs.map +1 -0
- package/dist/docs/types.cjs +3 -0
- package/dist/docs/types.cjs.map +1 -0
- package/dist/docs/types.d.cts +23 -0
- package/dist/docs/types.d.cts.map +1 -0
- package/dist/docs/types.d.mts +23 -0
- package/dist/docs/types.d.mts.map +1 -0
- package/dist/docs/types.mjs +2 -0
- package/dist/docs/types.mjs.map +1 -0
- package/package.json +24 -4
- package/template/docusaurus.config.ts +123 -0
- package/template/src/css/custom.css +314 -0
- package/template/static/fonts/MM-Sans/MM_Sans_Mono-Regular.woff2 +0 -0
- package/template/static/img/favicons/favicon-96x96.png +0 -0
- package/template/static/img/metamask-fox.svg +12 -0
- package/template/static/img/metamask-logo-dark.svg +3 -0
- package/template/static/img/metamask-logo.svg +3 -0
- package/template/tsconfig.json +13 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.generate = void 0;
|
|
27
|
+
const node_1 = require("@metamask/utils/node");
|
|
28
|
+
const node_child_process_1 = require("node:child_process");
|
|
29
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
30
|
+
const path = __importStar(require("node:path"));
|
|
31
|
+
const node_util_1 = require("node:util");
|
|
32
|
+
const discovery_1 = require("./discovery.cjs");
|
|
33
|
+
const extraction_1 = require("./extraction.cjs");
|
|
34
|
+
const markdown_1 = require("./markdown.cjs");
|
|
35
|
+
/**
|
|
36
|
+
* Compute a deduplication score for a messenger item, preferring items with
|
|
37
|
+
* JSDoc and from the "home" package whose name matches the namespace.
|
|
38
|
+
*
|
|
39
|
+
* @param item - The messenger item to score.
|
|
40
|
+
* @returns A numeric score (higher is better).
|
|
41
|
+
*/
|
|
42
|
+
function deduplicationScore(item) {
|
|
43
|
+
const jsDocScore = item.jsDoc ? 2 : 0;
|
|
44
|
+
const namespacePrefix = item.typeString
|
|
45
|
+
.split(':')[0]
|
|
46
|
+
.replace(/Controller|Service/u, '')
|
|
47
|
+
.toLowerCase();
|
|
48
|
+
const homeScore = item.sourceFile.includes(namespacePrefix) ? 1 : 0;
|
|
49
|
+
return jsDocScore + homeScore;
|
|
50
|
+
}
|
|
51
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the GitHub blob base URL for a project by reading its git remote.
|
|
54
|
+
*
|
|
55
|
+
* @param projectPath - Absolute path to the project root.
|
|
56
|
+
* @returns A base URL like "https://github.com/Owner/Repo/blob/main/" or null.
|
|
57
|
+
*/
|
|
58
|
+
async function resolveRepoBaseUrl(projectPath) {
|
|
59
|
+
try {
|
|
60
|
+
const { stdout: remoteRaw } = await execFileAsync('git', ['remote', 'get-url', 'origin'], { cwd: projectPath });
|
|
61
|
+
const remote = remoteRaw.trim();
|
|
62
|
+
// Parse owner/repo from SSH or HTTPS remote URLs
|
|
63
|
+
// Handles aliases like github.com-Org used in SSH configs
|
|
64
|
+
const match = remote.match(/github\.com[^:/]*[:/]([^/]+\/[^/]+?)(?:\.git)?$/u);
|
|
65
|
+
if (!match) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return `https://github.com/${match[1]}/blob/main/`;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Scan a project for messenger action/event types and generate documentation.
|
|
76
|
+
*
|
|
77
|
+
* @param options - Generation options.
|
|
78
|
+
* @returns A promise resolving to counts of generated namespaces, actions, and events.
|
|
79
|
+
*/
|
|
80
|
+
async function generate(options) {
|
|
81
|
+
const { projectPath, outputDir, scanDirs } = options;
|
|
82
|
+
const allItems = [];
|
|
83
|
+
// Check which sources are available
|
|
84
|
+
const existingScanDirs = [];
|
|
85
|
+
for (const dir of scanDirs) {
|
|
86
|
+
const abs = path.join(projectPath, dir);
|
|
87
|
+
if (await (0, node_1.directoryExists)(abs)) {
|
|
88
|
+
existingScanDirs.push(dir);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const packagesDir = path.join(projectPath, 'packages');
|
|
92
|
+
const hasPackages = await (0, node_1.directoryExists)(packagesDir);
|
|
93
|
+
const nmDir = path.join(projectPath, 'node_modules', '@metamask');
|
|
94
|
+
const hasNodeModules = await (0, node_1.directoryExists)(nmDir);
|
|
95
|
+
const sources = [];
|
|
96
|
+
for (const dir of existingScanDirs) {
|
|
97
|
+
sources.push(`${dir}/ (.ts)`);
|
|
98
|
+
}
|
|
99
|
+
if (hasPackages) {
|
|
100
|
+
sources.push('packages/*/src (.ts)');
|
|
101
|
+
}
|
|
102
|
+
if (hasNodeModules) {
|
|
103
|
+
sources.push('node_modules/@metamask/*/dist (.d.cts)');
|
|
104
|
+
}
|
|
105
|
+
console.log(`Scanning ${sources.join(', ')} for Messenger action/event types...`);
|
|
106
|
+
// Scan configured source directories for .ts files
|
|
107
|
+
for (const dir of existingScanDirs) {
|
|
108
|
+
const abs = path.join(projectPath, dir);
|
|
109
|
+
const tsFiles = await (0, discovery_1.findTsFiles)(abs);
|
|
110
|
+
for (const file of tsFiles) {
|
|
111
|
+
try {
|
|
112
|
+
const items = await (0, extraction_1.extractFromFile)(file, projectPath);
|
|
113
|
+
allItems.push(...items);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.warn(`Warning: failed to parse ${path.relative(projectPath, file)}: ${String(error)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Scan packages/*/src for .ts source files (monorepo)
|
|
121
|
+
if (hasPackages) {
|
|
122
|
+
const entries = await fs.readdir(packagesDir, { withFileTypes: true });
|
|
123
|
+
const packageDirs = entries
|
|
124
|
+
.filter((dirent) => dirent.isDirectory())
|
|
125
|
+
.map((dirent) => path.join(packagesDir, dirent.name, 'src'));
|
|
126
|
+
for (const srcDir of packageDirs) {
|
|
127
|
+
if (!(await (0, node_1.directoryExists)(srcDir))) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const tsFiles = await (0, discovery_1.findTsFiles)(srcDir);
|
|
131
|
+
for (const file of tsFiles) {
|
|
132
|
+
try {
|
|
133
|
+
const items = await (0, extraction_1.extractFromFile)(file, projectPath);
|
|
134
|
+
allItems.push(...items);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.warn(`Warning: failed to parse ${path.relative(projectPath, file)}: ${String(error)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Scan node_modules/@metamask/*/dist for .d.cts declaration files
|
|
143
|
+
if (hasNodeModules) {
|
|
144
|
+
const entries = await fs.readdir(nmDir, { withFileTypes: true });
|
|
145
|
+
const pkgDirs = entries
|
|
146
|
+
.filter((dirent) => dirent.isDirectory() || dirent.isSymbolicLink())
|
|
147
|
+
.map((dirent) => path.join(nmDir, dirent.name, 'dist'));
|
|
148
|
+
for (const distDir of pkgDirs) {
|
|
149
|
+
if (!(await (0, node_1.directoryExists)(distDir))) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const dtsFiles = await (0, discovery_1.findDtsFiles)(distDir);
|
|
153
|
+
for (const file of dtsFiles) {
|
|
154
|
+
try {
|
|
155
|
+
const items = await (0, extraction_1.extractFromFile)(file, projectPath);
|
|
156
|
+
allItems.push(...items);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.warn(`Warning: failed to parse ${path.relative(projectPath, file)}: ${String(error)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (existingScanDirs.length === 0 && !hasPackages && !hasNodeModules) {
|
|
165
|
+
throw new Error(`No scannable directories found in ${projectPath}. ` +
|
|
166
|
+
`Looked for: ${scanDirs.join(', ')}, packages/, node_modules/@metamask/`);
|
|
167
|
+
}
|
|
168
|
+
console.log(`Found ${allItems.length} messenger items total.`);
|
|
169
|
+
// Group by namespace (part before the colon), deduplicating by typeString.
|
|
170
|
+
// When duplicates exist, prefer the one with JSDoc, or from the package
|
|
171
|
+
// whose name matches the namespace.
|
|
172
|
+
const byNamespace = new Map();
|
|
173
|
+
const seen = new Map(); // key: typeString
|
|
174
|
+
for (const item of allItems) {
|
|
175
|
+
const existing = seen.get(item.typeString);
|
|
176
|
+
if (existing) {
|
|
177
|
+
// Prefer item with JSDoc, or from the "home" package
|
|
178
|
+
const existingScore = deduplicationScore(existing);
|
|
179
|
+
const newScore = deduplicationScore(item);
|
|
180
|
+
if (newScore <= existingScore) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
// Replace existing with better item
|
|
184
|
+
const ns = item.typeString.split(':')[0];
|
|
185
|
+
const group = byNamespace.get(ns);
|
|
186
|
+
if (group) {
|
|
187
|
+
const list = existing.kind === 'action' ? group.actions : group.events;
|
|
188
|
+
const idx = list.indexOf(existing);
|
|
189
|
+
if (idx !== -1) {
|
|
190
|
+
list[idx] = item;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
seen.set(item.typeString, item);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
seen.set(item.typeString, item);
|
|
197
|
+
const ns = item.typeString.split(':')[0];
|
|
198
|
+
if (!byNamespace.has(ns)) {
|
|
199
|
+
byNamespace.set(ns, { namespace: ns, actions: [], events: [] });
|
|
200
|
+
}
|
|
201
|
+
const group = byNamespace.get(ns);
|
|
202
|
+
if (group) {
|
|
203
|
+
if (item.kind === 'action') {
|
|
204
|
+
group.actions.push(item);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
group.events.push(item);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Sort namespaces alphabetically, sort items within each namespace
|
|
212
|
+
const namespaces = Array.from(byNamespace.values()).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
213
|
+
for (const ns of namespaces) {
|
|
214
|
+
ns.actions.sort((a, b) => a.typeString.localeCompare(b.typeString));
|
|
215
|
+
ns.events.sort((a, b) => a.typeString.localeCompare(b.typeString));
|
|
216
|
+
}
|
|
217
|
+
// Resolve repository base URL for source links
|
|
218
|
+
const repoBaseUrl = await resolveRepoBaseUrl(projectPath);
|
|
219
|
+
// Write output
|
|
220
|
+
const docsDir = path.join(outputDir, 'docs');
|
|
221
|
+
// Clean existing generated docs
|
|
222
|
+
if (await (0, node_1.directoryExists)(docsDir)) {
|
|
223
|
+
await fs.rm(docsDir, { recursive: true });
|
|
224
|
+
}
|
|
225
|
+
await fs.mkdir(docsDir, { recursive: true });
|
|
226
|
+
// Generate namespace pages
|
|
227
|
+
for (const ns of namespaces) {
|
|
228
|
+
const nsDir = path.join(docsDir, ns.namespace);
|
|
229
|
+
await fs.mkdir(nsDir, { recursive: true });
|
|
230
|
+
if (ns.actions.length > 0) {
|
|
231
|
+
await fs.writeFile(path.join(nsDir, 'actions.md'), (0, markdown_1.generateNamespacePage)(ns, 'action', repoBaseUrl));
|
|
232
|
+
}
|
|
233
|
+
if (ns.events.length > 0) {
|
|
234
|
+
await fs.writeFile(path.join(nsDir, 'events.md'), (0, markdown_1.generateNamespacePage)(ns, 'event', repoBaseUrl));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Generate index page
|
|
238
|
+
await fs.writeFile(path.join(docsDir, 'index.md'), (0, markdown_1.generateIndexPage)(namespaces));
|
|
239
|
+
// Generate sidebars
|
|
240
|
+
await fs.writeFile(path.join(outputDir, 'sidebars.ts'), (0, markdown_1.generateSidebars)(namespaces));
|
|
241
|
+
const totalActions = namespaces.reduce((sum, ns) => sum + ns.actions.length, 0);
|
|
242
|
+
const totalEvents = namespaces.reduce((sum, ns) => sum + ns.events.length, 0);
|
|
243
|
+
console.log(`Generated docs for ${namespaces.length} namespaces.`);
|
|
244
|
+
console.log(` Actions: ${totalActions}`);
|
|
245
|
+
console.log(` Events: ${totalEvents}`);
|
|
246
|
+
console.log(`Output: ${docsDir}/`);
|
|
247
|
+
return {
|
|
248
|
+
namespaces: namespaces.length,
|
|
249
|
+
actions: totalActions,
|
|
250
|
+
events: totalEvents,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
exports.generate = generate;
|
|
254
|
+
//# sourceMappingURL=generate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.cjs","sourceRoot":"","sources":["../../src/docs/generate.ts"],"names":[],"mappings":"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","sourcesContent":["import { directoryExists } from '@metamask/utils/node';\nimport { execFile } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { findDtsFiles, findTsFiles } from './discovery';\nimport { extractFromFile } from './extraction';\nimport {\n generateIndexPage,\n generateNamespacePage,\n generateSidebars,\n} from './markdown';\nimport type { MessengerItemDoc, NamespaceGroup } from './types';\n\n/**\n * Compute a deduplication score for a messenger item, preferring items with\n * JSDoc and from the \"home\" package whose name matches the namespace.\n *\n * @param item - The messenger item to score.\n * @returns A numeric score (higher is better).\n */\nfunction deduplicationScore(item: MessengerItemDoc): number {\n const jsDocScore = item.jsDoc ? 2 : 0;\n const namespacePrefix = item.typeString\n .split(':')[0]\n .replace(/Controller|Service/u, '')\n .toLowerCase();\n const homeScore = item.sourceFile.includes(namespacePrefix) ? 1 : 0;\n return jsDocScore + homeScore;\n}\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolve the GitHub blob base URL for a project by reading its git remote.\n *\n * @param projectPath - Absolute path to the project root.\n * @returns A base URL like \"https://github.com/Owner/Repo/blob/main/\" or null.\n */\nasync function resolveRepoBaseUrl(projectPath: string): Promise<string | null> {\n try {\n const { stdout: remoteRaw } = await execFileAsync(\n 'git',\n ['remote', 'get-url', 'origin'],\n { cwd: projectPath },\n );\n\n const remote = remoteRaw.trim();\n\n // Parse owner/repo from SSH or HTTPS remote URLs\n // Handles aliases like github.com-Org used in SSH configs\n const match = remote.match(\n /github\\.com[^:/]*[:/]([^/]+\\/[^/]+?)(?:\\.git)?$/u,\n );\n if (!match) {\n return null;\n }\n\n return `https://github.com/${match[1]}/blob/main/`;\n } catch {\n return null;\n }\n}\n\n/**\n * Options for the generate function.\n */\nexport type GenerateOptions = {\n /** Absolute path to the project to scan. */\n projectPath: string;\n /** Absolute path to the output directory for generated docs. */\n outputDir: string;\n /** Directories (relative to projectPath) to scan for .ts source files. */\n scanDirs: string[];\n};\n\n/**\n * Result returned by the generate function.\n */\nexport type GenerateResult = {\n namespaces: number;\n actions: number;\n events: number;\n};\n\n/**\n * Scan a project for messenger action/event types and generate documentation.\n *\n * @param options - Generation options.\n * @returns A promise resolving to counts of generated namespaces, actions, and events.\n */\nexport async function generate(\n options: GenerateOptions,\n): Promise<GenerateResult> {\n const { projectPath, outputDir, scanDirs } = options;\n\n const allItems: MessengerItemDoc[] = [];\n\n // Check which sources are available\n const existingScanDirs: string[] = [];\n for (const dir of scanDirs) {\n const abs = path.join(projectPath, dir);\n if (await directoryExists(abs)) {\n existingScanDirs.push(dir);\n }\n }\n const packagesDir = path.join(projectPath, 'packages');\n const hasPackages = await directoryExists(packagesDir);\n const nmDir = path.join(projectPath, 'node_modules', '@metamask');\n const hasNodeModules = await directoryExists(nmDir);\n\n const sources: string[] = [];\n for (const dir of existingScanDirs) {\n sources.push(`${dir}/ (.ts)`);\n }\n if (hasPackages) {\n sources.push('packages/*/src (.ts)');\n }\n if (hasNodeModules) {\n sources.push('node_modules/@metamask/*/dist (.d.cts)');\n }\n console.log(\n `Scanning ${sources.join(', ')} for Messenger action/event types...`,\n );\n\n // Scan configured source directories for .ts files\n for (const dir of existingScanDirs) {\n const abs = path.join(projectPath, dir);\n const tsFiles = await findTsFiles(abs);\n for (const file of tsFiles) {\n try {\n const items = await extractFromFile(file, projectPath);\n allItems.push(...items);\n } catch (error) {\n console.warn(\n `Warning: failed to parse ${path.relative(\n projectPath,\n file,\n )}: ${String(error)}`,\n );\n }\n }\n }\n\n // Scan packages/*/src for .ts source files (monorepo)\n if (hasPackages) {\n const entries = await fs.readdir(packagesDir, { withFileTypes: true });\n const packageDirs = entries\n .filter((dirent) => dirent.isDirectory())\n .map((dirent) => path.join(packagesDir, dirent.name, 'src'));\n\n for (const srcDir of packageDirs) {\n if (!(await directoryExists(srcDir))) {\n continue;\n }\n\n const tsFiles = await findTsFiles(srcDir);\n for (const file of tsFiles) {\n try {\n const items = await extractFromFile(file, projectPath);\n allItems.push(...items);\n } catch (error) {\n console.warn(\n `Warning: failed to parse ${path.relative(\n projectPath,\n file,\n )}: ${String(error)}`,\n );\n }\n }\n }\n }\n\n // Scan node_modules/@metamask/*/dist for .d.cts declaration files\n if (hasNodeModules) {\n const entries = await fs.readdir(nmDir, { withFileTypes: true });\n const pkgDirs = entries\n .filter((dirent) => dirent.isDirectory() || dirent.isSymbolicLink())\n .map((dirent) => path.join(nmDir, dirent.name, 'dist'));\n\n for (const distDir of pkgDirs) {\n if (!(await directoryExists(distDir))) {\n continue;\n }\n\n const dtsFiles = await findDtsFiles(distDir);\n for (const file of dtsFiles) {\n try {\n const items = await extractFromFile(file, projectPath);\n allItems.push(...items);\n } catch (error) {\n console.warn(\n `Warning: failed to parse ${path.relative(\n projectPath,\n file,\n )}: ${String(error)}`,\n );\n }\n }\n }\n }\n\n if (existingScanDirs.length === 0 && !hasPackages && !hasNodeModules) {\n throw new Error(\n `No scannable directories found in ${projectPath}. ` +\n `Looked for: ${scanDirs.join(', ')}, packages/, node_modules/@metamask/`,\n );\n }\n\n console.log(`Found ${allItems.length} messenger items total.`);\n\n // Group by namespace (part before the colon), deduplicating by typeString.\n // When duplicates exist, prefer the one with JSDoc, or from the package\n // whose name matches the namespace.\n const byNamespace = new Map<string, NamespaceGroup>();\n const seen = new Map<string, MessengerItemDoc>(); // key: typeString\n\n for (const item of allItems) {\n const existing = seen.get(item.typeString);\n if (existing) {\n // Prefer item with JSDoc, or from the \"home\" package\n const existingScore = deduplicationScore(existing);\n const newScore = deduplicationScore(item);\n if (newScore <= existingScore) {\n continue;\n }\n // Replace existing with better item\n const ns = item.typeString.split(':')[0];\n const group = byNamespace.get(ns);\n if (group) {\n const list = existing.kind === 'action' ? group.actions : group.events;\n const idx = list.indexOf(existing);\n if (idx !== -1) {\n list[idx] = item;\n }\n }\n seen.set(item.typeString, item);\n continue;\n }\n\n seen.set(item.typeString, item);\n const ns = item.typeString.split(':')[0];\n if (!byNamespace.has(ns)) {\n byNamespace.set(ns, { namespace: ns, actions: [], events: [] });\n }\n const group = byNamespace.get(ns);\n if (group) {\n if (item.kind === 'action') {\n group.actions.push(item);\n } else {\n group.events.push(item);\n }\n }\n }\n\n // Sort namespaces alphabetically, sort items within each namespace\n const namespaces = Array.from(byNamespace.values()).sort((a, b) =>\n a.namespace.localeCompare(b.namespace),\n );\n\n for (const ns of namespaces) {\n ns.actions.sort((a, b) => a.typeString.localeCompare(b.typeString));\n ns.events.sort((a, b) => a.typeString.localeCompare(b.typeString));\n }\n\n // Resolve repository base URL for source links\n const repoBaseUrl = await resolveRepoBaseUrl(projectPath);\n\n // Write output\n const docsDir = path.join(outputDir, 'docs');\n\n // Clean existing generated docs\n if (await directoryExists(docsDir)) {\n await fs.rm(docsDir, { recursive: true });\n }\n await fs.mkdir(docsDir, { recursive: true });\n\n // Generate namespace pages\n for (const ns of namespaces) {\n const nsDir = path.join(docsDir, ns.namespace);\n await fs.mkdir(nsDir, { recursive: true });\n\n if (ns.actions.length > 0) {\n await fs.writeFile(\n path.join(nsDir, 'actions.md'),\n generateNamespacePage(ns, 'action', repoBaseUrl),\n );\n }\n\n if (ns.events.length > 0) {\n await fs.writeFile(\n path.join(nsDir, 'events.md'),\n generateNamespacePage(ns, 'event', repoBaseUrl),\n );\n }\n }\n\n // Generate index page\n await fs.writeFile(\n path.join(docsDir, 'index.md'),\n generateIndexPage(namespaces),\n );\n\n // Generate sidebars\n await fs.writeFile(\n path.join(outputDir, 'sidebars.ts'),\n generateSidebars(namespaces),\n );\n\n const totalActions = namespaces.reduce(\n (sum, ns) => sum + ns.actions.length,\n 0,\n );\n const totalEvents = namespaces.reduce((sum, ns) => sum + ns.events.length, 0);\n\n console.log(`Generated docs for ${namespaces.length} namespaces.`);\n console.log(` Actions: ${totalActions}`);\n console.log(` Events: ${totalEvents}`);\n console.log(`Output: ${docsDir}/`);\n\n return {\n namespaces: namespaces.length,\n actions: totalActions,\n events: totalEvents,\n };\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for the generate function.
|
|
3
|
+
*/
|
|
4
|
+
export type GenerateOptions = {
|
|
5
|
+
/** Absolute path to the project to scan. */
|
|
6
|
+
projectPath: string;
|
|
7
|
+
/** Absolute path to the output directory for generated docs. */
|
|
8
|
+
outputDir: string;
|
|
9
|
+
/** Directories (relative to projectPath) to scan for .ts source files. */
|
|
10
|
+
scanDirs: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Result returned by the generate function.
|
|
14
|
+
*/
|
|
15
|
+
export type GenerateResult = {
|
|
16
|
+
namespaces: number;
|
|
17
|
+
actions: number;
|
|
18
|
+
events: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Scan a project for messenger action/event types and generate documentation.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Generation options.
|
|
24
|
+
* @returns A promise resolving to counts of generated namespaces, actions, and events.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generate(options: GenerateOptions): Promise<GenerateResult>;
|
|
27
|
+
//# sourceMappingURL=generate.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.cts","sourceRoot":"","sources":["../../src/docs/generate.ts"],"names":[],"mappings":"AAiEA;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAwOzB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for the generate function.
|
|
3
|
+
*/
|
|
4
|
+
export type GenerateOptions = {
|
|
5
|
+
/** Absolute path to the project to scan. */
|
|
6
|
+
projectPath: string;
|
|
7
|
+
/** Absolute path to the output directory for generated docs. */
|
|
8
|
+
outputDir: string;
|
|
9
|
+
/** Directories (relative to projectPath) to scan for .ts source files. */
|
|
10
|
+
scanDirs: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Result returned by the generate function.
|
|
14
|
+
*/
|
|
15
|
+
export type GenerateResult = {
|
|
16
|
+
namespaces: number;
|
|
17
|
+
actions: number;
|
|
18
|
+
events: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Scan a project for messenger action/event types and generate documentation.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Generation options.
|
|
24
|
+
* @returns A promise resolving to counts of generated namespaces, actions, and events.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generate(options: GenerateOptions): Promise<GenerateResult>;
|
|
27
|
+
//# sourceMappingURL=generate.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.mts","sourceRoot":"","sources":["../../src/docs/generate.ts"],"names":[],"mappings":"AAiEA;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAwOzB"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { directoryExists } from "@metamask/utils/node";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { findDtsFiles, findTsFiles } from "./discovery.mjs";
|
|
7
|
+
import { extractFromFile } from "./extraction.mjs";
|
|
8
|
+
import { generateIndexPage, generateNamespacePage, generateSidebars } from "./markdown.mjs";
|
|
9
|
+
/**
|
|
10
|
+
* Compute a deduplication score for a messenger item, preferring items with
|
|
11
|
+
* JSDoc and from the "home" package whose name matches the namespace.
|
|
12
|
+
*
|
|
13
|
+
* @param item - The messenger item to score.
|
|
14
|
+
* @returns A numeric score (higher is better).
|
|
15
|
+
*/
|
|
16
|
+
function deduplicationScore(item) {
|
|
17
|
+
const jsDocScore = item.jsDoc ? 2 : 0;
|
|
18
|
+
const namespacePrefix = item.typeString
|
|
19
|
+
.split(':')[0]
|
|
20
|
+
.replace(/Controller|Service/u, '')
|
|
21
|
+
.toLowerCase();
|
|
22
|
+
const homeScore = item.sourceFile.includes(namespacePrefix) ? 1 : 0;
|
|
23
|
+
return jsDocScore + homeScore;
|
|
24
|
+
}
|
|
25
|
+
const execFileAsync = promisify(execFile);
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the GitHub blob base URL for a project by reading its git remote.
|
|
28
|
+
*
|
|
29
|
+
* @param projectPath - Absolute path to the project root.
|
|
30
|
+
* @returns A base URL like "https://github.com/Owner/Repo/blob/main/" or null.
|
|
31
|
+
*/
|
|
32
|
+
async function resolveRepoBaseUrl(projectPath) {
|
|
33
|
+
try {
|
|
34
|
+
const { stdout: remoteRaw } = await execFileAsync('git', ['remote', 'get-url', 'origin'], { cwd: projectPath });
|
|
35
|
+
const remote = remoteRaw.trim();
|
|
36
|
+
// Parse owner/repo from SSH or HTTPS remote URLs
|
|
37
|
+
// Handles aliases like github.com-Org used in SSH configs
|
|
38
|
+
const match = remote.match(/github\.com[^:/]*[:/]([^/]+\/[^/]+?)(?:\.git)?$/u);
|
|
39
|
+
if (!match) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return `https://github.com/${match[1]}/blob/main/`;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Scan a project for messenger action/event types and generate documentation.
|
|
50
|
+
*
|
|
51
|
+
* @param options - Generation options.
|
|
52
|
+
* @returns A promise resolving to counts of generated namespaces, actions, and events.
|
|
53
|
+
*/
|
|
54
|
+
export async function generate(options) {
|
|
55
|
+
const { projectPath, outputDir, scanDirs } = options;
|
|
56
|
+
const allItems = [];
|
|
57
|
+
// Check which sources are available
|
|
58
|
+
const existingScanDirs = [];
|
|
59
|
+
for (const dir of scanDirs) {
|
|
60
|
+
const abs = path.join(projectPath, dir);
|
|
61
|
+
if (await directoryExists(abs)) {
|
|
62
|
+
existingScanDirs.push(dir);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const packagesDir = path.join(projectPath, 'packages');
|
|
66
|
+
const hasPackages = await directoryExists(packagesDir);
|
|
67
|
+
const nmDir = path.join(projectPath, 'node_modules', '@metamask');
|
|
68
|
+
const hasNodeModules = await directoryExists(nmDir);
|
|
69
|
+
const sources = [];
|
|
70
|
+
for (const dir of existingScanDirs) {
|
|
71
|
+
sources.push(`${dir}/ (.ts)`);
|
|
72
|
+
}
|
|
73
|
+
if (hasPackages) {
|
|
74
|
+
sources.push('packages/*/src (.ts)');
|
|
75
|
+
}
|
|
76
|
+
if (hasNodeModules) {
|
|
77
|
+
sources.push('node_modules/@metamask/*/dist (.d.cts)');
|
|
78
|
+
}
|
|
79
|
+
console.log(`Scanning ${sources.join(', ')} for Messenger action/event types...`);
|
|
80
|
+
// Scan configured source directories for .ts files
|
|
81
|
+
for (const dir of existingScanDirs) {
|
|
82
|
+
const abs = path.join(projectPath, dir);
|
|
83
|
+
const tsFiles = await findTsFiles(abs);
|
|
84
|
+
for (const file of tsFiles) {
|
|
85
|
+
try {
|
|
86
|
+
const items = await extractFromFile(file, projectPath);
|
|
87
|
+
allItems.push(...items);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.warn(`Warning: failed to parse ${path.relative(projectPath, file)}: ${String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Scan packages/*/src for .ts source files (monorepo)
|
|
95
|
+
if (hasPackages) {
|
|
96
|
+
const entries = await fs.readdir(packagesDir, { withFileTypes: true });
|
|
97
|
+
const packageDirs = entries
|
|
98
|
+
.filter((dirent) => dirent.isDirectory())
|
|
99
|
+
.map((dirent) => path.join(packagesDir, dirent.name, 'src'));
|
|
100
|
+
for (const srcDir of packageDirs) {
|
|
101
|
+
if (!(await directoryExists(srcDir))) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const tsFiles = await findTsFiles(srcDir);
|
|
105
|
+
for (const file of tsFiles) {
|
|
106
|
+
try {
|
|
107
|
+
const items = await extractFromFile(file, projectPath);
|
|
108
|
+
allItems.push(...items);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.warn(`Warning: failed to parse ${path.relative(projectPath, file)}: ${String(error)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Scan node_modules/@metamask/*/dist for .d.cts declaration files
|
|
117
|
+
if (hasNodeModules) {
|
|
118
|
+
const entries = await fs.readdir(nmDir, { withFileTypes: true });
|
|
119
|
+
const pkgDirs = entries
|
|
120
|
+
.filter((dirent) => dirent.isDirectory() || dirent.isSymbolicLink())
|
|
121
|
+
.map((dirent) => path.join(nmDir, dirent.name, 'dist'));
|
|
122
|
+
for (const distDir of pkgDirs) {
|
|
123
|
+
if (!(await directoryExists(distDir))) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const dtsFiles = await findDtsFiles(distDir);
|
|
127
|
+
for (const file of dtsFiles) {
|
|
128
|
+
try {
|
|
129
|
+
const items = await extractFromFile(file, projectPath);
|
|
130
|
+
allItems.push(...items);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.warn(`Warning: failed to parse ${path.relative(projectPath, file)}: ${String(error)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (existingScanDirs.length === 0 && !hasPackages && !hasNodeModules) {
|
|
139
|
+
throw new Error(`No scannable directories found in ${projectPath}. ` +
|
|
140
|
+
`Looked for: ${scanDirs.join(', ')}, packages/, node_modules/@metamask/`);
|
|
141
|
+
}
|
|
142
|
+
console.log(`Found ${allItems.length} messenger items total.`);
|
|
143
|
+
// Group by namespace (part before the colon), deduplicating by typeString.
|
|
144
|
+
// When duplicates exist, prefer the one with JSDoc, or from the package
|
|
145
|
+
// whose name matches the namespace.
|
|
146
|
+
const byNamespace = new Map();
|
|
147
|
+
const seen = new Map(); // key: typeString
|
|
148
|
+
for (const item of allItems) {
|
|
149
|
+
const existing = seen.get(item.typeString);
|
|
150
|
+
if (existing) {
|
|
151
|
+
// Prefer item with JSDoc, or from the "home" package
|
|
152
|
+
const existingScore = deduplicationScore(existing);
|
|
153
|
+
const newScore = deduplicationScore(item);
|
|
154
|
+
if (newScore <= existingScore) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// Replace existing with better item
|
|
158
|
+
const ns = item.typeString.split(':')[0];
|
|
159
|
+
const group = byNamespace.get(ns);
|
|
160
|
+
if (group) {
|
|
161
|
+
const list = existing.kind === 'action' ? group.actions : group.events;
|
|
162
|
+
const idx = list.indexOf(existing);
|
|
163
|
+
if (idx !== -1) {
|
|
164
|
+
list[idx] = item;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
seen.set(item.typeString, item);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
seen.set(item.typeString, item);
|
|
171
|
+
const ns = item.typeString.split(':')[0];
|
|
172
|
+
if (!byNamespace.has(ns)) {
|
|
173
|
+
byNamespace.set(ns, { namespace: ns, actions: [], events: [] });
|
|
174
|
+
}
|
|
175
|
+
const group = byNamespace.get(ns);
|
|
176
|
+
if (group) {
|
|
177
|
+
if (item.kind === 'action') {
|
|
178
|
+
group.actions.push(item);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
group.events.push(item);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Sort namespaces alphabetically, sort items within each namespace
|
|
186
|
+
const namespaces = Array.from(byNamespace.values()).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
187
|
+
for (const ns of namespaces) {
|
|
188
|
+
ns.actions.sort((a, b) => a.typeString.localeCompare(b.typeString));
|
|
189
|
+
ns.events.sort((a, b) => a.typeString.localeCompare(b.typeString));
|
|
190
|
+
}
|
|
191
|
+
// Resolve repository base URL for source links
|
|
192
|
+
const repoBaseUrl = await resolveRepoBaseUrl(projectPath);
|
|
193
|
+
// Write output
|
|
194
|
+
const docsDir = path.join(outputDir, 'docs');
|
|
195
|
+
// Clean existing generated docs
|
|
196
|
+
if (await directoryExists(docsDir)) {
|
|
197
|
+
await fs.rm(docsDir, { recursive: true });
|
|
198
|
+
}
|
|
199
|
+
await fs.mkdir(docsDir, { recursive: true });
|
|
200
|
+
// Generate namespace pages
|
|
201
|
+
for (const ns of namespaces) {
|
|
202
|
+
const nsDir = path.join(docsDir, ns.namespace);
|
|
203
|
+
await fs.mkdir(nsDir, { recursive: true });
|
|
204
|
+
if (ns.actions.length > 0) {
|
|
205
|
+
await fs.writeFile(path.join(nsDir, 'actions.md'), generateNamespacePage(ns, 'action', repoBaseUrl));
|
|
206
|
+
}
|
|
207
|
+
if (ns.events.length > 0) {
|
|
208
|
+
await fs.writeFile(path.join(nsDir, 'events.md'), generateNamespacePage(ns, 'event', repoBaseUrl));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Generate index page
|
|
212
|
+
await fs.writeFile(path.join(docsDir, 'index.md'), generateIndexPage(namespaces));
|
|
213
|
+
// Generate sidebars
|
|
214
|
+
await fs.writeFile(path.join(outputDir, 'sidebars.ts'), generateSidebars(namespaces));
|
|
215
|
+
const totalActions = namespaces.reduce((sum, ns) => sum + ns.actions.length, 0);
|
|
216
|
+
const totalEvents = namespaces.reduce((sum, ns) => sum + ns.events.length, 0);
|
|
217
|
+
console.log(`Generated docs for ${namespaces.length} namespaces.`);
|
|
218
|
+
console.log(` Actions: ${totalActions}`);
|
|
219
|
+
console.log(` Events: ${totalEvents}`);
|
|
220
|
+
console.log(`Output: ${docsDir}/`);
|
|
221
|
+
return {
|
|
222
|
+
namespaces: namespaces.length,
|
|
223
|
+
actions: totalActions,
|
|
224
|
+
events: totalEvents,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=generate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.mjs","sourceRoot":"","sources":["../../src/docs/generate.ts"],"names":[],"mappings":"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","sourcesContent":["import { directoryExists } from '@metamask/utils/node';\nimport { execFile } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { findDtsFiles, findTsFiles } from './discovery';\nimport { extractFromFile } from './extraction';\nimport {\n generateIndexPage,\n generateNamespacePage,\n generateSidebars,\n} from './markdown';\nimport type { MessengerItemDoc, NamespaceGroup } from './types';\n\n/**\n * Compute a deduplication score for a messenger item, preferring items with\n * JSDoc and from the \"home\" package whose name matches the namespace.\n *\n * @param item - The messenger item to score.\n * @returns A numeric score (higher is better).\n */\nfunction deduplicationScore(item: MessengerItemDoc): number {\n const jsDocScore = item.jsDoc ? 2 : 0;\n const namespacePrefix = item.typeString\n .split(':')[0]\n .replace(/Controller|Service/u, '')\n .toLowerCase();\n const homeScore = item.sourceFile.includes(namespacePrefix) ? 1 : 0;\n return jsDocScore + homeScore;\n}\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolve the GitHub blob base URL for a project by reading its git remote.\n *\n * @param projectPath - Absolute path to the project root.\n * @returns A base URL like \"https://github.com/Owner/Repo/blob/main/\" or null.\n */\nasync function resolveRepoBaseUrl(projectPath: string): Promise<string | null> {\n try {\n const { stdout: remoteRaw } = await execFileAsync(\n 'git',\n ['remote', 'get-url', 'origin'],\n { cwd: projectPath },\n );\n\n const remote = remoteRaw.trim();\n\n // Parse owner/repo from SSH or HTTPS remote URLs\n // Handles aliases like github.com-Org used in SSH configs\n const match = remote.match(\n /github\\.com[^:/]*[:/]([^/]+\\/[^/]+?)(?:\\.git)?$/u,\n );\n if (!match) {\n return null;\n }\n\n return `https://github.com/${match[1]}/blob/main/`;\n } catch {\n return null;\n }\n}\n\n/**\n * Options for the generate function.\n */\nexport type GenerateOptions = {\n /** Absolute path to the project to scan. */\n projectPath: string;\n /** Absolute path to the output directory for generated docs. */\n outputDir: string;\n /** Directories (relative to projectPath) to scan for .ts source files. */\n scanDirs: string[];\n};\n\n/**\n * Result returned by the generate function.\n */\nexport type GenerateResult = {\n namespaces: number;\n actions: number;\n events: number;\n};\n\n/**\n * Scan a project for messenger action/event types and generate documentation.\n *\n * @param options - Generation options.\n * @returns A promise resolving to counts of generated namespaces, actions, and events.\n */\nexport async function generate(\n options: GenerateOptions,\n): Promise<GenerateResult> {\n const { projectPath, outputDir, scanDirs } = options;\n\n const allItems: MessengerItemDoc[] = [];\n\n // Check which sources are available\n const existingScanDirs: string[] = [];\n for (const dir of scanDirs) {\n const abs = path.join(projectPath, dir);\n if (await directoryExists(abs)) {\n existingScanDirs.push(dir);\n }\n }\n const packagesDir = path.join(projectPath, 'packages');\n const hasPackages = await directoryExists(packagesDir);\n const nmDir = path.join(projectPath, 'node_modules', '@metamask');\n const hasNodeModules = await directoryExists(nmDir);\n\n const sources: string[] = [];\n for (const dir of existingScanDirs) {\n sources.push(`${dir}/ (.ts)`);\n }\n if (hasPackages) {\n sources.push('packages/*/src (.ts)');\n }\n if (hasNodeModules) {\n sources.push('node_modules/@metamask/*/dist (.d.cts)');\n }\n console.log(\n `Scanning ${sources.join(', ')} for Messenger action/event types...`,\n );\n\n // Scan configured source directories for .ts files\n for (const dir of existingScanDirs) {\n const abs = path.join(projectPath, dir);\n const tsFiles = await findTsFiles(abs);\n for (const file of tsFiles) {\n try {\n const items = await extractFromFile(file, projectPath);\n allItems.push(...items);\n } catch (error) {\n console.warn(\n `Warning: failed to parse ${path.relative(\n projectPath,\n file,\n )}: ${String(error)}`,\n );\n }\n }\n }\n\n // Scan packages/*/src for .ts source files (monorepo)\n if (hasPackages) {\n const entries = await fs.readdir(packagesDir, { withFileTypes: true });\n const packageDirs = entries\n .filter((dirent) => dirent.isDirectory())\n .map((dirent) => path.join(packagesDir, dirent.name, 'src'));\n\n for (const srcDir of packageDirs) {\n if (!(await directoryExists(srcDir))) {\n continue;\n }\n\n const tsFiles = await findTsFiles(srcDir);\n for (const file of tsFiles) {\n try {\n const items = await extractFromFile(file, projectPath);\n allItems.push(...items);\n } catch (error) {\n console.warn(\n `Warning: failed to parse ${path.relative(\n projectPath,\n file,\n )}: ${String(error)}`,\n );\n }\n }\n }\n }\n\n // Scan node_modules/@metamask/*/dist for .d.cts declaration files\n if (hasNodeModules) {\n const entries = await fs.readdir(nmDir, { withFileTypes: true });\n const pkgDirs = entries\n .filter((dirent) => dirent.isDirectory() || dirent.isSymbolicLink())\n .map((dirent) => path.join(nmDir, dirent.name, 'dist'));\n\n for (const distDir of pkgDirs) {\n if (!(await directoryExists(distDir))) {\n continue;\n }\n\n const dtsFiles = await findDtsFiles(distDir);\n for (const file of dtsFiles) {\n try {\n const items = await extractFromFile(file, projectPath);\n allItems.push(...items);\n } catch (error) {\n console.warn(\n `Warning: failed to parse ${path.relative(\n projectPath,\n file,\n )}: ${String(error)}`,\n );\n }\n }\n }\n }\n\n if (existingScanDirs.length === 0 && !hasPackages && !hasNodeModules) {\n throw new Error(\n `No scannable directories found in ${projectPath}. ` +\n `Looked for: ${scanDirs.join(', ')}, packages/, node_modules/@metamask/`,\n );\n }\n\n console.log(`Found ${allItems.length} messenger items total.`);\n\n // Group by namespace (part before the colon), deduplicating by typeString.\n // When duplicates exist, prefer the one with JSDoc, or from the package\n // whose name matches the namespace.\n const byNamespace = new Map<string, NamespaceGroup>();\n const seen = new Map<string, MessengerItemDoc>(); // key: typeString\n\n for (const item of allItems) {\n const existing = seen.get(item.typeString);\n if (existing) {\n // Prefer item with JSDoc, or from the \"home\" package\n const existingScore = deduplicationScore(existing);\n const newScore = deduplicationScore(item);\n if (newScore <= existingScore) {\n continue;\n }\n // Replace existing with better item\n const ns = item.typeString.split(':')[0];\n const group = byNamespace.get(ns);\n if (group) {\n const list = existing.kind === 'action' ? group.actions : group.events;\n const idx = list.indexOf(existing);\n if (idx !== -1) {\n list[idx] = item;\n }\n }\n seen.set(item.typeString, item);\n continue;\n }\n\n seen.set(item.typeString, item);\n const ns = item.typeString.split(':')[0];\n if (!byNamespace.has(ns)) {\n byNamespace.set(ns, { namespace: ns, actions: [], events: [] });\n }\n const group = byNamespace.get(ns);\n if (group) {\n if (item.kind === 'action') {\n group.actions.push(item);\n } else {\n group.events.push(item);\n }\n }\n }\n\n // Sort namespaces alphabetically, sort items within each namespace\n const namespaces = Array.from(byNamespace.values()).sort((a, b) =>\n a.namespace.localeCompare(b.namespace),\n );\n\n for (const ns of namespaces) {\n ns.actions.sort((a, b) => a.typeString.localeCompare(b.typeString));\n ns.events.sort((a, b) => a.typeString.localeCompare(b.typeString));\n }\n\n // Resolve repository base URL for source links\n const repoBaseUrl = await resolveRepoBaseUrl(projectPath);\n\n // Write output\n const docsDir = path.join(outputDir, 'docs');\n\n // Clean existing generated docs\n if (await directoryExists(docsDir)) {\n await fs.rm(docsDir, { recursive: true });\n }\n await fs.mkdir(docsDir, { recursive: true });\n\n // Generate namespace pages\n for (const ns of namespaces) {\n const nsDir = path.join(docsDir, ns.namespace);\n await fs.mkdir(nsDir, { recursive: true });\n\n if (ns.actions.length > 0) {\n await fs.writeFile(\n path.join(nsDir, 'actions.md'),\n generateNamespacePage(ns, 'action', repoBaseUrl),\n );\n }\n\n if (ns.events.length > 0) {\n await fs.writeFile(\n path.join(nsDir, 'events.md'),\n generateNamespacePage(ns, 'event', repoBaseUrl),\n );\n }\n }\n\n // Generate index page\n await fs.writeFile(\n path.join(docsDir, 'index.md'),\n generateIndexPage(namespaces),\n );\n\n // Generate sidebars\n await fs.writeFile(\n path.join(outputDir, 'sidebars.ts'),\n generateSidebars(namespaces),\n );\n\n const totalActions = namespaces.reduce(\n (sum, ns) => sum + ns.actions.length,\n 0,\n );\n const totalEvents = namespaces.reduce((sum, ns) => sum + ns.events.length, 0);\n\n console.log(`Generated docs for ${namespaces.length} namespaces.`);\n console.log(` Actions: ${totalActions}`);\n console.log(` Events: ${totalEvents}`);\n console.log(`Output: ${docsDir}/`);\n\n return {\n namespaces: namespaces.length,\n actions: totalActions,\n events: totalEvents,\n };\n}\n"]}
|