/* eslint-disable @typescript-eslint/no-unused-vars */ /* global require, process, console */ const fs = require("fs"); const path = require("path"); const util = require("util"); const childProcess = require("child_process"); const hosts = ["excel", "onenote", "outlook", "powerpoint", "project", "word"]; if (process.argv.length <= 2) { const hostList = hosts.map((host) => `'${host}'`).join(", "); console.log("SYNTAX: convertToSingleHost.js "); console.log(); console.log(` host (required): Specifies which Office app will host the add-in: ${hostList}`); console.log(` manifestType: Specify the type of manifest to use: 'xml' or 'json'. Defaults to 'xml'`); console.log(` projectName: The name of the project (use quotes when there are spaces in the name). Defaults to 'My Office Add-in'`); console.log(` appId: The id of the project or 'random' to generate one. Defaults to 'random'`); console.log(); process.exit(1); } const host = process.argv[2]; const manifestType = process.argv[3]; const projectName = process.argv[4]; let appId = process.argv[5]; const testPackages = [ "@types/mocha", "@types/node", "mocha", "office-addin-mock", "office-addin-test-helpers", "office-addin-test-server", "ts-node", ]; const readFileAsync = util.promisify(fs.readFile); const unlinkFileAsync = util.promisify(fs.unlink); const writeFileAsync = util.promisify(fs.writeFile); async function modifyProjectForSingleHost(host) { if (!host) { throw new Error("The host was not provided."); } if (!hosts.includes(host)) { throw new Error(`'${host}' is not a supported host.`); } await convertProjectToSingleHost(host); await updatePackageJsonForSingleHost(host); await updateLaunchJsonFile(host); } async function convertProjectToSingleHost(host) { // Copy host-specific manifest over manifest.xml const manifestContent = await readFileAsync(`./manifest.${host}.xml`, "utf8"); await writeFileAsync(`./manifest.xml`, manifestContent); // Copy host-specific office-document.ts over src/office-document.ts const srcContent = await readFileAsync(`./src/taskpane/${host}.ts`, "utf8"); await writeFileAsync(`./src/taskpane/taskpane.ts`, srcContent); // Delete all host-specific files hosts.forEach(async function (host) { await unlinkFileAsync(`./manifest.${host}.xml`); await unlinkFileAsync(`./src/taskpane/${host}.ts`); }); // Delete test folder deleteFolder(path.resolve(`./test`)); // Delete the .github folder deleteFolder(path.resolve(`./.github`)); // Delete CI/CD pipeline files deleteFolder(path.resolve(`./.azure-devops`)); // Delete repo support files await deleteSupportFiles(); } async function updatePackageJsonForSingleHost(host) { // Update package.json to reflect selected host const packageJson = `./package.json`; const data = await readFileAsync(packageJson, "utf8"); let content = JSON.parse(data); // Update 'config' section in package.json to use selected host content.config["app_to_debug"] = host; // Remove 'engines' section delete content.engines; // Remove scripts that are unrelated to the selected host Object.keys(content.scripts).forEach(function (key) { if (key === "convert-to-single-host") { delete content.scripts[key]; } }); // Remove test-related scripts Object.keys(content.scripts).forEach(function (key) { if (key.includes("test")) { delete content.scripts[key]; } }); // Remove test-related packages Object.keys(content.devDependencies).forEach(function (key) { if (testPackages.includes(key)) { delete content.devDependencies[key]; } }); // Write updated JSON to file await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); } async function updateLaunchJsonFile(host) { // Remove unneeded configuration from launch.json const launchJson = `.vscode/launch.json`; const launchJsonContent = await readFileAsync(launchJson, "utf8"); let content = JSON.parse(launchJsonContent); content.configurations = content.configurations.filter(function (config) { return config.name.toLowerCase().startsWith(host); }); await writeFileAsync(launchJson, JSON.stringify(content, null, 2)); } function deleteFolder(folder) { try { if (fs.existsSync(folder)) { fs.readdirSync(folder).forEach(function (file) { const curPath = `${folder}/${file}`; if (fs.lstatSync(curPath).isDirectory()) { deleteFolder(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(folder); } } catch (err) { throw new Error(`Unable to delete folder "${folder}".\n${err}`); } } async function deleteSupportFiles() { await unlinkFileAsync("CONTRIBUTING.md"); await unlinkFileAsync("LICENSE"); await unlinkFileAsync("README.md"); await unlinkFileAsync("SECURITY.md"); await unlinkFileAsync("./convertToSingleHost.js"); await unlinkFileAsync(".npmrc"); await unlinkFileAsync("package-lock.json"); } async function deleteJSONManifestRelatedFiles() { await unlinkFileAsync("assets/color.png"); await unlinkFileAsync("assets/outline.png"); } async function deleteXMLManifestRelatedFiles() { await unlinkFileAsync("manifest.xml"); } async function updatePackageJsonForXMLManifest() { const packageJson = `./package.json`; const data = await readFileAsync(packageJson, "utf8"); let content = JSON.parse(data); // Write updated JSON to file await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); } async function updatePackageJsonForJSONManifest() { const packageJson = `./package.json`; const data = await readFileAsync(packageJson, "utf8"); let content = JSON.parse(data); // Change manifest file name extension content.scripts.start = "office-addin-debugging start manifest.json"; content.scripts.stop = "office-addin-debugging stop manifest.json"; content.scripts.validate = "office-addin-manifest validate manifest.json"; // Write updated JSON to file await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); } async function updateTasksJsonFileForJSONManifest() { const tasksJson = `.vscode/tasks.json`; const data = await readFileAsync(tasksJson, "utf8"); let content = JSON.parse(data); content.tasks.forEach(function (task) { if (task.label.startsWith("Build")) { task.dependsOn = ["Install"]; } if (task.label === "Debug: Outlook Desktop") { task.script = "start"; task.dependsOn = ["Check OS", "Install"]; } }); const checkOSTask = { label: "Check OS", type: "shell", windows: { command: "echo 'Sideloading in Outlook on Windows is supported'", }, linux: { command: "echo 'Sideloading on Linux is not supported' && exit 1", }, osx: { command: "echo 'Sideloading in Outlook on Mac is not supported' && exit 1", }, presentation: { clear: true, panel: "dedicated", }, }; content.tasks.push(checkOSTask); await writeFileAsync(tasksJson, JSON.stringify(content, null, 2)); } async function updateWebpackConfigForJSONManifest() { const webPack = `webpack.config.js`; const webPackContent = await readFileAsync(webPack, "utf8"); const updatedContent = webPackContent.replace(".xml", ".json"); await writeFileAsync(webPack, updatedContent); } async function modifyProjectForJSONManifest() { await updatePackageJsonForJSONManifest(); await updateWebpackConfigForJSONManifest(); await updateTasksJsonFileForJSONManifest(); await deleteXMLManifestRelatedFiles(); } /** * Modify the project so that it only supports a single host. * @param host The host to support. */ modifyProjectForSingleHost(host).catch((err) => { console.error(`Error modifying for single host: ${err instanceof Error ? err.message : err}`); process.exitCode = 1; }); let manifestPath = "manifest.xml"; if (host !== "outlook" || manifestType !== "json") { // Remove things that are only relevant to JSON manifest deleteJSONManifestRelatedFiles(); updatePackageJsonForXMLManifest(); } else { manifestPath = "manifest.json"; modifyProjectForJSONManifest().catch((err) => { console.error(`Error modifying for JSON manifest: ${err instanceof Error ? err.message : err}`); process.exitCode = 1; }); } if (projectName) { if (!appId) { appId = "random"; } // Modify the manifest to include the name and id of the project const cmdLine = `npx office-addin-manifest modify ${manifestPath} -g ${appId} -d "${projectName}"`; childProcess.exec(cmdLine, (error, stdout) => { if (error) { Promise.reject(stdout); } else { Promise.resolve(); } }); }