forked from microsoft/FluidFramework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
api-markdown-documenter.js
220 lines (193 loc) · 7.84 KB
/
api-markdown-documenter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
const {
emitMarkdown,
getLinkUrlForApiItem,
getUnscopedPackageName,
loadModel,
markdownDocumenterConfigurationWithDefaults,
MarkdownEmitter,
renderDocuments,
DefaultPolicies
} = require("@fluid-tools/api-markdown-documenter");
const { StringBuilder } = require("@microsoft/tsdoc");
const { ApiItemKind } = require("@microsoft/api-extractor-model");
const chalk = require("chalk");
const fs = require("fs-extra");
const path = require("path");
const os = require("os");
const HugoMarkdownEmitter = require('./markdown-emitter')
const apiReportsDirectoryPath = path.resolve(__dirname, "_api-extractor-temp", "_build");
const apiDocsDirectoryPath = path.resolve(__dirname, "content", "docs", "apis");
const generatedContentNotice = "[//]: # (Do not edit this file. It is automatically generated by @fluidtools/api-markdown-documenter.)";
/**
* Creates Hugo front-matter for the given API item.
* This will be appended to the top of the generated API documents.
*
* @param {ApiItem} apiItem - The root API item of the document being rendered.
* @param {MarkdownDocumenterConfiguration} config - See
* {@link @fluid-tools/api-markdown-documenter#MarkdownDocumenterConfiguration}.
*
* @returns The JSON-formatted Hugo front-matter as a `string`.
*/
function frontMatterFromApiItem(apiItem, config) {
function extractSummary(docComment) {
const stringBuilder = new StringBuilder();
const summary = docComment.summarySection;
new MarkdownEmitter(config.apiModel).emit(stringBuilder, summary, {
contextApiItem: apiItem,
getLinkUrlApiItem: (apiItemForFilename) => {
return getLinkUrlForApiItem(apiItemForFilename, config);
}
});
return stringBuilder.toString().replace(/"/g, "'").trim();
}
const frontMatter = {};
frontMatter.title = apiItem.displayName.replace(/"/g, '').replace(/!/g, '');
let apiMembers = apiItem.members;
switch (apiItem.kind) {
case ApiItemKind.Model:
frontMatter.title = "Package Reference";
break;
case ApiItemKind.Class:
if (apiItem.tsdocComment) {
frontMatter.summary = extractSummary(apiItem.tsdocComment);
}
frontMatter.title += " Class";
break;
case ApiItemKind.Interface:
frontMatter.title += " Interface";
if (apiItem.tsdocComment) {
frontMatter.summary = extractSummary(apiItem.tsdocComment);
}
break;
case ApiItemKind.Package:
frontMatter.title += " Package";
apiMembers = apiItem.entryPoints[0].members;
if (apiItem.tsdocComment) {
frontMatter.summary = extractSummary(apiItem.tsdocComment);
}
break;
case ApiItemKind.Namespace:
frontMatter.title += " Namespace";
apiMembers = apiItem.members;
if (apiItem.tsdocComment) {
frontMatter.summary = extractSummary(apiItem.tsdocComment);
}
break;
default:
break;
}
frontMatter.kind = apiItem.kind;
frontMatter.members = new Map();
apiMembers.forEach(element => {
if (element.displayName === "") {
return;
}
if (!frontMatter.members[element.kind]) {
frontMatter.members[element.kind] = {};
}
frontMatter.members[element.kind][element.displayName] = getLinkUrlForApiItem(element, config);
});
const associatedPackage = apiItem.getAssociatedPackage();
if (associatedPackage) {
frontMatter.package = associatedPackage.name.replace(/"/g, '').replace(/!/g, '');
frontMatter.unscopedPackageName = getUnscopedPackageName(associatedPackage);
} else {
frontMatter.package = "undefined";
}
return JSON.stringify(frontMatter, undefined, 2).trim();
}
async function main() {
// Delete existing documentation output
console.log("Removing existing generated API docs...");
await fs.ensureDir(apiDocsDirectoryPath);
await fs.emptyDir(apiDocsDirectoryPath);
// Process API reports
console.group();
const apiModel = await loadModel(apiReportsDirectoryPath);
console.groupEnd();
const config = markdownDocumenterConfigurationWithDefaults({
apiModel,
newlineKind: "lf",
uriRoot: "/docs/apis",
includeTopLevelDocumentHeading: false, // This will be added automatically by Hugo
fileNamePolicy: (apiItem) => {
return apiItem.kind === ApiItemKind.Model
? "index"
: DefaultPolicies.defaultFileNamePolicy(apiItem);
},
});
console.log("Generating API docs...");
console.group();
let documents;
try {
documents = renderDocuments(config);
} catch(error) {
console.error(`Encountered error while generating API documentation:`);
console.error(error);
throw error;
}
console.groupEnd();
console.log("Writing API docs...");
console.group();
await Promise.all(documents.map(async (document) => {
let filePath = path.join(apiDocsDirectoryPath, document.path);
/**
* Generates front-matter content for the document from the provided API item.
*
* @param {ApiItem} apiItem - The root API item with which the document is associated.
*
* @see {@link @fluid-tools/api-markdown-documenter#MarkdownEmitter.generateFrontMatter}.
*/
function generateFrontMatter(apiItem) {
// Generate Hugo front-matter for the API item
let hugoFrontMatter;
try {
hugoFrontMatter = frontMatterFromApiItem(document.apiItem, config);
} catch (error) {
console.error(`Encountered error while generating front-matter for "${document.apiItem.displayName}":`);
console.error(error);
throw error;
}
// Also add comment noting that the contents are generated and should not be manually edited.
return [hugoFrontMatter, generatedContentNotice].join(`${os.EOL}${os.EOL}`).trim();
}
// Emit markdown for API docs
const markdownEmitter = new HugoMarkdownEmitter(config.apiModel, generateFrontMatter);
let fileContents;
try {
fileContents = emitMarkdown(document, config, markdownEmitter);
} catch (error) {
console.error(`Encountered error while emitting markdown for "${document.apiItem.displayName}":`);
console.error(error);
throw error;
}
try {
// Hugo uses a special file-naming syntax to represent documents with "child" documents in the same
// directory. Namely, "_index.md". However, the resulting html names these modules "index", rather than
// "_index", so we cannot use the "_index" convention when generating the docs and the links between them.
// To accommodate this, we will match on "index.md" files and adjust the file name accordingly.
if(filePath.endsWith("index.md")) {
filePath = filePath.replace("index.md", "_index.md");
}
await fs.ensureFile(filePath);
await fs.writeFile(filePath, fileContents);
} catch (error) {
console.error(`Encountered error while writing file output for "${document.apiItem.displayName}":`);
console.error(error);
throw error;
}
}));
console.groupEnd();
}
main().then(() => {
console.log(chalk.green("API docs written!"));
return 0;
}, (error) => {
console.error("API docs could not be written due to an error:");
console.error(error);
return 1;
})