#!/usr/bin/env node
/**
* What's this?? It will help you create release blog
* posts so you won't have to do the tedious work
* of stitching together data from changelog, shasums etc,
* but get a more or less complete release blog ready to go.
*
* Usage: $ node index.mjs [version]
*
* If the version argument is omitted, the latest version number
* will be picked from https://nodejs.org/dist/index.json.
*
* It'll create a file with the blog post content
* into ../../pages/en/blog/release/vX.md ready for you to commit
* or possibly edit by hand before committing.
*
* Happy releasing!
*/
'use strict';
import { existsSync, readFileSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import handlebars from 'handlebars';
import { format } from 'prettier';
import { downloadsTable } from './downloadsTable.mjs';
import prettierConfig from '../../.prettierrc.json' assert { type: 'json' };
import { getRelativePath } from '../../next.helpers.mjs';
const URLS = {
NODE_DIST_JSON: 'https://nodejs.org/dist/index.json',
GITHUB_PROFILE: author => `https://api.github.com/users/${author}`,
NODE_CHANGELOG_MD: releaseLine =>
`https://raw.githubusercontent.com/nodejs/node/main/doc/changelogs/CHANGELOG_V${releaseLine}.md`,
NODE_SHASUM: version =>
`https://nodejs.org/dist/v${version}/SHASUMS256.txt.asc`,
};
const ERRORS = {
NO_VERSION_PROVIDED: new Error('No version provided'),
RELEASE_EXISTS: version =>
new Error(`Release post for ${version} already exists!`),
NO_AUTHOR_FOUND: version =>
new Error(`Couldn't find @author of ${version} release :(`),
NO_VERSION_POLICY: version =>
new Error(`Could not find version policy of ${version} in its changelog`),
NO_CHANGELOG_FOUND: version =>
new Error(`Couldn't find matching changelog for ${version}`),
INVALID_STATUS_CODE: (url, status) =>
new Error(`Invalid status (!= 200) while retrieving ${url}: ${status}`),
FAILED_FILE_FORMATTING: reason =>
new Error(`Failed to format Release post: Reason: ${reason}`),
FAILED_FILE_CREATION: reason =>
new Error(`Failed to write Release post: Reason: ${reason}`),
};
const ARGS = {
CURRENT_PATH: process.argv[1],
SPECIFIC_VERSION: process.argv[2] && process.argv[2].replace('--force', ''),
SHOULD_FORCE: (process.argv[3] || process.argv[2]) === '--force',
};
// this allows us to get the current module working directory
const __dirname = getRelativePath(import.meta.url);
const request = options => {
return fetch(options.url, options).then(resp => {
if (resp.status !== 200) {
throw ERRORS.INVALID_STATUS_CODE(options.url, resp.status);
}
return options.json ? resp.json() : resp.text();
});
};
const explicitVersion = version =>
new Promise((resolve, reject) =>
version && version.length > 0
? resolve(version)
: reject(ERRORS.NO_VERSION_PROVIDED)
);
const findLatestVersion = () =>
request({ url: URLS.NODE_DIST_JSON, json: true })
.then(versions => versions.length && versions[0])
.then(({ version }) => version.substr(1));
const fetchDocs = version => {
const blogPostPieces = [
fetchChangelogBody(version),
fetchAuthor(version),
fetchVersionPolicy(version),
fetchShasums(version),
verifyDownloads(version),
];
return Promise.all(blogPostPieces).then(
([changelog, author, versionPolicy, shasums, files]) => ({
version,
changelog,
author,
versionPolicy,
shasums,
files,
})
);
};
const fetchAuthor = version => {
return fetchChangelog(version)
.then(section => findAuthorLogin(version, section))
.then(author => request({ url: URLS.GITHUB_PROFILE(author), json: true }))
.then(githubRes => githubRes.name);
};
const fetchChangelog = version => {
const parts = version.split('.');
const releaseLine = parts[0] === '0' ? parts.slice(0, 2).join('') : parts[0];
return request({ url: URLS.NODE_CHANGELOG_MD(releaseLine) }).then(data => {
// matches a complete release section
const rxSection = new RegExp(
`\\n([\\s\\S]+?)(?:\\n