Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/theme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
"#exports-comment": [
"The TypeScript entry points resolve to src/ for tooling that can read",
"TypeScript directly — tsc via `types`, node via jiti/tsgo, and bundlers",
"that opt into the `bundler` condition (see web/scripts/build-web.mjs).",
"that opt into the `bundler` condition (see web/scripts/build-web.ts).",
"`default` keeps the built dist/ output for plain Node and any consumer",
"that does not set a condition; publishConfig.exports points every entry",
"at dist/ for the published tarball.",
Expand Down
2 changes: 1 addition & 1 deletion web/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @import { StorybookConfig } from "@storybook/web-components-vite";
*/

import { copyAssets } from "../scripts/build-assets.mjs";
import { copyAssets } from "../scripts/build-assets.ts";

/**
* @param {TemplateStringsArray} strings
Expand Down
2 changes: 1 addition & 1 deletion web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ scripts/ # Build scripts (esbuild config, localization)
- `src/elements/Interface.ts` — Base interface class with context management
- `src/common/global.ts` — Global authentik config and state
- `src/flow/FlowExecutor.ts` — Flow execution engine
- `scripts/build-web.mjs` — Main ESBuild configuration
- `scripts/build-web.ts` — Main ESBuild configuration

### Conventions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
* styles and expose CSS parts (`title`, `content`) to host pages.
*/

import { rehypeAnchors, rehypeMermaid } from "./rehype.js";
import { rehypeAnchors, rehypeMermaid } from "./rehype.ts";
import {
normalizeAdmonitionLabels,
remarkAdmonition,
remarkHeadings,
remarkLists,
} from "./remark.js";
} from "./remark.ts";

import GithubSlugger from "github-slugger";
import type { Element, Root } from "hast";
import { toHtml } from "hast-util-to-html";
import apacheGrammar from "highlight.js/lib/languages/apache";
import diffGrammar from "highlight.js/lib/languages/diff";
Expand All @@ -33,11 +34,11 @@ import { parse as parseYAML } from "yaml";
/**
* Pull a YAML frontmatter block off the top of `source` and return both
* pieces. Returns an empty object if there is no frontmatter.
*
* @param {string} source
* @returns {{ body: string, frontmatter: Record<string, unknown> }}
*/
function splitFrontmatter(source) {
function splitFrontmatter(source: string): {
body: string;
frontmatter: Record<string, unknown>;
} {
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
if (!match) return { body: source, frontmatter: {} };
const frontmatter = parseYAML(match[1]) || {};
Expand All @@ -51,13 +52,11 @@ function splitFrontmatter(source) {
* escaping rules — no hand-rolled `&`/`<`/`>`/`"` replacement that has
* to be remembered and audited separately.
*
* @param {import('hast').Element[]} bodyChildren Hast nodes from the markdown pipeline.
* @param {string | null} title Frontmatter title, or `null` to omit the `<h1>`.
* @returns {string}
* @param bodyChildren Hast nodes from the markdown pipeline.
* @param title Frontmatter title, or `null` to omit the `<h1>`.
*/
function renderEnvelope(bodyChildren, title) {
/** @type {import('hast').Element[]} */
const children = [];
function renderEnvelope(bodyChildren: Element[], title: string | null): string {
const children: Element["children"] = [];

if (title) {
children.push({
Expand All @@ -70,8 +69,7 @@ function renderEnvelope(bodyChildren, title) {

children.push(...bodyChildren);

/** @type {import('hast').Root} */
const root = {
const root: Root = {
type: "root",
children: [
{
Expand All @@ -91,12 +89,14 @@ function renderEnvelope(bodyChildren, title) {
* frontmatter. Used by the build-time plugin; the runtime side mirrors
* this pipeline in the browser for admin-supplied prose.
*
* @param {string} source
* @param {string} publicDirectory Path of the file's directory inside the
* docs site, used to resolve relative `<a>` hrefs at build time.
* @returns {Promise<{ html: string, frontmatter: Record<string, unknown> }>}
* @param source The markdown source.
* @param publicDirectory Path of the file's directory inside the docs site,
* used to resolve relative `<a>` hrefs at build time.
*/
export async function compileMarkdown(source, publicDirectory) {
export async function compileMarkdown(
source: string,
publicDirectory: string,
): Promise<{ html: string; frontmatter: Record<string, unknown> }> {
const { body: rawBody, frontmatter } = splitFrontmatter(source);
const body = normalizeAdmonitionLabels(rawBody);
const slugger = new GithubSlugger();
Expand Down Expand Up @@ -125,12 +125,10 @@ export async function compileMarkdown(source, publicDirectory) {
})
.use(rehypeMermaid);

const tree = /** @type {import('hast').Root} */ (
await processor.run(processor.parse(body), body)
);
const tree = (await processor.run(processor.parse(body), body)) as Root;

const title = typeof frontmatter.title === "string" ? frontmatter.title : null;
const html = renderEnvelope(/** @type {import('hast').Element[]} */ (tree.children), title);
const html = renderEnvelope(tree.children as Element[], title);

return { html, frontmatter };
}
52 changes: 20 additions & 32 deletions web/bundler/mdx-plugin/node.js → web/bundler/mdx-plugin/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,43 @@
* over the existing fetch-then-set-innerHTML path used by `<ak-mdx>`. The
* shape is `{ content, frontmatter, publicPath, publicDirectory }` where
* `content` is now pre-rendered HTML rather than raw markdown source.
*
* @import {
* OnLoadArgs,
* OnLoadResult,
* OnResolveArgs,
* OnResolveResult,
* Plugin,
* PluginBuild
* } from "esbuild"
*/

import * as fs from "node:fs/promises";
import * as path from "node:path";

import { compileMarkdown } from "./compile.js";
import { compileMarkdown } from "./compile.ts";

import { MonoRepoRoot } from "@goauthentik/core/paths/node";

import type {
OnLoadArgs,
OnLoadResult,
OnResolveArgs,
OnResolveResult,
Plugin,
PluginBuild,
} from "esbuild";

const pluginName = "mdx-plugin";

/**
* @typedef MDXPluginOptions
* @property {string} root Repository root.
*/
export interface MDXPluginOptions {
/**
* Repository root.
*/
root: string;
}

/**
* Bundle markdown and MDX source into JSON modules.
*
* @param {MDXPluginOptions} options
* @returns {Plugin}
*/
export function mdxPlugin({ root }) {
export function mdxPlugin({ root }: MDXPluginOptions): Plugin {
const prefix = "~docs";
// TODO: Replace with `resolvePackage` after NPM Workspaces support is added.
const docsPackageRoot = path.resolve(MonoRepoRoot, "website");

/**
* @param {PluginBuild} build
*/
function setup(build) {
/**
* @param {OnResolveArgs} args
* @returns {Promise<OnResolveResult>}
*/
async function resolveListener(args) {
function setup(build: PluginBuild) {
async function resolveListener(args: OnResolveArgs): Promise<OnResolveResult> {
if (!args.path.startsWith("~")) return args;

return {
Expand All @@ -65,11 +57,7 @@ export function mdxPlugin({ root }) {
};
}

/**
* @param {OnLoadArgs} args
* @returns {Promise<OnLoadResult>}
*/
async function loadListener(args) {
async function loadListener(args: OnLoadArgs): Promise<OnLoadResult> {
const source = String(await fs.readFile(args.path));

const publicPath = path.resolve(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@

import { CurrentReleaseDocsURL } from "@goauthentik/core/version/node";

import type { Element, Root } from "hast";
import { SKIP, visit } from "unist-util-visit";

/**
* Resolve a relative `href` against the docs base URL. Same logic the old
* runtime `MDXAnchor` used: take a `./...` href relative to the file's
* `publicDirectory`, drop trailing `index`/`.md`/`.mdx`, and absolutize
* against {@linkcode CurrentReleaseDocsURL}.
*
* @param {string} href
* @param {string} publicDirectory
* @returns {string}
*/
function resolveDocsHref(href, publicDirectory) {
function resolveDocsHref(href: string, publicDirectory: string): string {
// `new URL(...)` against `file:///` lets us reuse the browser-style
// path resolver while preserving the hash and any query string.
const joined = `${publicDirectory}/${href}`.replace(/\/{2,}/g, "/");
Expand All @@ -28,6 +25,10 @@ function resolveDocsHref(href, publicDirectory) {
return next.toString();
}

export interface RehypeAnchorsOptions {
publicDirectory: string;
}

/**
* Rehype plugin: resolve relative anchors at build time and wrap every
* `<a>` in an `<ak-md-a>` light-DOM custom element. The wrapper attaches
Expand All @@ -40,11 +41,9 @@ function resolveDocsHref(href, publicDirectory) {
* `<ak-mdx>`'s shadow tree where the existing PatternFly link CSS in
* `styles.css` applies. The wrapper itself uses `display: contents` so
* it does not perturb inline-flow layout.
*
* @param {{ publicDirectory: string }} options
*/
export function rehypeAnchors({ publicDirectory }) {
return (/** @type {import('hast').Root} */ tree) => {
export function rehypeAnchors({ publicDirectory }: RehypeAnchorsOptions) {
return (tree: Root) => {
visit(tree, "element", (node) => {
if (node.tagName !== "a") return;

Expand All @@ -69,8 +68,7 @@ export function rehypeAnchors({ publicDirectory }) {
// the visitor from descending into the freshly-stamped
// child anchor (which would re-match this filter and
// recurse forever).
/** @type {import('hast').Element} */
const original = {
const original: Element = {
type: "element",
tagName: "a",
properties: { ...props },
Expand All @@ -93,7 +91,7 @@ export function rehypeAnchors({ publicDirectory }) {
* wrapper element is needed.
*/
export function rehypeMermaid() {
return (/** @type {import('hast').Root} */ tree) => {
return (tree: Root) => {
visit(tree, "element", (node) => {
if (node.tagName !== "pre") return;
const child = node.children?.[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* either pipeline grows a new transform.
*/

import type GithubSlugger from "github-slugger";
import type { Root } from "mdast";
import { visit } from "unist-util-visit";

const ADMONITIONS = new Set(["info", "warning", "danger", "note", "caution", "tip"]);
Expand All @@ -14,7 +16,7 @@ const ADMONITIONS = new Set(["info", "warning", "danger", "note", "caution", "ti
* `caution` and `tip` aren't first-class PatternFly alert levels — map
* them onto the closest equivalent so PFAlert styles render correctly.
*/
const ADMONITION_LEVEL = {
const ADMONITION_LEVEL: Record<string, string> = {
info: "pf-m-info",
warning: "pf-m-warning",
danger: "pf-m-danger",
Expand All @@ -38,11 +40,7 @@ const ADMONITION_BARE_LABEL_RE = new RegExp(
"gm",
);

/**
* @param {string} source
* @returns {string}
*/
export function normalizeAdmonitionLabels(source) {
export function normalizeAdmonitionLabels(source: string): string {
return source.replace(ADMONITION_BARE_LABEL_RE, "$1[$2]");
}

Expand All @@ -54,7 +52,7 @@ export function normalizeAdmonitionLabels(source) {
* element inside the slot.
*/
export function remarkAdmonition() {
return (/** @type {import('mdast').Root} */ tree) => {
return (tree: Root) => {
visit(tree, (node) => {
if (
node.type !== "containerDirective" &&
Expand All @@ -71,12 +69,11 @@ export function remarkAdmonition() {
data.hProperties = {
...(data.hProperties || {}),
...(node.attributes || {}),
level:
/** @type {Record<string, string>} */ (ADMONITION_LEVEL)[node.name] ??
`pf-m-${node.name}`,
level: ADMONITION_LEVEL[node.name] ?? `pf-m-${node.name}`,
};

const children = /** @type {any[]} */ (node.children || []);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const children = (node.children || []) as any[];
const labelIndex = children.findIndex(
(c) => c.type === "paragraph" && c.data?.directiveLabel,
);
Expand All @@ -91,31 +88,24 @@ export function remarkAdmonition() {
};
}

/**
* @typedef {object} RemarkHeadingsOptions
* @property {import("github-slugger").default} slugger
*/
export interface RemarkHeadingsOptions {
slugger: GithubSlugger;
}

/**
* Remark plugin: heading slugs into `id` attributes.
*
* Uses `github-slugger` to match the anchor IDs Docusaurus generates for the
* same content.
*
* @param {RemarkHeadingsOptions} options
*/
export function remarkHeadings({ slugger }) {
/**
* @param {{ value?: string, children?: any[] }} n
* @returns {string}
*/
const flatten = (n) => {
export function remarkHeadings({ slugger }: RemarkHeadingsOptions) {
const flatten = (n: { value?: string; children?: unknown[] }): string => {
if (n.value) return n.value;
if (n.children) return n.children.map(flatten).join("");
if (n.children) return n.children.map((child) => flatten(child as typeof n)).join("");
return "";
};

return (/** @type {import('mdast').Root} */ tree) => {
return (tree: Root) => {
visit(tree, "heading", (node) => {
const id = slugger.slug(flatten(node));
const data = node.data || (node.data = {});
Expand All @@ -128,7 +118,7 @@ export function remarkHeadings({ slugger }) {
* Remark plugin: tag lists with PatternFly's content class.
*/
export function remarkLists() {
return (/** @type {import('mdast').Root} */ tree) => {
return (tree: Root) => {
visit(tree, "list", (node) => {
const data = node.data || (node.data = {});
data.hProperties = {
Expand Down
Loading
Loading