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
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ jobs:
shell: bash
run: |
set -euo pipefail
node scripts/update-versions.mjs "$TAG"
node scripts/release/update-versions.mjs "$TAG"
node scripts/release/create-post.mjs "$TAG"
node --run format

- name: Open pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
Expand Down
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
10 changes: 2 additions & 8 deletions scripts/markdown/governance.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +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 { 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 @@ -50,7 +44,7 @@ await mkdir(outputDir, { recursive: true });
const results = await Promise.all(
Object.entries(FILE_MAP).map(async ([source, { output, label }]) => {
const url = `https://raw.githubusercontent.com/webpack/governance/HEAD/${source}`;
const res = await fetchWithRetry(url, { headers: BASE_HEADERS });
const res = await fetchWithAuth(url);

if (!res.ok) {
console.error(`Failed: ${source} -> ${res.status} ${res.statusText}`);
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
82 changes: 82 additions & 0 deletions scripts/release/create-post.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 getRemovedChangesets = async version => {
const { files = [] } = await fetchJSON(`${API_BASE}/commits/${version}`);

return files.filter(
file =>
file.status === 'removed' &&
file.filename.startsWith('.changeset/') &&
file.patch
);
};
Comment on lines +26 to +35

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.

Hmm, I'd rather open it about a week before the release, or whenever Alex says we're getting close, so we have time to make any necessary changes

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I can do that, but the idea behind automating is that you need to make minimal changes to the document

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.

yep, although I guess I could get used to writing the blog post after the release and making any necessary changes afterward

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Well, if it's easier to run before, I'm happy to make that change, you are the author, just keep in mind I don't think there's a way for us to automate "1 week before" without a manual trigger.

I can do:

  1. On manual trigger w/ version, check the tag for changesets, fall back to main
  2. If no blog post created before release triggered, run automatically?

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.

On manual trigger w/ version, check the tag for changesets, fall back to main
If no blog post created before release triggered, run automatically?

Yeah, I think that would be the right workflow.


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

const parseChangeset = file => {
const { data, content } = matter(stripPatchMarkers(file.patch));
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 getRemovedChangesets(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
);
38 changes: 38 additions & 0 deletions scripts/release/template.md.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
date: '{{date}}'
category: release
title: 'webpack {{version}}'
layout: blog-post
author: '{{author}}'
---

# webpack {{version}}

<!-- Hey Releaser! Add your super awesome description here. -->

{{#with (filter changes "notable" true) as |notableChanges|}}
{{#if notableChanges.length}}
## Notable Changes

{{#each notableChanges}}
### _({{semver}})_ {{title}}

{{description}}

{{/each}}
{{/if}}
{{/with}}
{{#with (filter changes "notable" false) as |otherChanges|}}
{{#if otherChanges.length}}
## Other Changes

{{#each (groupBy otherChanges "semver")}}
### {{@key}}

{{#each this}}
- {{title}}
{{/each}}

{{/each}}
{{/if}}
{{/with}}
File renamed without changes.
24 changes: 20 additions & 4 deletions scripts/utils/fetch.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// fetch() wrapper that retries flakey responses like the GitHub and Open Collective
// occasionally 503 midbuild,so one blip doesn't fail the whole deploy.
import { setTimeout as sleep } from 'node:timers/promises';

const { GH_TOKEN } = process.env;
const RETRYABLE = new Set([429, 502, 503, 504]);

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// Use the server's Retry After when it sends one, otherwise back off.
const delayFor = (attempt, baseDelay, response) => {
const retryAfter = Number(response?.headers.get('retry-after'));
Expand Down Expand Up @@ -40,3 +38,21 @@ export const fetchWithRetry = async (
await sleep(delayFor(attempt, baseDelay, response));
}
};

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

export const fetchWithAuth = (url, fetchOptions = {}, retryOptions) =>
fetchWithRetry(
url,
{
...fetchOptions,
headers: {
...fetchOptions?.headers,
githubHeaders,
},
},
retryOptions
);
Loading