-
Notifications
You must be signed in to change notification settings - Fork 179
Expand file tree
/
Copy pathdocs.build.js
More file actions
350 lines (291 loc) · 10.6 KB
/
docs.build.js
File metadata and controls
350 lines (291 loc) · 10.6 KB
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
// SPDX-FileCopyrightText: the secureCodeBox authors
//
// SPDX-License-Identifier: Apache-2.0
const fs = require("fs"),
rimraf = require("rimraf"),
colors = require("colors"),
matter = require("gray-matter"),
path = require('path'),
{ docsConfig: config } = require("./utils/config"),
{ removeWhitespaces } = require("./utils/capitalizer"),
Mustache = require("mustache");
colors.setTheme({
info: "blue",
help: "cyan",
warn: "yellow",
success: "green",
error: "red",
});
// For the documentation on this script look at the README.md of this repository
async function main() {
const currentDirectory = __dirname; // current directory is /documentation/src
const parentDirectory = path.dirname(currentDirectory); // parent is /documentation
const rootDirectory = path.dirname(parentDirectory); // root is /
const dataArray = await Promise.all(
config.filesFromRepository.map((dir) =>
readDirectory(`${rootDirectory}/${dir.src}`, false)
.then((res) => ({ ...dir, files: res }))
.catch((err) =>
console.error(
`ERROR: Could not read directory at: ${dir}`.error,
err.message.error
)
)
)
);
if (!fs.existsSync(config.targetPath)) {
fs.mkdirSync(config.targetPath);
}
// Clear preexisting findings
if (fs.existsSync(config.findingsDir)) {
rimraf.sync(config.findingsDir);
}
for (const dir of dataArray) {
const trgDir = `${config.targetPath}/${dir.dst}`;
const srcDir = `${rootDirectory}/${dir.src}`;
// Clears existing md files from directories
if (fs.existsSync(trgDir)) {
await removeExistingMarkdownFilesFromDirectory(trgDir, dir.keep);
console.warn(
`WARN: ${trgDir.info} already existed and was overwritten.`.warn
);
} else {
fs.mkdirSync(trgDir);
}
// If the source directory contains a ".helm-docs.gotmpl" file (such as in /scanners or /hooks), the doc files need to be generated.
// Else, the docs files are just copied to the destination path.
dir.files.includes(".helm-docs.gotmpl")
? await createDocFilesFromMainRepository(srcDir, trgDir, await readDirectory(srcDir))
: await copyFilesFromMainRepository(srcDir, trgDir, dir.exclude, dir.keep);
}
}
main().catch((err) => {
clearDocsOnFailure();
console.error(err.stack.error);
});
function readDirectory(dir, dirNamesOnly = true) {
return new Promise((res, rej) => {
fs.readdir(
dir,
{ encoding: "utf8", withFileTypes: true },
function (err, data) {
if (err) {
rej(err);
} else {
if (dirNamesOnly) data = data.filter((file) => file.isDirectory());
const directories = data.map((dirent) => dirent.name);
res(directories);
}
}
);
});
}
async function createDocFilesFromMainRepository(relPath, targetPath, dirNames) {
for (const dirName of dirNames) {
const readMe = `${relPath}/${dirName}/README.md`;
if (!fs.existsSync(readMe)) {
console.log(
`WARN: Skipping ${dirName.help}: file not found at ${readMe.info}.`.warn
);
continue;
}
// Read readme content of scanner / hook directory
const readmeContent = fs.readFileSync(readMe, { encoding: "utf8" });
const examples = await getExamples(`${relPath}/${dirName}/examples`);
const imageTypes = await getSupportedImageTypes(`${relPath}/${dirName}/Chart.yaml`);
// Add a custom editUrl to the frontMatter to ensure that it points to the correct repo
const { data: frontmatter, content } = matter(readmeContent);
// Either the path contains "secureCodeBox" or "repo" depending on whether the docs are locally generated or in netlify
const filePathInRepo = relPath.replace(/^.*(?:secureCodeBox|repo)\//, "");
const readmeWithEditUrl = matter.stringify(content, {
...frontmatter,
description: frontmatter?.usecase,
custom_edit_url: `https://github.com/${config.repository}/edit/${config.branch}/${filePathInRepo}/${dirName}/.helm-docs.gotmpl`,
});
// Skip File if its marked as "hidden" in its frontmatter
if (frontmatter.hidden !== undefined && frontmatter.hidden === true) {
continue;
}
const integrationPage = Mustache.render(
fs.readFileSync(path.join(__dirname, "utils/scannerReadme.mustache"), {
encoding: "utf8",
}),
{
readme: readmeWithEditUrl,
examples,
hasExamples: examples.length !== 0,
imageTypes,
hasImageTypes: imageTypes?.length > 0
}
);
let fileName = frontmatter.title ? frontmatter.title : dirName;
//Replace Spaces in the FileName with "-" and convert to lower case to avoid URL issues
fileName = fileName.replace(/ /g, "-").toLowerCase();
const filePath = `${targetPath}/${fileName}.md`;
fs.writeFileSync(filePath, integrationPage);
console.log(
`SUCCESS: Created file for ${dirName.help} at ${filePath.info}`.success
);
}
}
async function getExamples(dir) {
if (!fs.existsSync(dir)) {
return [];
}
const dirNames = await readDirectory(dir).catch(() => []);
if (dirNames.length === 0) {
console.warn(`WARN: Found empty examples folder at ${dir.info}.`.warn);
return [];
}
return dirNames.map((dirName) => {
let readMe = "";
if (fs.existsSync(`${dir}/${dirName}/README.md`)) {
readMe = matter(
fs.readFileSync(`${dir}/${dirName}/README.md`, {
encoding: "utf8",
})
).content;
}
let scanContent = null;
if (fs.existsSync(`${dir}/${dirName}/scan.yaml`)) {
scanContent = fs.readFileSync(`${dir}/${dirName}/scan.yaml`, {
encoding: "utf8",
});
}
let findingContent = null;
let findingSizeLimitReached = null;
if (fs.existsSync(`${dir}/${dirName}/findings.yaml`)) {
findingSizeLimitReached =
fs.statSync(`${dir}/${dirName}/findings.yaml`).size >= config.sizeLimit;
if (findingSizeLimitReached) {
console.warn(
`WARN: Findings for ${dirName.info} exceeded size limit.`.warn
);
findingContent = copyFindingsForDownload(
`${dir}/${dirName}/findings.yaml`
);
} else {
findingContent = fs.readFileSync(`${dir}/${dirName}/findings.yaml`, {
encoding: "utf8",
});
}
}
let findings = null;
if (findingContent && findingSizeLimitReached !== null) {
findings = {
value: findingContent,
limitReached: findingSizeLimitReached,
};
}
return {
name: removeWhitespaces(dirName),
exampleReadme: readMe,
scan: scanContent,
findings,
};
});
}
function getSupportedImageTypes(dir) {
if (fs.existsSync(dir)) {
const chartContent = fs.readFileSync(dir, {
encoding: "utf8",
});
// add an opening delimiter to help matter distinguish the file type
const { data: frontmatter} = matter(['---', ...chartContent.toString().split('\n')].join('\n'));
if ('annotations' in frontmatter && 'supported-platforms' in frontmatter.annotations) {
return frontmatter['annotations']['supported-platforms'].split(',');
}
}
}
function copyFindingsForDownload(filePath) {
const dirNames = filePath.split("/"),
name =
dirNames[dirNames.indexOf("examples") - 1] +
"-" +
dirNames[dirNames.indexOf("examples") + 1],
targetPath = `/${config.findingsDir}/${name}-findings.yaml`;
if (!fs.existsSync("static")) {
fs.mkdirSync("static/");
}
if (!fs.existsSync(`static/${config.findingsDir}`)) {
fs.mkdirSync(`static/${config.findingsDir}`);
}
fs.copyFileSync(filePath, "static" + targetPath);
console.log(`SUCCESS: Created download link for ${name.info}.`.success);
return targetPath;
}
function clearDocsOnFailure() {
for (const dir of config.filesFromRepository) {
const trgDir = `${config.targetPath}/${dir.src}`;
if (fs.existsSync(trgDir)) {
removeExistingMarkdownFilesFromDirectory(trgDir, dir.keep)
.then(() => {
console.log(
`Cleared ${trgDir.info} due to previous failure.`.magenta
);
})
.catch((err) => {
console.error(
`ERROR: Could not remove ${trgDir.info} on failure.`.error
);
console.error(err.message.error);
});
}
}
}
// Copy files from a given src directory from the main repo into the given dst directory
//
// Example: copyFilesFromMainRepository("docs/adr", "docs/architecture/adr");
// copyFilesFromMainRepository("docs/adr", "docs/architecture/adr", ["adr_0000.md", "adr_README.md"]);
//
// @param src required source directory in main repository (docsConfig.repository)
// @param dst required target directory in this repository relative to config.targetPath
// @param exclude optional array of files to exclude from srcPath
// @param keep optional array of files to keep in dstPath
async function copyFilesFromMainRepository(srcPath, dstPath, exclude, keep) {
exclude = exclude || [];
keep = keep || [];
if (fs.existsSync(srcPath)) {
console.error(`${srcPath.info}.`.error);
}
if (fs.existsSync(dstPath)) {
await removeExistingMarkdownFilesFromDirectory(dstPath, keep);
} else {
fs.mkdirSync(dstPath);
console.info(`Create target directory ${dstPath.info}...`.success);
}
fs.readdirSync(srcPath).map((fileName) => {
if (!exclude.includes(fileName)) {
console.log(`Copy ${fileName.info} to ${dstPath.info}...`.success);
fs.copyFileSync(`${srcPath}/${fileName}`, `${dstPath}/${fileName}`);
}
});
}
async function removeExistingMarkdownFilesFromDirectory(dirPath, filesToKeep) {
console.info(`Remove existing markdown files from ${dirPath.info}`)
const allFiles = await readDirectory(dirPath, false);
allFiles
.filter((fileName) => fileName.endsWith(".md"))
.filter(fileName => doNotKeepFile(fileName, filesToKeep))
.forEach((fileName) => {
const filePath = `${dirPath}/${fileName}`;
rimraf.sync(filePath);
console.warn(`WARN: ${filePath} was deleted.`.warn);
});
}
function doNotKeepFile(fileName, filesToKeep) {
// Helper method to make it harder to oversee the !. It is easier to see the negation in the name instead
// somewhere in the used filter invocation.
return !keepFile(fileName, filesToKeep);
}
function keepFile(fileName, filesToKeep) {
console.info(`Determine whether to keep '${fileName}' (${filesToKeep})`.info);
for (let index in filesToKeep) {
const fileToKeep = filesToKeep[index];
if (fileName.normalize() === fileToKeep.normalize()) {
console.info(`Keeping file ${fileName}`.info);
return true;
}
}
return false;
}