@lukoweb/apitogo 0.2.16 → 0.2.18
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.
|
@@ -12,6 +12,10 @@ export interface NpmInstallResult {
|
|
|
12
12
|
durationMs: number;
|
|
13
13
|
installLog: string;
|
|
14
14
|
}
|
|
15
|
+
export declare function runNpmCommand(args: string[], cwd: string): Promise<{
|
|
16
|
+
durationMs: number;
|
|
17
|
+
log: string;
|
|
18
|
+
}>;
|
|
15
19
|
export declare function runNpmInstall(sourceDirectory: string, options?: {
|
|
16
20
|
nodeVersion?: string;
|
|
17
21
|
}): Promise<NpmInstallResult>;
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { InputNavigationItem } from "../validators/InputNavigationSchema.js";
|
|
2
|
+
|
|
3
|
+
/** Label of the one header tab that holds the sidebar made from the docs pages. */
|
|
4
|
+
export const DOCS_NAVIGATION_LABEL = "Documentation";
|
|
5
|
+
|
|
6
|
+
type Branch = { pages: Set<string>; folders: Map<string, Branch> };
|
|
7
|
+
|
|
8
|
+
const emptyBranch = (): Branch => ({ pages: new Set(), folders: new Map() });
|
|
9
|
+
|
|
10
|
+
/** "getting-started" becomes "Getting started". */
|
|
11
|
+
export function folderLabel(name: string): string {
|
|
12
|
+
const words = name.replace(/[-_]+/g, " ").trim();
|
|
13
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const byName = (a: string, b: string) => a.localeCompare(b);
|
|
17
|
+
|
|
18
|
+
const toItems = (branch: Branch, prefix: string): InputNavigationItem[] => {
|
|
19
|
+
const pages = [...branch.pages].sort((a, b) =>
|
|
20
|
+
a === "index" ? -1 : b === "index" ? 1 : byName(a, b),
|
|
21
|
+
);
|
|
22
|
+
const folders = [...branch.folders.keys()].sort(byName);
|
|
23
|
+
return [
|
|
24
|
+
// The index page of a folder is the folder's own address, so the sidebar also shows on `/`.
|
|
25
|
+
...pages.map(
|
|
26
|
+
(page): InputNavigationItem =>
|
|
27
|
+
page === "index"
|
|
28
|
+
? {
|
|
29
|
+
type: "doc",
|
|
30
|
+
file: `${prefix}${page}`,
|
|
31
|
+
path: prefix === "" ? "/" : `/${prefix.slice(0, -1)}`,
|
|
32
|
+
}
|
|
33
|
+
: `${prefix}${page}`,
|
|
34
|
+
),
|
|
35
|
+
...folders.map(
|
|
36
|
+
(folder): InputNavigationItem => ({
|
|
37
|
+
type: "category",
|
|
38
|
+
label: folderLabel(folder),
|
|
39
|
+
collapsible: true,
|
|
40
|
+
collapsed: false,
|
|
41
|
+
items: toItems(
|
|
42
|
+
branch.folders.get(folder) ?? emptyBranch(),
|
|
43
|
+
`${prefix}${folder}/`,
|
|
44
|
+
),
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The sidebar for hosted docs, made from the pages themselves: the index page first, then the other pages by
|
|
52
|
+
* name, then one section per folder. `routes` are the docs plugin's route paths (`/overview`, `/guides/style`).
|
|
53
|
+
* Without pages there is nothing to navigate, so the result is empty.
|
|
54
|
+
*/
|
|
55
|
+
export function docsNavigation(routes: string[]): InputNavigationItem[] {
|
|
56
|
+
const root = emptyBranch();
|
|
57
|
+
for (const route of routes) {
|
|
58
|
+
const parts = route.split("/").filter(Boolean);
|
|
59
|
+
const page = parts.pop();
|
|
60
|
+
if (!page) continue;
|
|
61
|
+
const branch = parts.reduce((current, folder) => {
|
|
62
|
+
const next = current.folders.get(folder) ?? emptyBranch();
|
|
63
|
+
current.folders.set(folder, next);
|
|
64
|
+
return next;
|
|
65
|
+
}, root);
|
|
66
|
+
branch.pages.add(page);
|
|
67
|
+
}
|
|
68
|
+
const items = toItems(root, "");
|
|
69
|
+
return items.length === 0
|
|
70
|
+
? []
|
|
71
|
+
: [{ type: "category", label: DOCS_NAVIGATION_LABEL, items }];
|
|
72
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { glob } from "glob";
|
|
3
3
|
import globParent from "glob-parent";
|
|
4
|
+
import { readFrontmatter } from "../../lib/util/readFrontmatter.js";
|
|
4
5
|
|
|
5
6
|
export type HomeRedirect = { from: string; to: string };
|
|
6
7
|
|
|
@@ -10,7 +11,10 @@ const IGNORED = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
|
|
|
10
11
|
* The routes the docs plugin gives the pages these globs match: the path inside the glob's folder, without the
|
|
11
12
|
* extension. Missing folders and negated globs are fine, they just match nothing.
|
|
12
13
|
*/
|
|
13
|
-
export async function docRoutes(
|
|
14
|
+
export async function docRoutes(
|
|
15
|
+
patterns: string[],
|
|
16
|
+
options: { includeDrafts?: boolean } = {},
|
|
17
|
+
): Promise<string[]> {
|
|
14
18
|
const excluded = patterns
|
|
15
19
|
.filter((pattern) => pattern.startsWith("!"))
|
|
16
20
|
.map((pattern) => pattern.slice(1));
|
|
@@ -24,7 +28,20 @@ export async function docRoutes(patterns: string[]): Promise<string[]> {
|
|
|
24
28
|
ignore: [...IGNORED, ...excluded],
|
|
25
29
|
});
|
|
26
30
|
const parent = globParent(pattern);
|
|
27
|
-
|
|
31
|
+
// The docs plugin leaves draft pages out of a production build, so the routes must too.
|
|
32
|
+
const published = options.includeDrafts
|
|
33
|
+
? files
|
|
34
|
+
: (
|
|
35
|
+
await Promise.all(
|
|
36
|
+
files.map(async (file) => ({
|
|
37
|
+
file,
|
|
38
|
+
draft: (await readFrontmatter(file)).data.draft === true,
|
|
39
|
+
})),
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
.filter(({ draft }) => !draft)
|
|
43
|
+
.map(({ file }) => file);
|
|
44
|
+
return published.map(
|
|
28
45
|
(file) =>
|
|
29
46
|
`/${path.posix.relative(parent, file).replace(/\.mdx?$/, "")}`,
|
|
30
47
|
);
|
package/src/config/v2/wrapper.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { docsNavigation } from "./docs-navigation.js";
|
|
3
4
|
import { ConfigError, type ConfigIssue } from "./errors.js";
|
|
4
5
|
import {
|
|
5
6
|
ReferenceSpecError,
|
|
@@ -244,9 +245,23 @@ export function buildSiteConfig(
|
|
|
244
245
|
resolved: ResolvedConfigV2,
|
|
245
246
|
plan: PortalPlan,
|
|
246
247
|
theme?: unknown,
|
|
247
|
-
|
|
248
|
+
extras: { redirects?: HomeRedirect[]; docsNavigation?: unknown[] } = {},
|
|
248
249
|
): Record<string, unknown> {
|
|
249
250
|
const portal = resolved.portal;
|
|
251
|
+
const redirects = extras.redirects ?? [];
|
|
252
|
+
// One header tab for the docs sidebar, and the API reference as a link next to it.
|
|
253
|
+
const navigation = [
|
|
254
|
+
...(extras.docsNavigation ?? []),
|
|
255
|
+
...(plan.apiReference
|
|
256
|
+
? [
|
|
257
|
+
{
|
|
258
|
+
type: "link",
|
|
259
|
+
to: `/${API_REFERENCE_PATH}`,
|
|
260
|
+
label: "API Reference",
|
|
261
|
+
},
|
|
262
|
+
]
|
|
263
|
+
: []),
|
|
264
|
+
];
|
|
250
265
|
return {
|
|
251
266
|
site: {
|
|
252
267
|
title: portal?.name ?? resolved.project.name,
|
|
@@ -276,15 +291,9 @@ export function buildSiteConfig(
|
|
|
276
291
|
path: API_REFERENCE_PATH,
|
|
277
292
|
},
|
|
278
293
|
],
|
|
279
|
-
navigation: [
|
|
280
|
-
{
|
|
281
|
-
type: "link",
|
|
282
|
-
to: `/${API_REFERENCE_PATH}`,
|
|
283
|
-
label: "API Reference",
|
|
284
|
-
},
|
|
285
|
-
],
|
|
286
294
|
}
|
|
287
295
|
: {}),
|
|
296
|
+
...(navigation.length > 0 ? { navigation } : {}),
|
|
288
297
|
...(redirects.length > 0 ? { redirects } : {}),
|
|
289
298
|
...(theme !== undefined ? { theme } : {}),
|
|
290
299
|
...(plan.auth ? { authentication: { type: "openid", ...plan.auth } } : {}),
|
|
@@ -433,9 +442,14 @@ export async function preparePortal(
|
|
|
433
442
|
});
|
|
434
443
|
}
|
|
435
444
|
}
|
|
445
|
+
const hostedDocRoutes = plan.docs
|
|
446
|
+
? await docRoutes(profile.docs.content, {
|
|
447
|
+
includeDrafts: mode === "development",
|
|
448
|
+
})
|
|
449
|
+
: [];
|
|
436
450
|
const home = homeRedirect({
|
|
437
451
|
hostsDocs: plan.docs,
|
|
438
|
-
docRoutes:
|
|
452
|
+
docRoutes: hostedDocRoutes,
|
|
439
453
|
externalDocsUrl: profile.docs.url,
|
|
440
454
|
apiReferencePath: plan.apiReference ? `/${API_REFERENCE_PATH}` : undefined,
|
|
441
455
|
userPanel: plan.userPanel,
|
|
@@ -444,7 +458,10 @@ export async function preparePortal(
|
|
|
444
458
|
resolved,
|
|
445
459
|
plan,
|
|
446
460
|
await readTheme(resolved),
|
|
447
|
-
|
|
461
|
+
{
|
|
462
|
+
redirects: home ? [home] : [],
|
|
463
|
+
docsNavigation: docsNavigation(hostedDocRoutes),
|
|
464
|
+
},
|
|
448
465
|
);
|
|
449
466
|
const wrapper = renderWrapper(plan);
|
|
450
467
|
const wrapperPath = path.join(resolved.state.generatedDir, WRAPPER_FILE);
|