From 4f774247d9f00e6d1279651597b09c38f8bb1052 Mon Sep 17 00:00:00 2001 From: ayush-meghwani-28 Date: Tue, 7 Jul 2026 11:43:30 +0530 Subject: [PATCH] Guard convertToSingleHost against deeply nested JSON config files convertToSingleHost.js rewrites config files (launch.json / package.json / tasks.json) by reading, modifying, and writing them back with JSON.stringify. Node's JSON.stringify is recursive and throws 'RangeError: Maximum call stack size exceeded' on extremely deeply nested input, producing an opaque failure. Wrap the write-back serialization in a small helper that catches that RangeError and rethrows a clear message naming the file. Normal template content is unaffected. --- convertToSingleHost.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/convertToSingleHost.js b/convertToSingleHost.js index 23a3f44..af411d1 100644 --- a/convertToSingleHost.js +++ b/convertToSingleHost.js @@ -38,6 +38,20 @@ const readFileAsync = util.promisify(fs.readFile); const unlinkFileAsync = util.promisify(fs.unlink); const writeFileAsync = util.promisify(fs.writeFile); +// JSON.stringify is recursive and throws a "Maximum call stack size exceeded" RangeError on +// extremely deeply nested input. Catch that here and rethrow a clear, actionable message that +// names the file being written. +function stringifyJson(content, fileName) { + try { + return JSON.stringify(content, null, 2); + } catch (err) { + if (err instanceof RangeError) { + throw new Error(`Unable to update "${fileName}": its JSON content is too deeply nested.`); + } + throw err; + } +} + async function modifyProjectForSingleHost(host) { if (!host) { throw new Error("The host was not provided."); @@ -112,7 +126,7 @@ async function updatePackageJsonForSingleHost(host) { }); // Write updated JSON to file - await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); + await writeFileAsync(packageJson, stringifyJson(content, packageJson)); } async function updateLaunchJsonFile(host) { @@ -123,7 +137,7 @@ async function updateLaunchJsonFile(host) { content.configurations = content.configurations.filter(function (config) { return config.name.startsWith(getHostName(host)); }); - await writeFileAsync(launchJson, JSON.stringify(content, null, 2)); + await writeFileAsync(launchJson, stringifyJson(content, launchJson)); } function getHostName(host) { @@ -191,7 +205,7 @@ async function updatePackageJsonForXMLManifest() { let content = JSON.parse(data); // Write updated JSON to file - await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); + await writeFileAsync(packageJson, stringifyJson(content, packageJson)); } async function updatePackageJsonForJSONManifest() { @@ -205,7 +219,7 @@ async function updatePackageJsonForJSONManifest() { content.scripts.validate = "office-addin-manifest validate manifest.json"; // Write updated JSON to file - await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); + await writeFileAsync(packageJson, stringifyJson(content, packageJson)); } async function updateTasksJsonFileForJSONManifest() { @@ -242,7 +256,7 @@ async function updateTasksJsonFileForJSONManifest() { }; content.tasks.push(checkOSTask); - await writeFileAsync(tasksJson, JSON.stringify(content, null, 2)); + await writeFileAsync(tasksJson, stringifyJson(content, tasksJson)); } async function updateWebpackConfigForJSONManifest() {