diff --git a/README.md b/README.md index 1b623e04..f6a9c1c4 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,6 @@ Options: -o, --output Specify the relative or absolute output directory -v, --version Specify the target version of Node.js, semver compliant (default: "v22.6.0") -c, --changelog Specify the path (file: or https://) to the CHANGELOG.md file (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md") - -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page", "legacy-json", "legacy-json-all") + -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page", "legacy-json", "legacy-json-all", "addon-verify") -h, --help display help for command ``` diff --git a/src/generators/addon-verify/constants.mjs b/src/generators/addon-verify/constants.mjs new file mode 100644 index 00000000..50d8d178 --- /dev/null +++ b/src/generators/addon-verify/constants.mjs @@ -0,0 +1 @@ +export const EXTRACT_CODE_FILENAME_COMMENT = /^\/\/\s+(.*\.(?:cc|h|js))[\r\n]/; diff --git a/src/generators/addon-verify/index.mjs b/src/generators/addon-verify/index.mjs new file mode 100644 index 00000000..4c9d3712 --- /dev/null +++ b/src/generators/addon-verify/index.mjs @@ -0,0 +1,106 @@ +'use strict'; + +import { mkdir, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +import { visit } from 'unist-util-visit'; + +import { updateFilesForBuild } from './utils/updateFilesForBuild.mjs'; +import { EXTRACT_CODE_FILENAME_COMMENT } from './constants.mjs'; + +/** + * Normalizes a section name. + * + * @param {string} sectionName Section name + * @returns {string} + */ +export function normalizeSectionName(sectionName) { + return sectionName.toLowerCase().replace(/\s/g, '_').replace(/\W/g, ''); +} + +/** + * This generator generates a file list from code blocks extracted from + * `doc/api/addons.md` to facilitate C++ compilation and JavaScript runtime + * validations. + * + * @typedef {Array} Input + * + * @type {import('../types.d.ts').GeneratorMetadata} + */ +export default { + name: 'addon-verify', + + version: '1.0.0', + + description: + 'Generates a file list from code blocks extracted from `doc/api/addons.md` to facilitate C++ compilation and JavaScript runtime validations', + + dependsOn: 'ast', + + /** + * Generates a file list from code blocks. + * + * @param {Input} input + * @param {Partial} options + */ + async generate(input, { output }) { + const sectionsCodeBlocks = input.reduce((addons, node) => { + const sectionName = node.heading.data.name; + + const content = node.content; + + visit(content, childNode => { + if (childNode.type === 'code') { + const filename = childNode.value.match(EXTRACT_CODE_FILENAME_COMMENT); + + if (filename === null) { + return; + } + + if (!addons[sectionName]) { + addons[sectionName] = []; + } + + addons[sectionName].push({ + name: filename[1], + content: childNode.value, + }); + } + }); + + return addons; + }, {}); + + const files = await Promise.all( + Object.entries(sectionsCodeBlocks) + .filter(([, files]) => { + // Must have a .cc and a .js to be a valid test. + return ( + files.some(file => file.name.endsWith('.cc')) && + files.some(file => file.name.endsWith('.js')) + ); + }) + .flatMap(async ([sectionName, files], index) => { + const newFiles = updateFilesForBuild(files); + + if (output) { + const normalizedSectionName = normalizeSectionName(sectionName); + + const identifier = String(index + 1).padStart(2, '0'); + + const folder = `${identifier}_${normalizedSectionName}`; + + await mkdir(join(output, folder), { recursive: true }); + + newFiles.forEach(async ({ name, content }) => { + await writeFile(join(output, folder, name), content); + }); + } + + return newFiles; + }) + ); + + return files; + }, +}; diff --git a/src/generators/addon-verify/utils/updateFilesForBuild.mjs b/src/generators/addon-verify/utils/updateFilesForBuild.mjs new file mode 100644 index 00000000..3ab57ac8 --- /dev/null +++ b/src/generators/addon-verify/utils/updateFilesForBuild.mjs @@ -0,0 +1,41 @@ +/** + * Updates JavaScript files with correct require paths for the build tree + * and generates a `binding.gyp` file to compile C++ code. + * + * @param {{name: string, content: string}[]} files An array of file objects + * @returns {{name: string, content: string}[]} + */ +export const updateFilesForBuild = files => { + const updatedFiles = files.map(({ name, content }) => { + if (name === 'test.js') { + content = `'use strict'; +const common = require('../../common'); +${content.replace( + "'./build/Release/addon'", + + '`./build/${common.buildType}/addon`' +)} + `; + } + + return { + name: name, + content: content, + }; + }); + + updatedFiles.push({ + name: 'binding.gyp', + content: JSON.stringify({ + targets: [ + { + target_name: 'addon', + sources: files.map(({ name }) => name), + includes: ['../common.gypi'], + }, + ], + }), + }); + + return updatedFiles; +}; diff --git a/src/generators/index.mjs b/src/generators/index.mjs index f787a2b2..45a0f54e 100644 --- a/src/generators/index.mjs +++ b/src/generators/index.mjs @@ -6,6 +6,7 @@ import legacyHtmlAll from './legacy-html-all/index.mjs'; import manPage from './man-page/index.mjs'; import legacyJson from './legacy-json/index.mjs'; import legacyJsonAll from './legacy-json-all/index.mjs'; +import addonVerify from './addon-verify/index.mjs'; export default { 'json-simple': jsonSimple, @@ -14,4 +15,5 @@ export default { 'man-page': manPage, 'legacy-json': legacyJson, 'legacy-json-all': legacyJsonAll, + 'addon-verify': addonVerify, };