@lukoweb/apitogo 0.2.15 → 0.2.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -0,0 +1,81 @@
1
+ import path from "node:path";
2
+ import { glob } from "glob";
3
+ import globParent from "glob-parent";
4
+
5
+ export type HomeRedirect = { from: string; to: string };
6
+
7
+ const IGNORED = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
8
+
9
+ /**
10
+ * The routes the docs plugin gives the pages these globs match: the path inside the glob's folder, without the
11
+ * extension. Missing folders and negated globs are fine, they just match nothing.
12
+ */
13
+ export async function docRoutes(patterns: string[]): Promise<string[]> {
14
+ const excluded = patterns
15
+ .filter((pattern) => pattern.startsWith("!"))
16
+ .map((pattern) => pattern.slice(1));
17
+ const included = patterns.filter((pattern) => !pattern.startsWith("!"));
18
+
19
+ const routes = await Promise.all(
20
+ included.map(async (pattern) => {
21
+ const files = await glob(pattern, {
22
+ absolute: true,
23
+ posix: true,
24
+ ignore: [...IGNORED, ...excluded],
25
+ });
26
+ const parent = globParent(pattern);
27
+ return files.map(
28
+ (file) =>
29
+ `/${path.posix.relative(parent, file).replace(/\.mdx?$/, "")}`,
30
+ );
31
+ }),
32
+ );
33
+ return [...new Set(routes.flat())].sort();
34
+ }
35
+
36
+ /** The page a visitor lands on when the site has no home page: the top-most page, then the first by name. */
37
+ export function pickLandingRoute(routes: string[]): string | undefined {
38
+ const depth = (route: string) => route.split("/").length;
39
+ return [...routes].sort(
40
+ (a, b) => depth(a) - depth(b) || a.localeCompare(b),
41
+ )[0];
42
+ }
43
+
44
+ export type HomeRedirectInput = {
45
+ /** APItoGo builds the docs pages. */
46
+ hostsDocs: boolean;
47
+ /** Routes of those pages (see `docRoutes`). */
48
+ docRoutes: string[];
49
+ /** Where the docs live when someone else hosts them. */
50
+ externalDocsUrl?: string;
51
+ /** The portal has an API reference page, at this path. */
52
+ apiReferencePath?: string;
53
+ /** The portal has console pages (sign-in, keys, plans). */
54
+ userPanel: boolean;
55
+ };
56
+
57
+ /**
58
+ * Where `/` should go when the portal has no page of its own there. Without this a visitor of the home page sees
59
+ * "Page not found".
60
+ *
61
+ * - Hosted docs without an index page: the first page, so the team's own docs never need an extra file.
62
+ * - Hosted docs without any page: the API reference, when there is one.
63
+ * - External docs and no console: the external docs site, as the portal has nothing else to show.
64
+ */
65
+ export function homeRedirect(
66
+ input: HomeRedirectInput,
67
+ ): HomeRedirect | undefined {
68
+ if (input.hostsDocs) {
69
+ if (input.docRoutes.some((route) => route === "/" || route === "/index"))
70
+ return undefined;
71
+ const landing = pickLandingRoute(input.docRoutes);
72
+ if (landing) return { from: "/", to: landing };
73
+ return input.apiReferencePath
74
+ ? { from: "/", to: input.apiReferencePath }
75
+ : undefined;
76
+ }
77
+ if (input.externalDocsUrl && !input.userPanel) {
78
+ return { from: "/", to: input.externalDocsUrl };
79
+ }
80
+ return undefined;
81
+ }
@@ -5,6 +5,7 @@ import {
5
5
  ReferenceSpecError,
6
6
  renderReferenceSpec,
7
7
  } from "./gateway-reference.js";
8
+ import { docRoutes, type HomeRedirect, homeRedirect } from "./home-redirect.js";
8
9
  import {
9
10
  hasCapability,
10
11
  missingManifestServices,
@@ -243,6 +244,7 @@ export function buildSiteConfig(
243
244
  resolved: ResolvedConfigV2,
244
245
  plan: PortalPlan,
245
246
  theme?: unknown,
247
+ redirects: HomeRedirect[] = [],
246
248
  ): Record<string, unknown> {
247
249
  const portal = resolved.portal;
248
250
  return {
@@ -283,6 +285,7 @@ export function buildSiteConfig(
283
285
  ],
284
286
  }
285
287
  : {}),
288
+ ...(redirects.length > 0 ? { redirects } : {}),
286
289
  ...(theme !== undefined ? { theme } : {}),
287
290
  ...(plan.auth ? { authentication: { type: "openid", ...plan.auth } } : {}),
288
291
  ...(plan.preview ? { preview: true } : {}),
@@ -430,7 +433,19 @@ export async function preparePortal(
430
433
  });
431
434
  }
432
435
  }
433
- const siteConfig = buildSiteConfig(resolved, plan, await readTheme(resolved));
436
+ const home = homeRedirect({
437
+ hostsDocs: plan.docs,
438
+ docRoutes: plan.docs ? await docRoutes(profile.docs.content) : [],
439
+ externalDocsUrl: profile.docs.url,
440
+ apiReferencePath: plan.apiReference ? `/${API_REFERENCE_PATH}` : undefined,
441
+ userPanel: plan.userPanel,
442
+ });
443
+ const siteConfig = buildSiteConfig(
444
+ resolved,
445
+ plan,
446
+ await readTheme(resolved),
447
+ home ? [home] : [],
448
+ );
434
449
  const wrapper = renderWrapper(plan);
435
450
  const wrapperPath = path.join(resolved.state.generatedDir, WRAPPER_FILE);
436
451
  if (options.write !== false) {