From af1955ff6a066c7f86e2542df2cf7cc6cd12d4f3 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Rajak Date: Sat, 20 Jun 2026 13:45:04 +0000 Subject: [PATCH 1/2] feat(banner): show latest webpack release instead of Node.js banner --- scripts/data/banners.mjs | 71 +++++++++++++++++++++++++++++++++ scripts/html/doc-kit.config.mjs | 2 +- scripts/html/index.mjs | 9 ++++- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 scripts/data/banners.mjs diff --git a/scripts/data/banners.mjs b/scripts/data/banners.mjs new file mode 100644 index 00000000..6051b791 --- /dev/null +++ b/scripts/data/banners.mjs @@ -0,0 +1,71 @@ +// Builds the banner config doc-kit fetches at runtime, pointing it at the +// latest webpack release . + +import { readdir, readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import matter from 'gray-matter'; +import { major, minor } from 'semver'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); +const POSTS_DIR = join(ROOT, 'pages', 'blog', 'posts'); + +// How long the banner stays up after a release. +const BANNER_DURATION_DAYS = 7; + +const titleFromBody = body => body.match(/^#\s+(.+)$/m)?.[1].trim() ?? null; + +const isRelease = category => + typeof category === 'string' && category.toLowerCase() === 'release'; + +// Reads `category: Release` blog posts, newest first. +const readReleasePosts = async () => { + const files = (await readdir(POSTS_DIR)).filter(name => name.endsWith('.md')); + + const posts = await Promise.all( + files.map(async file => { + const { data, content } = matter( + await readFile(join(POSTS_DIR, file), 'utf8') + ); + + return isRelease(data.category) + ? { + slug: file.replace(/\.md$/, ''), + title: titleFromBody(content), + date: new Date(data.date), + } + : null; + }) + ); + + return posts.filter(Boolean).sort((a, b) => b.date - a.date); +}; + +// Builds the banner config for `version` (e.g. `v5.107.2`): links to the +// matching release post, or the newest one if none matches. +export const buildWebsiteBanners = async version => { + const releases = await readReleasePosts(); + + if (!releases.length) { + return { websiteBanners: {} }; + } + + const series = new RegExp(`\\b${major(version)}\\.${minor(version)}\\b`); + const release = + releases.find(post => post.title && series.test(post.title)) ?? releases[0]; + + const endDate = new Date(release.date); + endDate.setUTCDate(endDate.getUTCDate() + BANNER_DURATION_DAYS); + + return { + websiteBanners: { + index: { + text: `webpack ${version} is now available - read the release notes`, + link: `/blog/posts/${release.slug}`, + type: 'default', + startDate: release.date.toISOString(), + endDate: endDate.toISOString(), + }, + }, + }; +}; diff --git a/scripts/html/doc-kit.config.mjs b/scripts/html/doc-kit.config.mjs index d58f2d27..bdc9f6b0 100644 --- a/scripts/html/doc-kit.config.mjs +++ b/scripts/html/doc-kit.config.mjs @@ -44,7 +44,7 @@ export default { web: { project: 'webpack', useAbsoluteURLs: true, - remoteConfigUrl: null, + remoteConfigUrl: '/banners.json', title: VERSION ? `Webpack ${MAJOR_VERSION} Documentation` : 'Webpack', editURL: 'https://github.com/webpack/webpack-doc-kit/blob/main/pages/{path}.md', diff --git a/scripts/html/index.mjs b/scripts/html/index.mjs index acdacb3c..79a0ba45 100644 --- a/scripts/html/index.mjs +++ b/scripts/html/index.mjs @@ -1,13 +1,15 @@ import { execFile } from 'node:child_process'; -import { readFile, cp } from 'node:fs/promises'; +import { readFile, writeFile, cp } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { buildWebsiteBanners } from '../data/banners.mjs'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); const ASSETS_SOURCE = join(ROOT, 'assets'); const ASSETS_DESTINATION = join(ROOT, 'out/assets'); +const BANNERS_DESTINATION = join(ROOT, 'out/banners.json'); const execFileAsync = promisify(execFile); @@ -48,3 +50,8 @@ await runDocKit(); // copy assets folder to the out directory await cp(ASSETS_SOURCE, ASSETS_DESTINATION, { recursive: true }); + +// the announcement banner config advertising the latest webpack release + +const banners = await buildWebsiteBanners(versions[0]); +await writeFile(BANNERS_DESTINATION, `${JSON.stringify(banners, null, 2)}\n`); From 5558190d6b5673c1a7466ee0cdbbcaa116574f25 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Rajak Date: Sat, 20 Jun 2026 15:39:55 +0000 Subject: [PATCH 2/2] refactor(banner) : Now it is manually maintained --- assets/banners.json | 19 +++++++++ scripts/data/banners.mjs | 71 --------------------------------- scripts/html/doc-kit.config.mjs | 2 +- scripts/html/index.mjs | 9 +---- 4 files changed, 21 insertions(+), 80 deletions(-) create mode 100644 assets/banners.json delete mode 100644 scripts/data/banners.mjs diff --git a/assets/banners.json b/assets/banners.json new file mode 100644 index 00000000..3dee8025 --- /dev/null +++ b/assets/banners.json @@ -0,0 +1,19 @@ +{ + "//": [ + "Banner that shows at the top of the docs pages.", + "Nothing shows right now because there is no 'index' entry.", + "To put a banner up, copy 'example' below into 'index' and edit the text. Remove 'index' again to take it down.", + "text is required. link, type, startDate and endDate are optional.", + "type is one of 'default' (blue), 'warning' (orange), 'error' (red).", + "startDate and endDate are ISO dates that limit when the banner shows." + ], + "websiteBanners": { + "example": { + "text": "webpack 5.x.0 is now available, read the release notes", + "link": "/blog/posts/your-release-post", + "type": "default", + "startDate": "2026-01-01T00:00:00.000Z", + "endDate": "2026-01-08T00:00:00.000Z" + } + } +} diff --git a/scripts/data/banners.mjs b/scripts/data/banners.mjs deleted file mode 100644 index 6051b791..00000000 --- a/scripts/data/banners.mjs +++ /dev/null @@ -1,71 +0,0 @@ -// Builds the banner config doc-kit fetches at runtime, pointing it at the -// latest webpack release . - -import { readdir, readFile } from 'node:fs/promises'; -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import matter from 'gray-matter'; -import { major, minor } from 'semver'; - -const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); -const POSTS_DIR = join(ROOT, 'pages', 'blog', 'posts'); - -// How long the banner stays up after a release. -const BANNER_DURATION_DAYS = 7; - -const titleFromBody = body => body.match(/^#\s+(.+)$/m)?.[1].trim() ?? null; - -const isRelease = category => - typeof category === 'string' && category.toLowerCase() === 'release'; - -// Reads `category: Release` blog posts, newest first. -const readReleasePosts = async () => { - const files = (await readdir(POSTS_DIR)).filter(name => name.endsWith('.md')); - - const posts = await Promise.all( - files.map(async file => { - const { data, content } = matter( - await readFile(join(POSTS_DIR, file), 'utf8') - ); - - return isRelease(data.category) - ? { - slug: file.replace(/\.md$/, ''), - title: titleFromBody(content), - date: new Date(data.date), - } - : null; - }) - ); - - return posts.filter(Boolean).sort((a, b) => b.date - a.date); -}; - -// Builds the banner config for `version` (e.g. `v5.107.2`): links to the -// matching release post, or the newest one if none matches. -export const buildWebsiteBanners = async version => { - const releases = await readReleasePosts(); - - if (!releases.length) { - return { websiteBanners: {} }; - } - - const series = new RegExp(`\\b${major(version)}\\.${minor(version)}\\b`); - const release = - releases.find(post => post.title && series.test(post.title)) ?? releases[0]; - - const endDate = new Date(release.date); - endDate.setUTCDate(endDate.getUTCDate() + BANNER_DURATION_DAYS); - - return { - websiteBanners: { - index: { - text: `webpack ${version} is now available - read the release notes`, - link: `/blog/posts/${release.slug}`, - type: 'default', - startDate: release.date.toISOString(), - endDate: endDate.toISOString(), - }, - }, - }; -}; diff --git a/scripts/html/doc-kit.config.mjs b/scripts/html/doc-kit.config.mjs index bdc9f6b0..6e5348be 100644 --- a/scripts/html/doc-kit.config.mjs +++ b/scripts/html/doc-kit.config.mjs @@ -44,7 +44,7 @@ export default { web: { project: 'webpack', useAbsoluteURLs: true, - remoteConfigUrl: '/banners.json', + remoteConfigUrl: '/assets/banners.json', title: VERSION ? `Webpack ${MAJOR_VERSION} Documentation` : 'Webpack', editURL: 'https://github.com/webpack/webpack-doc-kit/blob/main/pages/{path}.md', diff --git a/scripts/html/index.mjs b/scripts/html/index.mjs index 79a0ba45..acdacb3c 100644 --- a/scripts/html/index.mjs +++ b/scripts/html/index.mjs @@ -1,15 +1,13 @@ import { execFile } from 'node:child_process'; -import { readFile, writeFile, cp } from 'node:fs/promises'; +import { readFile, cp } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; -import { buildWebsiteBanners } from '../data/banners.mjs'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); const ASSETS_SOURCE = join(ROOT, 'assets'); const ASSETS_DESTINATION = join(ROOT, 'out/assets'); -const BANNERS_DESTINATION = join(ROOT, 'out/banners.json'); const execFileAsync = promisify(execFile); @@ -50,8 +48,3 @@ await runDocKit(); // copy assets folder to the out directory await cp(ASSETS_SOURCE, ASSETS_DESTINATION, { recursive: true }); - -// the announcement banner config advertising the latest webpack release - -const banners = await buildWebsiteBanners(versions[0]); -await writeFile(BANNERS_DESTINATION, `${JSON.stringify(banners, null, 2)}\n`);