diff --git a/components/git/security.js b/components/git/security.js index 47aac459..60ff3205 100644 --- a/components/git/security.js +++ b/components/git/security.js @@ -41,8 +41,8 @@ const securityOptions = { type: 'boolean' }, 'post-release': { - describe: 'Create the post-release announcement', - type: 'boolean' + describe: 'Create the post-release announcement to the given nodejs.org folder', + type: 'string' }, cleanup: { describe: 'cleanup the security release.', @@ -83,7 +83,7 @@ export function builder(yargs) { 'Request CVEs for a security release of Node.js based on' + ' the next-security-release/vulnerabilities.json' ).example( - 'git node security --post-release', + 'git node security --post-release="../nodejs.org/"', 'Create the post-release announcement on the Nodejs.org repo' ).example( 'git node security --cleanup', @@ -164,11 +164,12 @@ async function requestCVEs() { return hackerOneCve.requestCVEs(); } -async function createPostRelease() { +async function createPostRelease(argv) { + const nodejsOrgFolder = argv['post-release']; const logStream = process.stdout.isTTY ? process.stdout : process.stderr; const cli = new CLI(logStream); const blog = new SecurityBlog(cli); - return blog.createPostRelease(); + return blog.createPostRelease(nodejsOrgFolder); } async function startSecurityRelease() { diff --git a/docs/git-node.md b/docs/git-node.md index adf58320..f36be7c5 100644 --- a/docs/git-node.md +++ b/docs/git-node.md @@ -479,6 +479,15 @@ Example: git node security --pre-release="/path/to/nodejs.org" ``` +### `git node security --post-release` + +This command creates the post-release announcement for the security release. +Example: + +```sh + git node security --post-release="/path/to/nodejs.org" +``` + ### `git node security --add-report=report-id` This command adds a HackerOne report to the `vulnerabilities.json`. diff --git a/lib/security_blog.js b/lib/security_blog.js index 78256cc1..d2f51d4e 100644 --- a/lib/security_blog.js +++ b/lib/security_blog.js @@ -11,8 +11,6 @@ import { import auth from './auth.js'; import Request from './request.js'; -const kChanged = Symbol('changed'); - export default class SecurityBlog extends SecurityRelease { req; @@ -54,24 +52,23 @@ export default class SecurityBlog extends SecurityRelease { const file = path.resolve(process.cwd(), nodejsOrgFolder, pathToBlogPosts, fileNameExt); const site = path.resolve(process.cwd(), nodejsOrgFolder, pathToBannerJson); - const siteJson = JSON.parse(fs.readFileSync(site)); const endDate = new Date(data.annoucementDate); endDate.setDate(endDate.getDate() + 7); - const capitalizedMonth = month[0].toUpperCase() + month.slice(1); - siteJson.websiteBanners.index = { + + this.updateWebsiteBanner(site, { startDate: data.annoucementDate, endDate: endDate.toISOString(), - text: `${capitalizedMonth} Security Release is available`, + text: `New security releases to be made available ${data.releaseDate}`, link: `https://nodejs.org/en/blog/vulnerability/${fileName}`, type: 'warning' - }; + }); + fs.writeFileSync(file, preRelease); - fs.writeFileSync(site, JSON.stringify(siteJson, null, 2)); cli.ok(`Announcement file created and banner has been updated. Folder: ${nodejsOrgFolder}`); } - async createPostRelease() { + async createPostRelease(nodejsOrgFolder) { const { cli } = this; const credentials = await auth({ github: true, @@ -84,7 +81,7 @@ export default class SecurityBlog extends SecurityRelease { checkoutOnSecurityReleaseBranch(cli, this.repository); // read vulnerabilities JSON file - const content = this.readVulnerabilitiesJSON(cli); + const content = this.readVulnerabilitiesJSON(); if (!content.releaseDate) { cli.error('Release date is not set in vulnerabilities.json,' + ' run `git node security --update-date=YYYY/MM/DD` to set the release date.'); @@ -95,47 +92,54 @@ export default class SecurityBlog extends SecurityRelease { const releaseDate = new Date(content.releaseDate); const template = this.getSecurityPostReleaseTemplate(); const data = { - // TODO: read from pre-sec-release - annoucementDate: await this.getAnnouncementDate(cli), + annoucementDate: releaseDate.toISOString(), releaseDate: this.formatReleaseDate(releaseDate), affectedVersions: this.getAffectedVersions(content), vulnerabilities: this.getVulnerabilities(content), slug: this.getSlug(releaseDate), - author: await this.promptAuthor(cli), + author: 'The Node.js Project', dependencyUpdates: content.dependencies }; - const postReleaseContent = await this.buildPostRelease(template, data, content); - const pathPreRelease = await this.promptExistingPreRelease(cli); - // read the existing pre-release announcement - let preReleaseContent = fs.readFileSync(pathPreRelease, 'utf-8'); + const pathToBlogPosts = path.resolve(nodejsOrgFolder, 'apps/site/pages/en/blog/release'); + const pathToBannerJson = path.resolve(nodejsOrgFolder, 'apps/site/site.json'); + + const preReleasePath = path.resolve(pathToBlogPosts, data.slug + '.md'); + let preReleaseContent = this.findExistingPreRelease(preReleasePath); + if (!preReleaseContent) { + cli.error(`Existing pre-release not found! Path: ${preReleasePath} `); + process.exit(1); + } + + const postReleaseContent = await this.buildPostRelease(template, data, content); // cut the part before summary const preSummary = preReleaseContent.indexOf('# Summary'); if (preSummary !== -1) { preReleaseContent = preReleaseContent.substring(preSummary); } - const updatedContent = postReleaseContent + preReleaseContent; - fs.writeFileSync(pathPreRelease, updatedContent); - cli.ok(`Post-release announcement file updated at ${pathPreRelease}`); + const endDate = new Date(data.annoucementDate); + endDate.setDate(endDate.getDate() + 7); + const month = releaseDate.toLocaleString('en-US', { month: 'long' }); + const capitalizedMonth = month[0].toUpperCase() + month.slice(1); - // if the vulnerabilities.json has been changed, update the file - if (!content[kChanged]) return; - this.updateVulnerabilitiesJSON(content); - } + this.updateWebsiteBanner(pathToBannerJson, { + startDate: releaseDate, + endDate, + text: `${capitalizedMonth} Security Release is available` + }); - async promptExistingPreRelease(cli) { - const pathPreRelease = await cli.prompt( - 'Please provide the path of the existing pre-release announcement:', { - questionType: 'input', - defaultAnswer: '' - }); + fs.writeFileSync(preReleasePath, updatedContent); + cli.ok(`Announcement file and banner has been updated. Folder: ${nodejsOrgFolder}`); + } - if (!pathPreRelease || !fs.existsSync(path.resolve(pathPreRelease))) { - return this.promptExistingPreRelease(cli); + findExistingPreRelease(filepath) { + if (!fs.existsSync(filepath)) { + return null; } - return pathPreRelease; + + return fs.readFileSync(filepath, 'utf-8'); } promptAuthor(cli) { @@ -146,6 +150,20 @@ export default class SecurityBlog extends SecurityRelease { }); } + updateWebsiteBanner(siteJsonPath, content) { + const siteJson = JSON.parse(fs.readFileSync(siteJsonPath)); + + const currentValue = siteJson.websiteBanners.index; + siteJson.websiteBanners.index = { + startDate: content.startDate ?? currentValue.startDate, + endDate: content.endDate ?? currentValue.endDate, + text: content.text ?? currentValue.text, + link: content.link ?? currentValue.link, + type: content.type ?? currentValue.type + }; + fs.writeFileSync(siteJsonPath, JSON.stringify(siteJson, null, 2)); + } + formatReleaseDate(releaseDate) { const options = { weekday: 'long',