diff --git a/package-lock.json b/package-lock.json index de99378e69ff3..8077df7248794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "remark-gfm": "~3.0.0", "semver": "~7.5.4", "sharp": "0.32.6", - "shiki": "^0.14.3", + "shiki": "^0.14.5", "tailwindcss": "^3.3.3", "turbo": "^1.10.14", "typescript": "~5.2.2", @@ -23877,9 +23877,9 @@ } }, "node_modules/shiki": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz", - "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", "dependencies": { "ansi-sequence-parser": "^1.1.0", "jsonc-parser": "^3.2.0", diff --git a/package.json b/package.json index 25d3e1941e21f..2dd70005a85c1 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "remark-gfm": "~3.0.0", "semver": "~7.5.4", "sharp": "0.32.6", - "shiki": "^0.14.3", + "shiki": "^0.14.5", "tailwindcss": "^3.3.3", "turbo": "^1.10.14", "typescript": "~5.2.2", diff --git a/pages/en/blog/release/v15.1.0.md b/pages/en/blog/release/v15.1.0.md index 92c60053022ef..d96407a8daebd 100644 --- a/pages/en/blog/release/v15.1.0.md +++ b/pages/en/blog/release/v15.1.0.md @@ -104,7 +104,7 @@ When generating snapshots, garbage collection may be triggered and bring the hea Generating V8 snapshots takes time and memory (both memory managed by the V8 heap and native memory outside the V8 heap). The bigger the heap is, the more resources it needs. Node.js will adjust the V8 heap to accommondate the additional V8 heap memory overhead, and try its best to avoid using up all the memory avialable to the process. -```shell-session +```console $ node --max-old-space-size=100 --heapsnapshot-near-heap-limit=3 index.js Wrote snapshot to Heap.20200430.100036.49580.0.001.heapsnapshot Wrote snapshot to Heap.20200430.100037.49580.0.002.heapsnapshot diff --git a/pages/en/blog/release/v18.16.0.md b/pages/en/blog/release/v18.16.0.md index 1fc74be3efc2d..692bc615fc52a 100644 --- a/pages/en/blog/release/v18.16.0.md +++ b/pages/en/blog/release/v18.16.0.md @@ -12,7 +12,7 @@ author: Danielle Adams Compile a JavaScript file into a single executable application: -```shell-session +```console $ echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js $ cp $(command -v node) hello diff --git a/scripts/release-post/downloadsTable.mjs b/scripts/release-post/downloadsTable.mjs index 412ae8cfed97b..5b64290338e8d 100644 --- a/scripts/release-post/downloadsTable.mjs +++ b/scripts/release-post/downloadsTable.mjs @@ -2,7 +2,7 @@ import semVer from 'semver'; -const allDownloads = [ +const downloadOptions = [ { title: 'Windows 32-bit Installer', templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%-x86.msi', @@ -41,11 +41,6 @@ const allDownloads = [ templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x64.tar.gz', }, - { - title: 'Linux 32-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x86.tar.xz', - }, { title: 'Linux 64-bit Binary', templateUrl: @@ -56,11 +51,6 @@ const allDownloads = [ templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%-linux-ppc64le.tar.xz', }, - { - title: 'Linux PPC BE 64-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-linux-ppc64.tar.xz', - }, { title: 'Linux s390x 64-bit Binary', templateUrl: @@ -71,21 +61,6 @@ const allDownloads = [ templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%-aix-ppc64.tar.gz', }, - { - title: 'SmartOS 32-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x86.tar.xz', - }, - { - title: 'SmartOS 64-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x64.tar.xz', - }, - { - title: 'ARMv6 32-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-linux-armv6l.tar.xz', - }, { title: 'ARMv7 32-bit Binary', templateUrl: @@ -102,98 +77,13 @@ const allDownloads = [ }, ]; -// v0.x of Node.js -const legacyDownloads = [ - { - title: 'Windows 32-bit Installer', - templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%-x86.msi', - }, - { - title: 'Windows 64-bit Installer', - templateUrl: - 'https://nodejs.org/dist/v%version%/x64/node-v%version%-x64.msi', - }, - { - title: 'Windows 32-bit Binary', - templateUrl: 'https://nodejs.org/dist/v%version%/node.exe', - }, - { - title: 'Windows 64-bit Binary', - templateUrl: 'https://nodejs.org/dist/v%version%/x64/node.exe', - }, - { - title: 'macOS Universal Installer', - templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%.pkg', - }, - { - title: 'macOS 64-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x64.tar.gz', - }, - { - title: 'macOS 32-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-darwin-x86.tar.gz', - }, - { - title: 'Linux 32-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x86.tar.gz', - }, - { - title: 'Linux 64-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-linux-x64.tar.gz', - }, - { - title: 'SmartOS 32-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x86.tar.gz', - }, - { - title: 'SmartOS 64-bit Binary', - templateUrl: - 'https://nodejs.org/dist/v%version%/node-v%version%-sunos-x64.tar.gz', - }, - { - title: 'Source Code', - templateUrl: 'https://nodejs.org/dist/v%version%/node-v%version%.tar.gz', - }, -]; - const resolveUrl = (item, version) => { const url = item.templateUrl.replace(/%version%/g, version); return Object.assign({ url }, item); }; const resolveDownloads = version => { - let downloads = allDownloads; - - if (semVer.satisfies(version, '< 1.0.0')) { - return legacyDownloads; - } - - if (semVer.satisfies(version, '>= 8.0.0')) { - downloads = downloads.filter( - ver => ver.title !== 'Linux PPC BE 64-bit Binary' - ); - } - - if (semVer.satisfies(version, '>= 10.0.0')) { - downloads = downloads.filter( - ver => - ver.title !== 'Linux 32-bit Binary' && - ver.title !== 'SmartOS 32-bit Binary' - ); - } - - if (semVer.satisfies(version, '>= 12.0.0')) { - downloads = downloads.filter(ver => ver.title !== 'ARMv6 32-bit Binary'); - } - - if (semVer.satisfies(version, '>= 14.0.0')) { - downloads = downloads.filter(ver => ver.title !== 'SmartOS 64-bit Binary'); - } + let downloads = downloadOptions; if (semVer.satisfies(version, '< 16.0.0')) { downloads = downloads.filter( @@ -212,7 +102,5 @@ const resolveDownloads = version => { return downloads; }; -const downloadsTable = version => +export const downloadsTable = version => resolveDownloads(version).map(item => resolveUrl(item, version)); - -export default downloadsTable; diff --git a/scripts/release-post/index.mjs b/scripts/release-post/index.mjs index ea5b717975afe..2b6fb831e8edd 100644 --- a/scripts/release-post/index.mjs +++ b/scripts/release-post/index.mjs @@ -20,75 +20,98 @@ 'use strict'; -import fs from 'node:fs'; -import path from 'node:path'; -import url from 'node:url'; +import { existsSync, readFileSync } from 'node:fs'; +import { writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; import handlebars from 'handlebars'; -import downloadsTable from './downloadsTable.mjs'; +import { downloadsTable } from './downloadsTable.mjs'; 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_BODY: version => + new Error(`Could not find changelog body of ${version} release`), + 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_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 sendRequest = opts => { - const options = { - headers: { 'User-Agent': 'nodejs.org release blog post script' }, - ...opts, - }; - +const request = options => { return fetch(options.url, options).then(resp => { if (resp.status !== 200) { - throw new Error( - `Invalid status code (!= 200) while retrieving ${options.url}: ${resp.status}` - ); + throw ERRORS.INVALID_STATUS_CODE(options.url, resp.status); } return options.json ? resp.json() : resp.text(); }); }; -const explicitVersion = version => { - return version - ? Promise.resolve(version) - : Promise.reject(new Error('Invalid "version" argument')); -}; +const explicitVersion = version => + new Promise((resolve, reject) => + version && version.length > 0 + ? resolve(version) + : reject(ERRORS.NO_VERSION_PROVIDED) + ); -const findLatestVersion = () => { - return sendRequest({ - url: 'https://nodejs.org/dist/index.json', - json: true, - }).then(versions => versions[0].version.substr(1)); -}; +const findLatestVersion = () => + request({ url: URLS.NODE_DIST_JSON, json: true }) + .then(versions => versions.length && versions[0]) + .then(({ version }) => version.substr(1)); const fetchDocs = version => { - return Promise.all([ + const blogPostPieces = [ fetchChangelogBody(version), fetchAuthor(version), fetchVersionPolicy(version), fetchShasums(version), verifyDownloads(version), - ]).then(results => { - const [changelog, author, versionPolicy, shasums, files] = results; + ]; - return { + 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 => - sendRequest({ url: `https://api.github.com/users/${author}`, json: true }) - ) + .then(author => request({ url: URLS.GITHUB_PROFILE(author), json: true })) .then(githubRes => githubRes.name); }; @@ -96,9 +119,7 @@ const fetchChangelog = version => { const parts = version.split('.'); const releaseLine = parts[0] === '0' ? parts.slice(0, 2).join('') : parts[0]; - return sendRequest({ - url: `https://raw.githubusercontent.com/nodejs/node/main/doc/changelogs/CHANGELOG_V${releaseLine}.md`, - }).then(data => { + return request({ url: URLS.NODE_CHANGELOG_MD(releaseLine) }).then(data => { // matches a complete release section const rxSection = new RegExp( `\\n([\\s\\S]+?)(?:\\n + str.replace(/^([ ]{0,4})(\* )/gm, '$1- '); - return matches - ? matches[1].trim().replace(rxSectionConsole, '```shell-session') - : Promise.reject( - new Error(`Could not find changelog body of ${version} release`) - ); + return new Promise((resolve, reject) => + matches.length && matches[1] + ? resolve(replaceAsteriskLists(matches[1].trim())) + : reject(ERRORS.NO_CHANGELOG_BODY(version)) + ); }); }; @@ -145,28 +164,23 @@ const fetchVersionPolicy = version => { // ## 2015-12-04, Version 0.12.9 (LTS), @rvagg const rxPolicy = /^## ?\d{4}-\d{2}-\d{2}, Version [^(].*\(([^)]+)\)/; const matches = rxPolicy.exec(section); - return matches - ? matches[1] - : Promise.reject( - new Error( - `Could not find version policy of ${version} in its changelog` - ) - ); - }); -}; -const fetchShasums = version => { - return sendRequest({ - url: `https://nodejs.org/dist/v${version}/SHASUMS256.txt.asc`, - }).then(null, () => '[INSERT SHASUMS HERE]'); + return new Promise((resolve, reject) => + matches.length && matches[1] + ? resolve(matches[1]) + : reject(ERRORS.NO_VERSION_POLICY(version)) + ); + }); }; -const verifyDownloads = version => { - const allDownloads = downloadsTable(version); - const reqs = allDownloads.map(urlOrComingSoon); +const fetchShasums = version => + request({ url: URLS.NODE_SHASUM(version) }).then( + result => result.trim(), + () => '[INSERT SHASUMS HERE]' + ); - return Promise.all(reqs); -}; +const verifyDownloads = version => + Promise.all(downloadsTable(version).map(urlOrComingSoon)); const findAuthorLogin = (version, section) => { // looking for the @author part of the release header, eg: @@ -176,82 +190,57 @@ const findAuthorLogin = (version, section) => { const rxReleaseAuthor = /^## .*? \([^)]+\)[,.] @(\S+)/; const matches = rxReleaseAuthor.exec(section); - return matches - ? matches[1] - : Promise.reject( - new Error(`Couldn't find @author of ${version} release :(`) - ); + return new Promise((resolve, reject) => + matches.length && matches[1] + ? resolve(matches[1]) + : reject(ERRORS.RELEASE_EXISTS(version)) + ); }; const urlOrComingSoon = binary => { - const url = binary.url.replace('nodejs.org', 'direct.nodejs.org'); - - return sendRequest({ url, method: 'HEAD' }).then( + return request({ url: binary.url, method: 'HEAD' }).then( () => `${binary.title}: ${binary.url}`, - () => { - console.log(`\x1B[32m "${binary.title}" is Coming soon...\x1B[39m`); - return `${binary.title}: *Coming soon*`; - } + () => `${binary.title}: *Coming soon*` ); }; const renderPost = results => { - const templateStr = fs - .readFileSync(path.resolve(__dirname, 'template.hbs')) - .toString('utf8'); - - const template = handlebars.compile(templateStr, { noEscape: true }); - - const view = Object.assign( - { - date: new Date().toISOString(), - versionSlug: slugify(results.version), - }, - results + const blogTemplateSource = readFileSync( + resolve(__dirname, 'template.hbs'), + 'utf8' ); - return Object.assign( - { - content: template(view), - }, - results - ); + const template = handlebars.compile(blogTemplateSource, { noEscape: true }); + + const templateParameters = { + date: new Date().toISOString(), + versionSlug: slugify(results.version), + ...results, + }; + + return { content: template(templateParameters), ...results }; }; const writeToFile = results => { - const filepath = path.resolve( + const blogPostPath = resolve( __dirname, - '..', - '..', - 'pages', - 'en', - 'blog', - 'release', + '../../pages/en/blog/release', `v${results.version}.md` ); return new Promise((resolve, reject) => { - if (fs.existsSync(filepath) && process.argv[3] !== '--force') { - return reject( - new Error(`Release post for ${results.version} already exists!`) - ); + if (existsSync(blogPostPath) && !ARGS.SHOULD_FORCE) { + reject(ERRORS.RELEASE_EXISTS(results.version)); + return; } - try { - fs.writeFileSync(filepath, results.content); - } catch (error) { - return reject( - new Error(`Failed to write Release post: Reason: ${error.message}`) - ); - } - - resolve(filepath); + writeFile(blogPostPath, results.content) + .then(() => resolve(blogPostPath)) + .catch(error => reject(ERRORS.FAILED_FILE_CREATION(error.message))); }); }; -const slugify = str => { - return str.replace(/\./g, '-'); -}; +const slugify = str => str.replace(/\./g, '-'); export { explicitVersion, @@ -267,22 +256,15 @@ export { // This allows us to verify that the script is being run directly from node.js/cli if (import.meta.url.startsWith('file:')) { - const modulePath = url.fileURLToPath(import.meta.url); - - if (process.argv[1] === modulePath) { - explicitVersion(process.argv[2]) + if (ARGS.CURRENT_PATH === `${__dirname}index.mjs`) { + explicitVersion(ARGS.SPECIFIC_VERSION) .then(null, findLatestVersion) .then(fetchDocs) .then(renderPost) .then(writeToFile) .then( - filepath => { - console.log('Release post created:', filepath); - }, - err => { - console.error('Some error occurred here!', err.stack); - process.exit(1); - } + filepath => console.log('Release post created:', filepath), + error => console.error('Some error occurred here!', error.stack) ); } } diff --git a/scripts/release-post/template.hbs b/scripts/release-post/template.hbs index 39b38db442fd7..29d075e8b9d90 100644 --- a/scripts/release-post/template.hbs +++ b/scripts/release-post/template.hbs @@ -16,6 +16,6 @@ Documentation: https://nodejs.org/docs/v{{version}}/api/ ### SHASUMS -``` +```text {{shasums}} ``` diff --git a/shiki.config.mjs b/shiki.config.mjs index 36badfe1e0ee0..a41d29bdb7696 100644 --- a/shiki.config.mjs +++ b/shiki.config.mjs @@ -17,6 +17,8 @@ import cppLanguage from 'shiki/languages/cpp.tmLanguage.json' assert { type: 'js import javaScriptLanguage from 'shiki/languages/javascript.tmLanguage.json' assert { type: 'json' }; import jsonLanguage from 'shiki/languages/json.tmLanguage.json' assert { type: 'json' }; import jsxLanguage from 'shiki/languages/jsx.tmLanguage.json' assert { type: 'json' }; +import shellScriptLanguage from 'shiki/languages/shellscript.tmLanguage.json' assert { type: 'json' }; +import shellSessionLanguage from 'shiki/languages/shellsession.tmLanguage.json' assert { type: 'json' }; import typeScriptLanguage from 'shiki/languages/typescript.tmLanguage.json' assert { type: 'json' }; import xmlLanguage from 'shiki/languages/xml.tmLanguage.json' assert { type: 'json' }; import yamlLanguage from 'shiki/languages/yaml.tmLanguage.json' assert { type: 'json' }; @@ -36,8 +38,8 @@ export const SUPPORTED_LANGUAGES = [ { id: 'javascript', scopeName: 'source.js', - aliases: ['js'], grammar: javaScriptLanguage, + aliases: ['js'], }, { id: 'json', @@ -52,8 +54,8 @@ export const SUPPORTED_LANGUAGES = [ { id: 'typescript', scopeName: 'source.ts', - aliases: ['ts'], grammar: typeScriptLanguage, + aliases: ['ts'], }, { id: 'xml', @@ -64,5 +66,18 @@ export const SUPPORTED_LANGUAGES = [ id: 'yaml', scopeName: 'source.yaml', grammar: yamlLanguage, + aliases: ['yml'], + }, + { + id: 'shellscript', + scopeName: 'source.shell', + grammar: shellScriptLanguage, + aliases: ['bash', 'sh', 'shell', 'zsh'], + }, + { + id: 'shellsession', + scopeName: 'text.shell-session', + grammar: shellSessionLanguage, + aliases: ['console'], }, ];