Skip to content
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
60 changes: 60 additions & 0 deletions .github/workflows/create-blog-post.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Create Blog Post

on:
workflow_call:
inputs:
tag:
description: 'Release tag (e.g. v5.99.0)'
required: true
type: string
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v5.99.0)'
required: true
type: string
permissions:
contents: write
pull-requests: write

jobs:
create-post:
runs-on: ubuntu-latest
env:
TAG: ${{ inputs.tag }}
AUTHOR: ${{ github.actor }}

steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: lts/*
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Create post
shell: bash
run: |
set -euo pipefail
node scripts/release/create-post.mjs "$TAG" --author="$AUTHOR"
node --run format

- name: Open pull request
uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit-message: 'chore: blog post for webpack@${{ env.TAG }}'
title: 'chore: blog post for webpack@${{ env.TAG }}'
body: |
Automated blog post by the `${{ env.TAG }}` webpack release.
branch: 'chore/blog-${{ env.TAG }}'
author: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
draft: true
16 changes: 13 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,25 @@ jobs:
shell: bash
run: |
set -euo pipefail
node scripts/update-versions.mjs "$TAG"
node scripts/release/update-versions.mjs "$TAG"

- name: Open pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
uses: gr2m/create-or-update-pull-request-action@b65137ca591da0b9f43bad7b24df13050ea45d1b # v1.10.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync to webpack@${{ env.TAG }}'
title: 'chore: sync to webpack@${{ env.TAG }}'
body: |
Automated version sync triggered by the `${{ env.TAG }}` webpack release.
branch: 'update-versions'
author: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
draft: true

blog-post:
uses: ./.github/workflows/create-blog-post.yml
permissions:
contents: write
pull-requests: write
with:
tag: ${{ inputs.tag }}
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ out
/pages/docs/plugins/*
!/pages/docs/plugins/index.md
versions.json
/generated
/generated
*.hbs
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"eslint": "^10.4.0",
"feed": "^6.0.0",
"globals": "^17.6.0",
"handlebars": "^4.7.9",
"husky": "^9.1.7",
"lint-staged": "^17.0.5",
"prettier": "^3.8.3",
Expand Down
20 changes: 4 additions & 16 deletions scripts/markdown/governance.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fetchWithRetry } from '../utils/fetch.mjs';
import { fetchWithAuth } from '../utils/fetch.mjs';
import { rewriteRelativeLinks } from './sanitize.mjs';

const REPO = 'webpack/governance';
const RAW_URL = `https://raw.githubusercontent.com/${REPO}/HEAD`;
// GitHub's editor needs a real branch name, HEAD only works for reading.
const EDIT_URL = `https://github.com/${REPO}/edit/main`;

const { GH_TOKEN } = process.env;

const BASE_HEADERS = {
...(GH_TOKEN && { Authorization: `Bearer ${GH_TOKEN}` }),
};

// Maps source filenames in webpack/governance repo to their output slug and sidebar label.
// Insertion order determines sidebar order, this could be changed as per need.
const FILE_MAP = {
Expand Down Expand Up @@ -54,9 +43,8 @@ await mkdir(outputDir, { recursive: true });

const results = await Promise.all(
Object.entries(FILE_MAP).map(async ([source, { output, label }]) => {
const res = await fetchWithRetry(`${RAW_URL}/${source}`, {
headers: BASE_HEADERS,
});
const url = `https://raw.githubusercontent.com/webpack/governance/HEAD/${source}`;
const res = await fetchWithAuth(url);

if (!res.ok) {
console.error(`Failed: ${source} -> ${res.status} ${res.statusText}`);
Expand All @@ -72,7 +60,7 @@ const results = await Promise.all(
// site derives the page title from — fall back to the sidebar label.
if (!/^# /m.test(body)) body = `# ${label}\n\n${body}`;

const content = `---\nsource: ${EDIT_URL}/${source}\n---\n\n${body}`;
const content = `---\nsource: https://github.com/webpack/governance/edit/main/${source}\n---\n\n${body}`;
await writeFile(join(outputDir, `${output}.md`), content, 'utf8');
console.log(`Fetched: ${source} -> ${output}.md`);
return { output, label };
Expand Down
11 changes: 2 additions & 9 deletions scripts/markdown/readmes.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fetchWithRetry } from '../utils/fetch.mjs';
import { fetchWithAuth, fetchWithRetry } from '../utils/fetch.mjs';
import cleanupMarkdown from './sanitize.mjs';

const { GH_TOKEN } = process.env;

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

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

Expand All @@ -20,7 +13,7 @@ const discoverRepos = async () => {
'https://api.github.com/orgs/webpack/repos?per_page=100&type=public';

while (url) {
const res = await fetchWithRetry(url, { headers: BASE_HEADERS });
const res = await fetchWithAuth(url);

for (const repo of await res.json()) {
if (repo.archived) continue;
Expand Down
93 changes: 93 additions & 0 deletions scripts/release/create-post.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { readFile, writeFile } from 'node:fs/promises';
import { parseArgs } from 'node:util';

import Handlebars from 'handlebars';
import matter from 'gray-matter';

import { fetchWithAuth } from '../../utils/fetch.mjs';

const { values } = parseArgs({
options: {
// Author
author: { type: 'string', default: 'avivkeller' },
},
});

const API_BASE = 'https://api.github.com/repos/webpack/webpack';
const TEMPLATE_PATH = new URL('./template.md.hbs', import.meta.url);

const fetchJSON = url => fetchWithAuth(url).then(r => r.json());

const getLatestVersion = async () => {
const { tag_name } = await fetchJSON(`${API_BASE}/releases/latest`);
return tag_name;
};

const getChangesets = async version => {
try {
const { files = [] } = await fetchJSON(`${API_BASE}/commits/${version}`);

return files
.filter(
file =>
file.status === 'removed' &&
file.filename.startsWith('.changeset/') &&
file.patch
)
.map(({ patch }) => stripPatchMarkers(patch));
} catch {
const changesets = await fetchJSON(`${API_BASE}/contents/.changeset`);
return Promise.all(
changesets.map(({ download_url }) =>
fetch(download_url).then(d => d.text())
)
);
}
};

const stripPatchMarkers = patch => patch.replace(/^@@.*\n|^[- ]/gm, '');
const capitalize = value => value.charAt(0).toUpperCase() + value.slice(1);

const parseChangeset = file => {
const { data, content } = matter(file);
const [title, ...paragraphs] = content.trim().split(/\n{2,}/);

return {
notable: data.notable === true,
semver: capitalize(data.webpack),
title,
description: paragraphs.join('\n\n'),
};
};

async function renderReleaseNotes(version, changes, date) {
Handlebars.registerHelper('filter', (items, property, expected) =>
items.filter(item => item[property] === expected)
);

Handlebars.registerHelper('groupBy', (items, property) =>
Object.groupBy(items, item => item[property])
);

const source = await readFile(TEMPLATE_PATH, 'utf8');
const render = Handlebars.compile(source, { noEscape: true });

return render({
changes,
version,
date,
...values,
});
}

const date = new Date().toISOString().slice(0, 10);
const version = await getLatestVersion();
const changesets = await getChangesets(version);
const changes = changesets.map(parseChangeset);
const post = await renderReleaseNotes(version, changes, date);
writeFile(
new URL(
import.meta.resolve(`../../../pages/blog/${date}-webpack-${version}.md`)
),
post
);
Loading
Loading