@t8/docsgen 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 +0 -0
- package/dist/bin.js +756 -0
- package/dist/css/base.css +232 -0
- package/dist/css/code.css +30 -0
- package/dist/css/code.lightbulb.css +278 -0
- package/dist/css/index.css +212 -0
- package/dist/css/section.css +200 -0
- package/package.json +36 -0
- package/src/bin/cleanup.ts +11 -0
- package/src/bin/createFiles.ts +8 -0
- package/src/bin/fetchText.ts +16 -0
- package/src/bin/getConfig.ts +44 -0
- package/src/bin/getCounterContent.ts +17 -0
- package/src/bin/getNav.ts +64 -0
- package/src/bin/getParsedContent.ts +180 -0
- package/src/bin/getRepoLink.ts +9 -0
- package/src/bin/getSlug.ts +21 -0
- package/src/bin/getTitle.ts +49 -0
- package/src/bin/run.ts +31 -0
- package/src/bin/runEntry.ts +55 -0
- package/src/bin/setCName.ts +13 -0
- package/src/bin/setContent.ts +251 -0
- package/src/bin/setImages.ts +8 -0
- package/src/bin/toConfig.ts +14 -0
- package/src/bin/toFileContent.ts +3 -0
- package/src/bin/toRepoURL.ts +22 -0
- package/src/const/packageName.ts +1 -0
- package/src/css/base.css +232 -0
- package/src/css/code.css +30 -0
- package/src/css/code.lightbulb.css +278 -0
- package/src/css/index.css +212 -0
- package/src/css/section.css +200 -0
- package/src/types/BinConfig.ts +6 -0
- package/src/types/Context.ts +4 -0
- package/src/types/EntryConfig.ts +57 -0
- package/src/types/NavItem.ts +8 -0
- package/src/types/PackageMetadata.ts +11 -0
- package/src/types/Theme.ts +1 -0
- package/src/utils/escapeHTML.ts +17 -0
- package/src/utils/escapeRegExp.ts +4 -0
- package/src/utils/getIcon.ts +16 -0
- package/src/utils/getSVGDataURL.ts +9 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { JSDOM } from "jsdom";
|
|
2
|
+
import Markdown from "markdown-it";
|
|
3
|
+
import type { Context } from "../types/Context";
|
|
4
|
+
import type { NavItem } from "../types/NavItem";
|
|
5
|
+
import { fetchText } from "./fetchText";
|
|
6
|
+
import { getSlug } from "./getSlug";
|
|
7
|
+
|
|
8
|
+
const md = new Markdown({
|
|
9
|
+
html: true,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function joinLines(x: string[]) {
|
|
13
|
+
return x.join("\n").trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildNav(ctx: Context, dom: JSDOM) {
|
|
17
|
+
let { root, contentDir, singlePage } = ctx;
|
|
18
|
+
let linkMap: Record<string, string> = {};
|
|
19
|
+
|
|
20
|
+
let navItem: NavItem | null = null;
|
|
21
|
+
let nav: NavItem[] = [];
|
|
22
|
+
|
|
23
|
+
if (singlePage)
|
|
24
|
+
nav.push({
|
|
25
|
+
id: "Overview",
|
|
26
|
+
title: "Overview",
|
|
27
|
+
items: [],
|
|
28
|
+
});
|
|
29
|
+
else {
|
|
30
|
+
let headings =
|
|
31
|
+
dom.window.document.body.querySelectorAll("h2, h3, h4, h5, h6");
|
|
32
|
+
|
|
33
|
+
for (let element of headings) {
|
|
34
|
+
let tagName = element.tagName.toLowerCase();
|
|
35
|
+
|
|
36
|
+
let isSectionTitle = tagName === "h2";
|
|
37
|
+
let isSubsectionTitle = tagName === "h3";
|
|
38
|
+
|
|
39
|
+
let sectionId = isSectionTitle
|
|
40
|
+
? getSlug(element.textContent)
|
|
41
|
+
: (navItem?.id ?? "");
|
|
42
|
+
let elementId = element.id;
|
|
43
|
+
|
|
44
|
+
if (!elementId)
|
|
45
|
+
elementId = getSlug(element.textContent)
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/_/g, "-");
|
|
48
|
+
|
|
49
|
+
if (elementId) {
|
|
50
|
+
element.id = elementId;
|
|
51
|
+
|
|
52
|
+
let link = `${root}${contentDir}/${sectionId}`;
|
|
53
|
+
|
|
54
|
+
if (!isSectionTitle) link += `#${elementId}`;
|
|
55
|
+
|
|
56
|
+
linkMap[`#${elementId}`] = link;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isSectionTitle) {
|
|
60
|
+
if (navItem) nav.push(navItem);
|
|
61
|
+
|
|
62
|
+
navItem = {
|
|
63
|
+
id: getSlug(element.textContent),
|
|
64
|
+
title: element.innerHTML.trim(),
|
|
65
|
+
items: [],
|
|
66
|
+
};
|
|
67
|
+
} else if (isSubsectionTitle) {
|
|
68
|
+
if (navItem)
|
|
69
|
+
navItem.items.push({
|
|
70
|
+
id: getSlug(element.textContent),
|
|
71
|
+
title: element.innerHTML.trim(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (navItem) nav.push(navItem);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
nav,
|
|
81
|
+
linkMap,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getInstallationCode(element: Element) {
|
|
86
|
+
return element
|
|
87
|
+
.querySelector("code")
|
|
88
|
+
?.innerHTML.trim()
|
|
89
|
+
.match(/(\S\s*)?(npm (i|install) .*)/)?.[2];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getSectionPostprocess(linkMap: Record<string, string>) {
|
|
93
|
+
return (content: string) => {
|
|
94
|
+
let s = content;
|
|
95
|
+
|
|
96
|
+
s = s.replace(/<a href="([^"]+)">/g, (_, url) => {
|
|
97
|
+
if (url?.startsWith("#")) return `<a href="${linkMap[url] ?? url}">`;
|
|
98
|
+
return `<a href="${url}" target="_blank">`;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return s;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function getParsedContent(ctx: Context) {
|
|
106
|
+
let { source = "README.md", singlePage } = ctx;
|
|
107
|
+
let content = md.render(await fetchText(source));
|
|
108
|
+
let dom = new JSDOM(content);
|
|
109
|
+
|
|
110
|
+
let { nav, linkMap } = buildNav(ctx, dom);
|
|
111
|
+
|
|
112
|
+
let badges: string[] = [];
|
|
113
|
+
let title = "";
|
|
114
|
+
let description: string[] = [];
|
|
115
|
+
let features: string[] = [];
|
|
116
|
+
let note: string[] = [];
|
|
117
|
+
let installation = "";
|
|
118
|
+
|
|
119
|
+
let section: string[] = [];
|
|
120
|
+
let sections: string[] = [];
|
|
121
|
+
|
|
122
|
+
let hasTitle = false;
|
|
123
|
+
let hasFeatures = false;
|
|
124
|
+
let indexComplete = false;
|
|
125
|
+
|
|
126
|
+
let element = dom.window.document.body.firstElementChild;
|
|
127
|
+
|
|
128
|
+
while (element !== null) {
|
|
129
|
+
if (element.matches("h1")) hasTitle = true;
|
|
130
|
+
else {
|
|
131
|
+
if (element.matches("h2")) {
|
|
132
|
+
if (!indexComplete) indexComplete = true;
|
|
133
|
+
|
|
134
|
+
if (!singlePage && section.length !== 0) {
|
|
135
|
+
sections.push(joinLines(section));
|
|
136
|
+
section = [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let { outerHTML } = element;
|
|
141
|
+
|
|
142
|
+
if (indexComplete) section.push(outerHTML);
|
|
143
|
+
else if (!hasTitle) {
|
|
144
|
+
badges.push(outerHTML);
|
|
145
|
+
} else if (!hasFeatures) {
|
|
146
|
+
if (element.matches("ul")) {
|
|
147
|
+
hasFeatures = true;
|
|
148
|
+
features.push(outerHTML);
|
|
149
|
+
} else {
|
|
150
|
+
let installationCode = getInstallationCode(element);
|
|
151
|
+
|
|
152
|
+
if (installationCode) installation = installationCode;
|
|
153
|
+
else description.push(outerHTML);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
let installationCode = getInstallationCode(element);
|
|
157
|
+
|
|
158
|
+
if (installationCode) installation = installationCode;
|
|
159
|
+
else note.push(outerHTML);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
element = element.nextElementSibling;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (section.length !== 0) sections.push(joinLines(section));
|
|
167
|
+
|
|
168
|
+
let postprocess = getSectionPostprocess(linkMap);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
badges: joinLines(badges),
|
|
172
|
+
title,
|
|
173
|
+
description: joinLines(description),
|
|
174
|
+
features: joinLines(features),
|
|
175
|
+
note: joinLines(note),
|
|
176
|
+
installation,
|
|
177
|
+
sections: sections.map(postprocess),
|
|
178
|
+
nav,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Context } from "../types/Context";
|
|
2
|
+
|
|
3
|
+
export function getRepoLink({ repo }: Context, className?: string) {
|
|
4
|
+
if (!repo) return "";
|
|
5
|
+
|
|
6
|
+
let caption = /\bgithub\.com\//.test(repo) ? "GitHub" : "Repository";
|
|
7
|
+
|
|
8
|
+
return `<a href="${repo}"${className ? ` class="${className}"` : ""} target="_blank">${caption}</a>`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function getSlug(title: string | null) {
|
|
2
|
+
if (!title) return "";
|
|
3
|
+
|
|
4
|
+
let slug = title.trim();
|
|
5
|
+
|
|
6
|
+
if (
|
|
7
|
+
(slug.startsWith("<") && slug.endsWith(">")) ||
|
|
8
|
+
(slug.startsWith("`") && slug.endsWith("`"))
|
|
9
|
+
)
|
|
10
|
+
slug = slug.slice(1, -1);
|
|
11
|
+
|
|
12
|
+
slug = slug
|
|
13
|
+
.replace(/[<>`:?!.,]+/g, " ")
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/\s+/g, "_")
|
|
16
|
+
.replace(/'/g, "");
|
|
17
|
+
|
|
18
|
+
if (/^\w+\(\)$/.test(slug)) slug = slug.slice(0, -2);
|
|
19
|
+
|
|
20
|
+
return slug;
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Context } from "../types/Context";
|
|
2
|
+
import { escapeHTML } from "../utils/escapeHTML";
|
|
3
|
+
|
|
4
|
+
type GetTitleParams = {
|
|
5
|
+
cover?: boolean;
|
|
6
|
+
originalContent?: string;
|
|
7
|
+
withPackageURL?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function getTitle(
|
|
11
|
+
ctx: Context,
|
|
12
|
+
{ cover, originalContent, withPackageURL }: GetTitleParams = {},
|
|
13
|
+
) {
|
|
14
|
+
let { root, name, title: packageTitle, scope, theme } = ctx;
|
|
15
|
+
|
|
16
|
+
if (originalContent && ![name, packageTitle].includes(originalContent.trim()))
|
|
17
|
+
return originalContent;
|
|
18
|
+
|
|
19
|
+
if (packageTitle) {
|
|
20
|
+
let escapedTitle = escapeHTML(packageTitle);
|
|
21
|
+
|
|
22
|
+
if (cover && theme === "t8" && packageTitle.startsWith("T8 "))
|
|
23
|
+
return `<a href="/">T8</a> <span class="name">${packageTitle.replace(/^T8 /, "")}</span>`;
|
|
24
|
+
|
|
25
|
+
return withPackageURL
|
|
26
|
+
? `<a href="${root}" class="name">${escapedTitle}</a>`
|
|
27
|
+
: `<span class="name">${escapedTitle}</span>`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let scopeMatches = name?.match(/^(@[^/]+)\/?(.*)/);
|
|
31
|
+
|
|
32
|
+
if (!scope || !scopeMatches) {
|
|
33
|
+
let escapedName = escapeHTML(name);
|
|
34
|
+
|
|
35
|
+
return withPackageURL
|
|
36
|
+
? `<a href="${root}" class="name">${escapedName}</a>`
|
|
37
|
+
: `<span class="name">${escapedName}</span>`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let title =
|
|
41
|
+
`<a href="${scope}" class="scope">${scopeMatches[1]}</a>` +
|
|
42
|
+
'<span class="sep">/</span>';
|
|
43
|
+
|
|
44
|
+
title += withPackageURL
|
|
45
|
+
? `<a href="${root}" class="name">${scopeMatches[2]}</a>`
|
|
46
|
+
: `<span class="name">${scopeMatches[2]}</span>`;
|
|
47
|
+
|
|
48
|
+
return title;
|
|
49
|
+
}
|
package/src/bin/run.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getConfig } from "./getConfig";
|
|
3
|
+
import { runEntry } from "./runEntry";
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
let { targetId, entries, ...rootCtx } = await getConfig();
|
|
7
|
+
|
|
8
|
+
if (!entries) {
|
|
9
|
+
if (!targetId || targetId === rootCtx.id) return await runEntry(rootCtx);
|
|
10
|
+
|
|
11
|
+
console.warn(`Specified config entry not found: '${targetId}'`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (targetId) {
|
|
16
|
+
let entryCtx = entries.find(({ id }) => id === targetId);
|
|
17
|
+
|
|
18
|
+
if (entryCtx) runEntry(entryCtx);
|
|
19
|
+
else console.warn(`Specified config entry not found: '${targetId}'`);
|
|
20
|
+
} else {
|
|
21
|
+
await Promise.all(
|
|
22
|
+
entries.map((entryCtx) => {
|
|
23
|
+
return runEntry({ ...rootCtx, ...entryCtx });
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
(async () => {
|
|
30
|
+
await run();
|
|
31
|
+
})();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { exec as defaultExec } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import type { Context } from "../types/Context";
|
|
5
|
+
import { cleanup } from "./cleanup";
|
|
6
|
+
import { createFiles } from "./createFiles";
|
|
7
|
+
|
|
8
|
+
const exec = promisify(defaultExec);
|
|
9
|
+
const stdout = async (cmd: string) => (await exec(cmd)).stdout.trim();
|
|
10
|
+
|
|
11
|
+
async function isGitDir() {
|
|
12
|
+
try {
|
|
13
|
+
await access("./.git");
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runEntry(ctx: Context) {
|
|
21
|
+
let { targetBranch, mainBranch, remove } = ctx;
|
|
22
|
+
|
|
23
|
+
if (!targetBranch || !(await isGitDir())) {
|
|
24
|
+
await cleanup(ctx);
|
|
25
|
+
await createFiles(ctx);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let originalBranch = await stdout("git rev-parse --abbrev-ref HEAD");
|
|
30
|
+
|
|
31
|
+
if (originalBranch === targetBranch) await exec(`git checkout ${mainBranch}`);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await exec(`git branch -D ${targetBranch}`);
|
|
35
|
+
} catch {}
|
|
36
|
+
try {
|
|
37
|
+
await exec(`git push origin ${targetBranch} --delete`);
|
|
38
|
+
} catch {}
|
|
39
|
+
|
|
40
|
+
if (remove) return;
|
|
41
|
+
|
|
42
|
+
await exec(`git checkout -b ${targetBranch}`);
|
|
43
|
+
await createFiles(ctx);
|
|
44
|
+
await exec("git add *");
|
|
45
|
+
|
|
46
|
+
let updated = (await stdout("git diff --cached --name-only")) !== "";
|
|
47
|
+
|
|
48
|
+
if (updated) {
|
|
49
|
+
await exec('git commit -m "release gh-pages"');
|
|
50
|
+
await exec(`git push -u origin ${targetBranch} -f`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (originalBranch && originalBranch !== targetBranch)
|
|
54
|
+
await exec(`git checkout ${originalBranch}`);
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { Context } from "../types/Context";
|
|
4
|
+
|
|
5
|
+
export async function setCName({ dir = "", name, cname, jsorg }: Context) {
|
|
6
|
+
let domain = "";
|
|
7
|
+
|
|
8
|
+
if (cname) domain = cname;
|
|
9
|
+
else if (typeof jsorg === "string") domain = jsorg ? `${jsorg}.js.org` : "";
|
|
10
|
+
else if (jsorg === true) domain = name ? `${name}.js.org` : "";
|
|
11
|
+
|
|
12
|
+
if (domain !== "") await writeFile(join(dir, "CNAME"), domain);
|
|
13
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { exec as defaultExec } from "node:child_process";
|
|
2
|
+
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { packageName } from "../const/packageName";
|
|
6
|
+
import type { Context } from "../types/Context";
|
|
7
|
+
import { escapeHTML } from "../utils/escapeHTML";
|
|
8
|
+
import { escapeRegExp } from "../utils/escapeRegExp";
|
|
9
|
+
import { getCounterContent } from "./getCounterContent";
|
|
10
|
+
import { getNav } from "./getNav";
|
|
11
|
+
import { getParsedContent } from "./getParsedContent";
|
|
12
|
+
import { getRepoLink } from "./getRepoLink";
|
|
13
|
+
import { getTitle } from "./getTitle";
|
|
14
|
+
import { toFileContent } from "./toFileContent";
|
|
15
|
+
|
|
16
|
+
const exec = promisify(defaultExec);
|
|
17
|
+
|
|
18
|
+
export async function setContent(ctx: Context) {
|
|
19
|
+
let {
|
|
20
|
+
dir = "",
|
|
21
|
+
colorScheme,
|
|
22
|
+
theme,
|
|
23
|
+
root,
|
|
24
|
+
contentDir = "",
|
|
25
|
+
name,
|
|
26
|
+
title,
|
|
27
|
+
description: packageDescription,
|
|
28
|
+
backstory,
|
|
29
|
+
redirect,
|
|
30
|
+
} = ctx;
|
|
31
|
+
|
|
32
|
+
let counterContent = getCounterContent(ctx);
|
|
33
|
+
let escapedName = escapeHTML(name);
|
|
34
|
+
let escapedTitle = title ? escapeHTML(title) : escapedName;
|
|
35
|
+
|
|
36
|
+
let packageVersion = (await exec(`npm view ${packageName} version`)).stdout
|
|
37
|
+
.trim()
|
|
38
|
+
.split(".")
|
|
39
|
+
.slice(0, 2)
|
|
40
|
+
.join(".");
|
|
41
|
+
|
|
42
|
+
let packageUrl = `https://unpkg.com/${packageName}@${packageVersion}`;
|
|
43
|
+
let rootAttrs = "";
|
|
44
|
+
|
|
45
|
+
let icon = {
|
|
46
|
+
url: `${root}favicon.svg`,
|
|
47
|
+
type: "image/svg+xml",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (theme) rootAttrs += ` data-theme="${escapeHTML(theme)}"`;
|
|
51
|
+
|
|
52
|
+
if (colorScheme)
|
|
53
|
+
rootAttrs += ` style="--color-scheme: ${escapeHTML(colorScheme)}"`;
|
|
54
|
+
|
|
55
|
+
if (theme === "t8")
|
|
56
|
+
icon = {
|
|
57
|
+
url: "/assets/t8.png",
|
|
58
|
+
type: "image/png",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (redirect) {
|
|
62
|
+
let escapedRedirect = escapeHTML(redirect);
|
|
63
|
+
|
|
64
|
+
await writeFile(
|
|
65
|
+
join(dir, "index.html"),
|
|
66
|
+
toFileContent(`
|
|
67
|
+
<!DOCTYPE html>
|
|
68
|
+
<html lang="en" class="blank"${rootAttrs}>
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="utf-8">
|
|
71
|
+
<meta name="viewport" content="width=device-width">
|
|
72
|
+
<meta http-equiv="refresh" content="0; URL=${escapedRedirect}">
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
${counterContent}
|
|
76
|
+
<script>window.location.replace("${escapedRedirect}");</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
`),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let { badges, description, features, installation, sections, nav } =
|
|
86
|
+
await getParsedContent(ctx);
|
|
87
|
+
|
|
88
|
+
let navContent = await getNav(ctx, nav);
|
|
89
|
+
let dirs = [contentDir];
|
|
90
|
+
|
|
91
|
+
await Promise.all(
|
|
92
|
+
dirs.map(async (path) => {
|
|
93
|
+
let dirPath = join(dir, path);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await access(dirPath);
|
|
97
|
+
} catch {
|
|
98
|
+
await mkdir(dirPath);
|
|
99
|
+
}
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await Promise.all([
|
|
104
|
+
...sections.map(async (content, i) =>
|
|
105
|
+
writeFile(
|
|
106
|
+
join(dir, contentDir, `${nav[i]?.id ?? `_untitled_${i}`}.html`),
|
|
107
|
+
toFileContent(`
|
|
108
|
+
<!DOCTYPE html>
|
|
109
|
+
<html lang="en"${rootAttrs}>
|
|
110
|
+
<head>
|
|
111
|
+
<meta charset="utf-8">
|
|
112
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
113
|
+
<title>${escapeHTML(nav[i]?.title)} | ${escapedTitle}</title>
|
|
114
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/base.css">
|
|
115
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/section.css">
|
|
116
|
+
<link rel="icon" type="${icon.type}" href="${icon.url}">
|
|
117
|
+
${nav[i + 1]?.id ? `<link rel="prefetch" href="${root}${contentDir}/${nav[i + 1]?.id}">` : ""}
|
|
118
|
+
${nav[i - 1]?.id ? `<link rel="prefetch" href="${root}${contentDir}/${nav[i - 1]?.id}">` : ""}
|
|
119
|
+
</head>
|
|
120
|
+
<body>
|
|
121
|
+
<div class="layout">
|
|
122
|
+
<div class="${navContent ? "" : "no-nav "}body">
|
|
123
|
+
<main>
|
|
124
|
+
<h1>${getTitle(ctx, { withPackageURL: true })}</h1>
|
|
125
|
+
${content}
|
|
126
|
+
|
|
127
|
+
<p class="pagenav">
|
|
128
|
+
<span class="prev">
|
|
129
|
+
<span class="icon">←</span>
|
|
130
|
+
${nav[i - 1]?.id ? `<a href="${root}${contentDir}/${nav[i - 1]?.id}">${nav[i - 1]?.title}</a>` : `<a href="${root}">Intro</a>`}
|
|
131
|
+
</span>
|
|
132
|
+
<span class="sep">|</span>
|
|
133
|
+
${nav[i + 1]?.id ? `<span class="next"><a href="${root}${contentDir}/${nav[i + 1]?.id}">${nav[i + 1]?.title}</a> <span class="icon">→</span></span>` : `<span class="repo next">${getRepoLink(ctx)} <span class="icon">✦</span></span>`}
|
|
134
|
+
</p>
|
|
135
|
+
</main>
|
|
136
|
+
${navContent ? "<hr>" : ""}
|
|
137
|
+
${navContent.replace(
|
|
138
|
+
new RegExp(
|
|
139
|
+
`(<li data-id="${escapeRegExp(nav[i]?.id)}">)<a href="[^"]+">([^<]+)</a>(</li>)`,
|
|
140
|
+
),
|
|
141
|
+
"$1<strong>$2</strong>$3",
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
${
|
|
147
|
+
content.includes("<pre><code ")
|
|
148
|
+
? `
|
|
149
|
+
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/base16/material.min.css">
|
|
150
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/code.css">
|
|
151
|
+
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js"></script>
|
|
152
|
+
<script>hljs.highlightAll()</script>
|
|
153
|
+
`.trim()
|
|
154
|
+
: ""
|
|
155
|
+
}
|
|
156
|
+
${counterContent}
|
|
157
|
+
</body>
|
|
158
|
+
</html>
|
|
159
|
+
`),
|
|
160
|
+
),
|
|
161
|
+
),
|
|
162
|
+
writeFile(
|
|
163
|
+
join(dir, "index.html"),
|
|
164
|
+
toFileContent(`
|
|
165
|
+
<!DOCTYPE html>
|
|
166
|
+
<html lang="en"${rootAttrs}>
|
|
167
|
+
<head>
|
|
168
|
+
<meta charset="utf-8">
|
|
169
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
170
|
+
<title>${escapedTitle}${packageDescription ? ` | ${escapeHTML(packageDescription)}` : ""}</title>
|
|
171
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/base.css">
|
|
172
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/index.css">
|
|
173
|
+
<link rel="icon" type="${icon.type}" href="${icon.url}">
|
|
174
|
+
<link rel="prefetch" href="${root}start">
|
|
175
|
+
${nav[0] ? `<link rel="prefetch" href="${root}${contentDir}/${nav[0]?.id ?? ""}">` : ""}
|
|
176
|
+
</head>
|
|
177
|
+
<body>
|
|
178
|
+
<div class="layout">
|
|
179
|
+
<main>
|
|
180
|
+
<section class="intro-title">
|
|
181
|
+
<div class="badges">
|
|
182
|
+
${badges}
|
|
183
|
+
</div>
|
|
184
|
+
<h1>${getTitle(ctx, { cover: true })}</h1>
|
|
185
|
+
<div class="description">
|
|
186
|
+
${description}
|
|
187
|
+
</div>
|
|
188
|
+
<p class="actions">
|
|
189
|
+
<a href="${root}start" class="primary button">Docs</a>
|
|
190
|
+
<span class="sep"> • </span>
|
|
191
|
+
${getRepoLink(ctx, "button")}
|
|
192
|
+
</p>
|
|
193
|
+
${backstory ? `<p class="ref"><a href="${backstory}">Backstory</a></p>` : ""}
|
|
194
|
+
<p class="installation"><code>${installation}</code></p>
|
|
195
|
+
</section>
|
|
196
|
+
${
|
|
197
|
+
features
|
|
198
|
+
? `
|
|
199
|
+
<section class="intro">
|
|
200
|
+
<div class="features">
|
|
201
|
+
<h2>Features</h2>
|
|
202
|
+
${features}
|
|
203
|
+
</div>
|
|
204
|
+
</section>
|
|
205
|
+
`
|
|
206
|
+
: ""
|
|
207
|
+
}
|
|
208
|
+
</main>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
${
|
|
212
|
+
[description, features].some((s) => s.includes("<pre><code "))
|
|
213
|
+
? `
|
|
214
|
+
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/base16/material.min.css">
|
|
215
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/code.css">
|
|
216
|
+
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js"></script>
|
|
217
|
+
<script>hljs.highlightAll()</script>
|
|
218
|
+
`.trim()
|
|
219
|
+
: ""
|
|
220
|
+
}
|
|
221
|
+
${counterContent}
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
224
|
+
`),
|
|
225
|
+
),
|
|
226
|
+
writeFile(
|
|
227
|
+
join(dir, "start.html"),
|
|
228
|
+
toFileContent(`
|
|
229
|
+
<!DOCTYPE html>
|
|
230
|
+
<html lang="en" class="blank"${rootAttrs}>
|
|
231
|
+
<head>
|
|
232
|
+
<meta charset="utf-8">
|
|
233
|
+
<meta name="viewport" content="width=device-width">
|
|
234
|
+
<meta http-equiv="refresh" content="0; URL=${root}${contentDir}/${nav[0]?.id}">
|
|
235
|
+
<title>${escapedTitle}</title>
|
|
236
|
+
<link rel="stylesheet" href="${packageUrl}/dist/css/base.css">
|
|
237
|
+
<link rel="icon" type="${icon.type}" href="${icon.url}">
|
|
238
|
+
<script>window.location.replace("${root}${contentDir}/${nav[0]?.id}");</script>
|
|
239
|
+
</head>
|
|
240
|
+
<body>
|
|
241
|
+
<div class="layout">
|
|
242
|
+
<h1>${escapedTitle}</h1>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
${counterContent}
|
|
246
|
+
</body>
|
|
247
|
+
</html>
|
|
248
|
+
`),
|
|
249
|
+
),
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { Context } from "../types/Context";
|
|
4
|
+
import { getIcon } from "../utils/getIcon";
|
|
5
|
+
|
|
6
|
+
export async function setImages({ dir = "", colorScheme }: Context) {
|
|
7
|
+
await writeFile(join(dir, "./favicon.svg"), `${getIcon(colorScheme)}\n`);
|
|
8
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BinConfig } from "../types/BinConfig";
|
|
2
|
+
import type { PackageMetadata } from "../types/PackageMetadata";
|
|
3
|
+
import { toRepoURL } from "./toRepoURL";
|
|
4
|
+
|
|
5
|
+
export function toConfig(metadata: PackageMetadata): Partial<BinConfig> {
|
|
6
|
+
let { name, description, version, repository } = metadata;
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
name,
|
|
10
|
+
description,
|
|
11
|
+
version,
|
|
12
|
+
repo: toRepoURL(repository),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PackageMetadata } from "../types/PackageMetadata";
|
|
2
|
+
|
|
3
|
+
export function toRepoURL(x: PackageMetadata["repository"]) {
|
|
4
|
+
if (!x) return "";
|
|
5
|
+
|
|
6
|
+
let s = typeof x === "string" ? x : x.url;
|
|
7
|
+
|
|
8
|
+
if (!s) return;
|
|
9
|
+
|
|
10
|
+
if (/^https?:\/\//.test(s)) return s;
|
|
11
|
+
|
|
12
|
+
if (/^git\+https?:\/\/.*\.git$/.test(s))
|
|
13
|
+
return s.replace(/^git\+(https?:\/\/.*)\.git$/, "$1");
|
|
14
|
+
|
|
15
|
+
if (/^git:\/\/.*\.git$/.test(s))
|
|
16
|
+
return s.replace(/^git(:\/\/.*)\.git$/, "https$1");
|
|
17
|
+
|
|
18
|
+
if (/^github:/.test(s))
|
|
19
|
+
return `https://github.com/${s.replace(/^github:/, "")}`;
|
|
20
|
+
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const packageName = "@t8/docsgen";
|