// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import AdmZip from "adm-zip"; import fs from "fs"; import fsExtra from "fs-extra"; import path from "path"; import util from "util"; import { exec } from "child_process"; import { convert } from "office-addin-manifest-converter"; import { ExpectedError } from "office-addin-usage-data"; /* global console process */ const execAsync = util.promisify(exec); const skipBackup: string[] = ["node_modules"]; export async function convertProject( manifestPath: string = "./manifest.xml", backupPath: string = "./backup.zip", projectDir: string = "", devPreview: boolean = false ) { if (manifestPath.endsWith(".json")) { throw new ExpectedError(`The convert command only works on xml manifest based projects`); } if (!fs.existsSync(manifestPath)) { throw new ExpectedError(`The manifest file '${manifestPath}' does not exist`); } const outputPath: string = path.dirname(manifestPath); const currentDir: string = process.cwd(); await backupProject(backupPath); try { // assume project dir is the same as manifest dir if not specified projectDir = projectDir === "" ? outputPath : projectDir; process.chdir(projectDir); if (!devPreview) { await convert(manifestPath, outputPath, false /* imageDownload */); } else { // override the schema used in the json manifest to use the devPreview schema await convert( manifestPath, outputPath, false /* imageDownload */, "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json", "devPreview" ); } await updatePackages(); await updateManifestXmlReferences(); fs.unlinkSync(manifestPath); } catch (err: any) { console.log(`Error in conversion. Restoring project initial state.`); await restoreBackup(backupPath); throw err; } finally { process.chdir(currentDir); } } async function backupProject(backupPath: string) { const zip: AdmZip = new AdmZip(); const outputPath: string = path.resolve(backupPath); const rootDir: string = path.resolve(); const files: string[] = fs.readdirSync(rootDir); files.forEach((entry) => { const fullPath = path.resolve(entry); const entryStats = fs.lstatSync(fullPath); if (skipBackup.includes(entry)) { // Don't add it to the backup } else if (entryStats.isDirectory()) { zip.addLocalFolder(entry, entry); } else { zip.addLocalFile(entry); } }); fsExtra.ensureDirSync(path.dirname(outputPath)); if (await zip.writeZipPromise(outputPath)) { console.log(`A backup of your project was created to ${outputPath}`); } else { throw new Error(`Error writting zip file to ${outputPath}`); } } async function restoreBackup(backupPath: string) { var zip = new AdmZip(backupPath); // reading archives zip.extractAllTo("./", true); // overwrite } async function updatePackages(): Promise { // Contains name of the package and minimum version const depedentPackages: string[] = ["office-addin-debugging", "office-addin-manifest"]; let command: string = "npm install"; let messageToBePrinted: string = "Installing latest versions of"; for (let i = 0; i < depedentPackages.length; i++) { const depedentPackage = depedentPackages[i]; command += ` ${depedentPackage}@latest`; messageToBePrinted += ` ${depedentPackage}`; if (i === depedentPackages.length - 2) { messageToBePrinted += " and"; } else { messageToBePrinted += ","; } } command += ` --save-dev`; console.log(messageToBePrinted.slice(0, -1)); await execAsync(command); } async function updateManifestXmlReferences(): Promise { await updatePackageJson(); await updateWebpackConfig(); } async function updatePackageJson(): Promise { const packageJson = `./package.json`; const readFileAsync = util.promisify(fs.readFile); const data = await readFileAsync(packageJson, "utf8"); let content = JSON.parse(data); // Change .xml references to .json Object.keys(content.scripts).forEach(function (key) { content.scripts[key] = content.scripts[key].replace(/manifest.xml/gi, `manifest.json`); }); // write updated json to file const writeFileAsync = util.promisify(fs.writeFile); await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); } async function updateWebpackConfig(): Promise { const weppackConfig = `./webpack.config.js`; const readFileAsync = util.promisify(fs.readFile); const data = await readFileAsync(weppackConfig, "utf8"); // switching to json extension is the easy fix. // TODO: update to remove the manifest copy plugin since it's not needed in webpack let content = data.replace(/"(manifest\*\.)xml"/gi, '"$1json"'); const writeFileAsync = util.promisify(fs.writeFile); await writeFileAsync(weppackConfig, content); }