Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ out
*.generated.*
/.cache
/pages/api
/pages/loaders/
/pages/plugins/
2 changes: 1 addition & 1 deletion components/Footer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';
import LinkedInIcon from '@node-core/ui-components/Icons/Social/LinkedIn';
import DiscordIcon from '@node-core/ui-components/Icons/Social/Discord';
import XIcon from '@node-core/ui-components/Icons/Social/X';
import { footer } from '#theme/site' with { type: 'json' };
import { footer } from '#theme/site';

import Logo from '#theme/Logo';
import styles from './index.module.css';
Expand Down
2 changes: 1 addition & 1 deletion components/NavBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';

import SearchBox from '@node-core/doc-kit/src/generators/web/ui/components/SearchBox';
import { useTheme } from '@node-core/doc-kit/src/generators/web/ui/hooks/useTheme.mjs';
import { navbar } from '#theme/site' with { type: 'json' };
import { navbar } from '#theme/site';
import Logo from '#theme/Logo';

/**
Expand Down
29 changes: 19 additions & 10 deletions components/SideBar.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import SideBar from '@node-core/ui-components/Containers/Sidebar';
import { sidebar } from '#theme/local/site' with { type: 'json' };
import { sidebar } from '#theme/local/site';

/** @param {string} url */
const redirect = url => (window.location.href = url);
Expand All @@ -8,15 +8,24 @@ const PrefetchLink = props => <a {...props} rel="prefetch" />;

const pathnameFor = path => path.replace(/\/index$/, '') || '/';

const groupsFor = path => {
const segment = path.split('/').filter(Boolean)[0];
const matched = sidebar.filter(g => g.groupName.toLowerCase() === segment);
return matched.length > 0 ? matched : sidebar;
};

/**
* Sidebar component for MDX documentation with page navigation.
*/
export default ({ metadata }) => (
<SideBar
pathname={pathnameFor(metadata.path)}
groups={sidebar}
onSelect={redirect}
as={PrefetchLink}
title="Navigation"
/>
);
export default ({ metadata }) => {
const path = pathnameFor(metadata.path);
return (
<SideBar
pathname={path}
groups={groupsFor(path)}
onSelect={redirect}
as={PrefetchLink}
title="Navigation"
/>
);
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
"scripts": {
"prep": "node scripts/prepare/index.mjs",
"build:md": "node scripts/markdown/index.mjs",
"build:md:loaders": "node scripts/fetch-readmes.mjs --loaders",
"build:md:plugins": "node scripts/fetch-readmes.mjs --plugins",
"build:md:readmes": "node scripts/fetch-readmes.mjs",
"build:html": "node scripts/html/index.mjs",
"build": "npm run prep && npm run build:md && npm run build:html",
"build": "npm run prep && npm run build:md && npm run build:md:readmes && npm run build:html",
Comment thread
ryzrr marked this conversation as resolved.
Outdated
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write .",
Expand Down
7 changes: 7 additions & 0 deletions pages/site.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import base from './site.json' with { type: 'json' };
import loadersSite from './loaders/site.json' with { type: 'json' };
import pluginsSite from './plugins/site.json' with { type: 'json' };
Comment thread
ryzrr marked this conversation as resolved.
Outdated

export const { navbar, footer } = base;

export const sidebar = [...loadersSite.sidebar, ...pluginsSite.sidebar];
Comment thread
ryzrr marked this conversation as resolved.
Outdated
199 changes: 199 additions & 0 deletions scripts/fetch-readmes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

const { GH_TOKEN } = process.env;

const BASE_HEADERS = {
...(GH_TOKEN && { Authorization: `Bearer ${GH_TOKEN}` }),
'X-GitHub-Api-Version': '2022-11-28',
};

const parseNextLink = linkHeader => {
if (!linkHeader) return null;
const match = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
return match ? match[1] : null;
};

const discoverRepos = async () => {
const loaders = [];
const plugins = [];
let url =
'https://api.github.com/orgs/webpack/repos?per_page=100&type=public';

while (url) {
const res = await fetch(url, { headers: BASE_HEADERS });
if (!res.ok)
throw new Error(
`Failed to list org repos: ${res.status} ${res.statusText}`
);

const repos = await res.json();
for (const repo of repos) {
if (repo.archived) continue;
if (repo.name.endsWith('-loader')) {
loaders.push(repo.full_name);
} else if (
repo.name.endsWith('-webpack-plugin') ||
repo.name.endsWith('-plugin')
Comment thread
ryzrr marked this conversation as resolved.
Outdated
) {
plugins.push(repo.full_name);
}
}

url = parseNextLink(res.headers.get('link'));
}

return { loaders, plugins };
};

const stripLeadingDiv = content =>
content.replace(/^\s*<div[\s\S]*?<\/div>\n*/i, '');

// Remove badge lines - lines consisting only of [![...][ref]][ref] or [![...](url)](url) links
const stripBadges = content =>
content
.replace(
/^(\[!\[[^\]]*\](?:\[[^\]]*\]|\([^)]*\))\]\s*(?:\[[^\]]*\]|\([^)]*\))\s*)+$/gm,
''
)
.replace(/\n{3,}/g, '\n\n');

// TODO: remove this allowlist once Shiki silently skips unknown languages instead of build errors.
const SUPPORTED_LANGS = new Set([
'bash',
'c',
'c++',
'cjs',
'coffee',
'coffeescript',
'console',
'cpp',
'diff',
'docker',
'dockerfile',
'glsl',
'gql',
'graphql',
'http',
'ini',
'java',
'javascript',
'js',
'json',
'jsx',
'mjs',
'powershell',
'ps',
'ps1',
'regex',
'regexp',
'sh',
'shell',
'shellscript',
'shellsession',
'sql',
'ts',
'tsx',
'typescript',
'xml',
'yaml',
'yml',
'zsh',
]);

const sanitizeCodeFences = content =>
content.replace(/^```([a-zA-Z0-9_+-]+)\b/gm, (match, lang) =>
SUPPORTED_LANGS.has(lang.toLowerCase()) ? match : '```'
);
Comment thread
avivkeller marked this conversation as resolved.
Outdated
Comment thread
avivkeller marked this conversation as resolved.
Outdated
Comment thread
ryzrr marked this conversation as resolved.
Outdated

// remark-gfm does not support GitHub alert syntax (> [!TYPE]); rewrite to bold label inside the blockquote.
const GFM_ALERT_LABELS = {
NOTE: 'Note',
TIP: 'Tip',
IMPORTANT: 'Important',
WARNING: 'Warning',
CAUTION: 'Caution',
};
const GFM_ALERT_RE =
/^([ \t]*>[ \t]*)\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*$/gim;

const transformGfmAlerts = content =>
content.replace(
GFM_ALERT_RE,
(_, prefix, type) => `${prefix}**${GFM_ALERT_LABELS[type]}:**`
);
Comment thread
avivkeller marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread
ryzrr marked this conversation as resolved.
Outdated

const processContent = content =>
transformGfmAlerts(sanitizeCodeFences(stripBadges(stripLeadingDiv(content))));

const fetchReadme = async fullName => {
const url = `https://raw.githubusercontent.com/${fullName}/HEAD/README.md`;
const res = await fetch(url);
return res.ok
? { ok: true, text: await res.text() }
: { ok: false, status: res.status };
};

const processRepos = async (repos, { groupName, basePath, outputDir }) => {
mkdirSync(outputDir, { recursive: true });
const repoName = r => r.split('/')[1];
console.log(
`Discovered ${groupName.toLowerCase()}: ${repos.map(repoName).join(', ')}`
);

const fetched = [];
for (const fullName of repos) {
const name = repoName(fullName);
const result = await fetchReadme(fullName);
if (!result.ok) {
console.log(`Failed: ${name} — ${result.status}`);
continue;
}
const content = processContent(result.text);
writeFileSync(join(outputDir, `${name}.md`), content, 'utf8');
fetched.push(name);
console.log(`Fetched: ${name}`);
}
Comment thread
ryzrr marked this conversation as resolved.
Outdated

const siteJson = {
sidebar: [
{
groupName,
items: fetched
.sort()
.map(name => ({ link: `${basePath}/${name}`, label: name })),
},
],
};
writeFileSync(
join(outputDir, 'site.json'),
JSON.stringify(siteJson, null, 2) + '\n',
'utf8'
);
console.log(
`Written: ${outputDir}/site.json (${fetched.length} ${groupName.toLowerCase()})`
);
Comment thread
avivkeller marked this conversation as resolved.
Outdated
};

const args = process.argv.slice(2);
const runLoaders = args.includes('--loaders') || args.length === 0;
const runPlugins = args.includes('--plugins') || args.length === 0;

const root = join(import.meta.dirname, '..');
Comment thread
ryzrr marked this conversation as resolved.
Outdated
const { loaders, plugins } = await discoverRepos();
Comment thread
ryzrr marked this conversation as resolved.
Outdated

if (runLoaders) {
await processRepos(loaders, {
groupName: 'Loaders',
basePath: '/loaders',
outputDir: join(root, 'pages/loaders'),
});
}

if (runPlugins) {
await processRepos(plugins, {
groupName: 'Plugins',
basePath: '/plugins',
outputDir: join(root, 'pages/plugins'),
});
}
6 changes: 4 additions & 2 deletions scripts/html/doc-kit.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ export default {
useAbsoluteURLs: true,
remoteConfigUrl: null,
imports: {
'#theme/local/site': join(inputDir, 'site.json'),
'#theme/local/site': VERSION
? join(inputDir, 'site.json')
: join(ROOT, 'pages/site.mjs'),
Comment thread
ryzrr marked this conversation as resolved.

'#theme/Sidebar': join(ROOT, 'components/SideBar.jsx'),
'#theme/site': join(ROOT, 'pages/site.json'),
'#theme/site': join(ROOT, 'pages/site.mjs'),
'#theme/Layout': join(ROOT, 'components/Layout.jsx'),
'#theme/Navigation': join(ROOT, 'components/NavBar.jsx'),
'#theme/Footer': join(ROOT, 'components/Footer/index.jsx'),
Expand Down