-
Notifications
You must be signed in to change notification settings - Fork 121
/
Copy pathbuild-typedoc.js
301 lines (284 loc) · 9.61 KB
/
build-typedoc.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
const spawn = require("cross-spawn");
const { join, resolve } = require("path");
const { readFile, writeFile } = require("fs");
const _ = require("lodash");
const OUTPUT = join(process.cwd(), "docs", "src", `typedoc.json`);
const { prettyifyUrl } = require("acetate/lib/utils.js");
const slug = require("slug");
const minimatch = require("minimatch");
const cheerio = require("cheerio");
const MarkdownIt = require("markdown-it");
const md = new MarkdownIt();
(function generateTypeDoc() {
return new Promise((resolve, reject) => {
const typedoc = spawn(
"typedoc",
[
"-json",
OUTPUT,
"--ignoreCompilerErrors",
"--module",
"common",
"--tsconfig",
"./tsconfig.json"
],
{
stdio: "inherit"
}
);
typedoc.on("close", code => {
if (code !== 0) {
reject(code);
return;
}
readFile(OUTPUT, (error, content) => {
if (error) {
reject(error);
return;
}
resolve(JSON.parse(content.toString()));
});
});
})
.then(json => {
/**
* `json.children` will be a list of all TypeScript files in our project.
* We dont care about the files we need need all their children so reduce
* this to just a general list of everything in the entire project.
*/
return json.children.reduce(
(allChildren, fileChildren) => allChildren.concat(fileChildren),
[]
);
})
.then(children => {
/**
* We dont want TypeScript files that have been symlinked into `node_modules`
* folders by Lerna so ignore any child whose source file has `node_modules`
* in its `fileName`
*/
return children.filter(
c => !minimatch(c.sources[0].fileName, "**/node_modules/**")
);
})
.then(children => {
/**
* We now only want children that are actual source files, not tests, so
* filter out all children without `src` in their path.
*/
return children.filter(c =>
minimatch(c.sources[0].fileName, "**/src/**/*.ts")
);
})
.then(children => {
/**
* Some children might be empty so there is nothing to document in them.
* For example most `index.ts` files don't have anything besides `import`
* or `export` so we can safely filter these children out.
*/
return children.filter(c => !!c.children);
})
.then(children => {
/**
* The `name` of each child is wrapped in extra quote marks remove these
* quote marks and add `.ts` back to the end of the `name`,
*/
return children.map(child => {
child.name = child.name.replace(/\"/g, "") + ".ts";
return child;
});
})
.then(children => {
/**
* Now determine which `package` each of our children belongs to based on
* its name.
*/
return children.map(child => {
child.name = _.first(child.name.split("/"));
child.package = child.name;
return child;
});
})
.then(children => {
/**
* `children` is currently a list of all TypeScript soruce files in
* `packages`. `children.children` is an array of all declarations in that
* source file. We need to concat all `children.children` arrays togather
* into a giant array of all declarations in all packages.
*/
return children.reduce((allChildren, child) => {
return allChildren.concat(
child.children.map(c => {
c.package = child.package;
return c;
})
);
}, []);
})
.then(children => {
/**
* Next we remove all children that are not exported out of their files.
*/
return children.filter(c => c.flags && c.flags.isExported);
})
.then(declarations => {
/**
* Now that we have a list of all declarations accross the entire project
* we can begin to generate additonal information about each declaration.
* For example we can now determine the `src` of the page page that will
* be generated for this declaration. Each `declaration` will also have
* `children` which we can generate and `icon` property for. These
* additonal properties, `src`, `pageUrl`, `icon` and `children` are then
* merged into the `declaration`. This also adds a `title`, `description`
* and `titleSegments` to each page which are used in the template for SEO.
*/
return declarations.map(declaration => {
const abbreviatedPackageName = declaration.package.replace("arcgis-rest-", "")
const src = `api/${abbreviatedPackageName}/${declaration.name}.html`;
let children;
if (declaration.children) {
children = declaration.children.map(child => {
child.icon = `tsd-kind-${slug(
child.kindString
).toLowerCase()} tsd-parent-kind-${slug(
declaration.kindString
).toLowerCase()}`;
if (child.flags.isPrivate) {
child.icon += ` tsd-is-private`;
}
if (child.flags.isProtected) {
child.icon += ` tsd-is-protected`;
}
if (child.flags.isStatic) {
child.icon += ` tsd-is-static`;
}
if (child.signatures) {
child.signatures = child.signatures.map(sig => {
sig.icon = child.icon;
return sig;
});
}
return child;
});
}
declaration.title = declaration.name;
declaration.titleSegments = ["API Reference"];
declaration.description =
declaration.comment && declaration.comment.shortText
? cheerio
.load(md.render(declaration.comment.shortText))
.text()
.replace("\n", "")
: "API Reference documentation for ${child.name}, part of ${declaration.package}.";
return Object.assign(declaration, {
src,
pageUrl: prettyifyUrl(src),
icon: `tsd-kind-${slug(declaration.kindString).toLowerCase()}`,
children
});
});
})
.then(declarations => {
/**
* We now have a list of `declarations` that will be used to generate the
* individual API reference pages, but we still need to generate a landing
* page for each `package`. Return a new object with our `declarations` and
* a new property `packages` which is an array of every `package` with a
* page `src`, a `pageUrl`, an `icon` and a list of `declarations`. This
* also adds a `title`, `description` and `titleSegments` to each page
* which are used in the template for SEO.
*/
return {
declarations,
packages: _(declarations)
.map(d => d.package)
.uniq()
.reduce((packages, package) => {
const abbreviatedPackageName = package.replace("arcgis-rest-", "")
const src = `api/${abbreviatedPackageName}.html`;
const pkg = require(`${process.cwd()}/packages/${package}/package.json`);
packages.push({
package,
pkg,
title: package,
description: pkg.description,
titleSegments: ["API Reference"],
name: package,
declarations: declarations.filter(d => d.package === package),
icon: "tsd-kind-module",
src,
pageUrl: prettyifyUrl(src)
});
return packages;
}, [])
};
})
.then(api => {
/**
* Since we generated the TypeDoc for the entire project at once each
* `declaration` has a unique numerical `id` property. We occassionally
* need to lookup a declaration by its `id` so we can prebuild an index of
* them here.
*/
api.index = api.declarations.reduce((index, declaration) => {
index[declaration.id] = declaration;
return index;
}, {});
return api;
})
.then(api => {
/**
* In order to power the API reference quick search we can build an array
* of all declarations and child items
*/
api.quickSearchIndex = api.declarations.reduce(
(quickSearchIndex, declaration) => {
if (declaration.children) {
quickSearchIndex = quickSearchIndex.concat(
declaration.children.map(child => {
return {
title: `${declaration.name}.${child.name}`,
url: `${declaration.pageUrl}#${child.name}`,
icon: child.icon
};
})
);
}
return quickSearchIndex.concat([
{
title: declaration.name,
url: declaration.pageUrl,
icon: declaration.icon
}
]);
},
[]
);
return api;
})
.then(api => {
/**
* Our final object looks like this:
*
* {
* packages: [Array of packages.],
* declarations: [Array of each exported declaration accross all source files.]
* index: { Object mapping each declaration.id as a key with the declaration as its value}
* }
*
* We now export this to the Acetate source directory.
*/
return new Promise((resolve, reject) => {
writeFile(OUTPUT, JSON.stringify(api, null, 2), e => {
if (e) {
reject(e);
return;
}
resolve(api);
});
});
})
.catch(e => {
console.error(e);
});
})();