Skip to content

Allow submodule version aliases for import paths #202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 9, 2025
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/dependency-review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ jobs:
with:
allow-additional-licenses: >
LicenseRef-scancode-public-domain AND Unlicense
# @swc/core@1.11.24 uses Apache-2.0, but
# LicenseRef-scancode-unknown-license-reference is also detected.
# https://www.npmjs.com/package/@swc/core/v/1.11.24?activeTab=code
# https://scancode-licensedb.aboutcode.org/unknown-license-reference.html
allow-dependencies-licenses: >
pkg:npm/swc/core,
pkg:npm/%40inkeep/cxkit-color-mode,
pkg:npm/%40inkeep/cxkit-primitives,
pkg:npm/%40inkeep/cxkit-react,
Expand Down
2 changes: 2 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import remarkUpdateAssetPaths from "./server/remark-update-asset-paths";
import remarkIncludes from "./server/remark-includes";
import remarkVariables from "./server/remark-variables";
import remarkVersionAlias from "./server/remark-version-alias";
import remarkCodeSnippet from "./server/remark-code-snippet";
import { fetchVideoMeta } from "./server/youtube-meta";
import { getRedirects } from "./server/redirects";
Expand Down Expand Up @@ -214,6 +215,7 @@ const config: Config = {
versions: getDocusaurusConfigVersionOptions(),
// Our custom plugins need to be before default plugins
beforeDefaultRemarkPlugins: [
[remarkVersionAlias, latestVersion],
[
remarkIncludes,
{
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"nanoid": "^5.1.0",
"postcss-preset-env": "^10.1.6",
"prism-react-renderer": "^2.3.0",
"raw-loader": "^4.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-loadable": "^5.5.0",
Expand Down Expand Up @@ -139,4 +140,4 @@
"engines": {
"node": ">=18.0"
}
}
}
100 changes: 100 additions & 0 deletions server/remark-version-alias.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { describe, expect, test } from "@jest/globals";
import { VFile, VFileOptions } from "vfile";
import { remark } from "remark";
import mdx from "remark-mdx";
import remarkVersionAlias from "./remark-version-alias";
import remarkFrontmatter from "remark-frontmatter";

const transformer = (vfileOptions: VFileOptions) => {
const file: VFile = new VFile(vfileOptions);

return remark()
.use(mdx as any)
.use(remarkFrontmatter) // Test cases use frontmatter
.use(remarkVersionAlias as any, "15.x")
.processSync(file as any);
};

describe("server/remark-version-alias", () => {
interface testCase {
description: string;
input: string;
expected: string;
path: string;
}

const testCases: Array<testCase> = [
{
description: "import statement in latest-version docs path",
input: `---
title: My page
description: My page
---

import CodeExample from "@version/examples/access-plugin-minimal/config.go"

This is a paragraph.`,
expected: `---
title: My page
description: My page
---

import CodeExample from '@site/content/15.x/examples/access-plugin-minimal/config.go'

This is a paragraph.
`,
path: "docs/mypage.mdx",
},
{
description: "import statement in non-latest docs path",
input: `---
title: My page
description: My page
---

import CodeExample from "@version/examples/access-plugin-minimal/config.go"

This is a paragraph.`,
expected: `---
title: My page
description: My page
---

import CodeExample from '@site/content/16.x/examples/access-plugin-minimal/config.go'

This is a paragraph.
`,
path: "versioned_docs/version-16.x/mypage.mdx",
},
{
description: "raw loader",
input: `---
title: My page
description: My page
---

import CodeExample from "!!raw-loader!@version/examples/access-plugin-minimal/config.go"

This is a paragraph.`,
expected: `---
title: My page
description: My page
---

import CodeExample from '!!raw-loader!@site/content/16.x/examples/access-plugin-minimal/config.go'

This is a paragraph.
`,
path: "versioned_docs/version-16.x/mypage.mdx",
},
];

test.each(testCases)("$description", (tc) => {
const result = transformer({
value: tc.input,
path: tc.path,
}).toString();

expect(result).toEqual(tc.expected);
});
});
54 changes: 54 additions & 0 deletions server/remark-version-alias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { MdxjsEsm } from "mdast-util-mdxjs-esm";
import type { Root, Paragraph, Literal } from "mdast";
import type { VFile } from "vfile";
import type { Transformer } from "unified";
import type { Node } from "unist";
import { visit, CONTINUE, SKIP } from "unist-util-visit";

const versionedDocsPattern = `versioned_docs/version-([0-9]+\\.x)/`;

export default function remarkVersionAlias(latestVersion: string): Transformer {
return (root: Root, vfile: VFile) => {
visit(root, (node: Node) => {
if (node.type != "mdxjsEsm") {
return CONTINUE;
}

// Only process import statements that import an identifier from a default
// export.
const esm = node as unknown as MdxjsEsm;
if (
!esm.data ||
!esm.data.estree ||
esm.data.estree.body.length !== 1 ||
esm.data.estree.body[0]["type"] != "ImportDeclaration" ||
esm.data.estree.body[0].specifiers.length !== 1 ||
esm.data.estree.body[0].specifiers[0].type != "ImportDefaultSpecifier"
) {
return CONTINUE;
}

let version: string = latestVersion;
const versionedPathParts = vfile.path.match(versionedDocsPattern);
if (versionedPathParts) {
version = versionedPathParts[1];
}

const decl = esm.data.estree.body[0];

const newPath = (decl.source.value as string).replace(
"@version",
`@site/content/${version}`,
);

esm.value = `import ${esm.data.estree.body[0].specifiers[0].local.name} from '${newPath}'`;
decl.source = {
type: "Literal",
value: newPath,
raw: `"${newPath}"`,
};

return SKIP;
});
};
}
Loading