Skip to content

Commit

Permalink
fix: office add-in generator flow (#11410)
Browse files Browse the repository at this point in the history
* refactor: office addin download

* test: ut

* test: ut

* test: ut

* refactor: clean up unused codes

* refactor: clean up unused codes

* test: ut
  • Loading branch information
jayzhang authored Apr 19, 2024
1 parent 16b87f3 commit ded43fb
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 289 deletions.
1 change: 1 addition & 0 deletions packages/fx-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"test:projcheck": "nyc mocha \"tests/common/projectTypeChecker.test.ts\"",
"test:telemetry": "nyc mocha \"tests/common/telemetry.test.ts\"",
"test:stringUtils": "nyc mocha \"tests/common/stringUtils.test.ts\"",
"test:generatorUtils": "nyc mocha \"tests/component/generatorUtils.test.ts\"",
"clean": "rm -rf build",
"prebuild": "npm run gen:cli",
"build": "rimraf build && npx tsc -p ./",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { toLower } from "lodash";
import { convertToLangKey } from "../utils";
import { DefaultTemplateGenerator } from "../templates/templateGenerator";
import { TemplateInfo } from "../templates/templateInfo";
import { fetchAndUnzip } from "../../utils";

const componentName = "office-addin";
const telemetryEvent = "generate";
Expand Down Expand Up @@ -141,7 +142,10 @@ export class OfficeAddinGenerator {

// Copy project template files from project repository
if (projectLink) {
await HelperMethods.downloadProjectTemplateZipFile(addinRoot, projectLink);
const fetchRes = await fetchAndUnzip("office-addin-generator", projectLink, addinRoot);
if (fetchRes.isErr()) {
return err(fetchRes.error);
}
let cmdLine = ""; // Call 'convert-to-single-host' npm script in generated project, passing in host parameter
if (inputs[QuestionNames.ProjectType] === ProjectTypeOptions.officeAddin().id) {
cmdLine = `npm run convert-to-single-host --if-present -- ${host} json`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,82 +16,6 @@ import { AccessGithubError, ReadFileError } from "../../../error/common";
const zipFile = "project.zip";

export class HelperMethods {
static async downloadProjectTemplateZipFile(
projectFolder: string,
projectRepo: string
): Promise<void> {
const projectTemplateZipFile = projectRepo;
let response: any;
try {
response = await fetch(projectTemplateZipFile, { method: "GET" });
} catch (e: any) {
throw new AccessGithubError(projectTemplateZipFile, "OfficeAddinGenerator", e);
}

return new Promise<void>((resolve, reject) => {
if (response.body) {
response.body
.pipe(fs.createWriteStream(path.resolve(projectFolder, zipFile)))
.on("error", (err: Error) => {
reject(new AccessGithubError(projectTemplateZipFile, "OfficeAddinGenerator", err));
})
.on("close", () => {
HelperMethods.unzipProjectTemplate(projectFolder)
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
} else {
reject(
new AccessGithubError(
projectTemplateZipFile,
"OfficeAddinGenerator",
new Error(`Response body of GET "${projectTemplateZipFile}" is null.`)
)
);
}
});
}

static async unzipProjectTemplate(projectFolder: string): Promise<void> {
return new Promise((resolve, reject) => {
// TODO: Verify file exists
const readStream = fs.createReadStream(path.resolve(`${projectFolder}/${zipFile}`));
readStream
.pipe(unzip.Extract({ path: projectFolder }))
.on("error", function (err: Error) {
unzipErrorHandler(projectFolder, reject, err);
})
.on("close", () => {
HelperMethods.moveUnzippedFiles(projectFolder);
resolve();
});
});
}

static moveUnzippedFiles(projectFolder: string): void {
// delete original zip file
const zipFilePath = path.resolve(`${projectFolder}/${zipFile}`);
if (fs.existsSync(zipFilePath)) {
fs.unlinkSync(zipFilePath);
}

// get path to unzipped folder
const unzippedFolder = fs.readdirSync(projectFolder).filter(function (file) {
return fs.statSync(`${projectFolder}/${file}`).isDirectory();
});

// construct paths to move files out of unzipped folder into project root folder
const fromFolder = path.resolve(`${projectFolder}/${unzippedFolder[0]}`);
HelperMethods.copyAddinFiles(fromFolder, projectFolder);

// delete project zipped folder
fs.rmSync(fromFolder, { recursive: true, force: true });
}

static copyAddinFiles(fromFolder: string, toFolder: string): void {
fse.copySync(fromFolder, toFolder, {
filter: (path) => !path.includes("node_modules"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Generator } from "../generator";
import { HelperMethods } from "../officeAddin/helperMethods";
import { getOfficeAddinTemplateConfig } from "./projectConfig";
import { convertToLangKey } from "../utils";
import { fetchAndUnzip } from "../../utils";

const COMPONENT_NAME = "office-xml-addin";
const TELEMETRY_EVENT = "generate";
Expand Down Expand Up @@ -85,8 +86,14 @@ export class OfficeXMLAddinGenerator {
// [Condition]: Project have remote repo (not manifest-only proj)

// -> Step: Download the project from GitHub
await HelperMethods.downloadProjectTemplateZipFile(destinationPath, projectLink);

const fetchRes = await fetchAndUnzip(
"office-xml-addin-generator",
projectLink,
destinationPath
);
if (fetchRes.isErr()) {
return err(fetchRes.error);
}
// -> Step: Convert to single Host
await OfficeXMLAddinGenerator.childProcessExec(
`npm run convert-to-single-host --if-present -- ${_.toLower(host)}`
Expand Down
61 changes: 60 additions & 1 deletion packages/fx-core/src/component/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@
// Licensed under the MIT license.
"use strict";

import { Context, FxError, Inputs, TelemetryReporter, UserError } from "@microsoft/teamsfx-api";
import {
Context,
FxError,
Inputs,
Result,
TelemetryReporter,
UserError,
err,
ok,
} from "@microsoft/teamsfx-api";
import AdmZip from "adm-zip";
import fs from "fs-extra";
import { cloneDeep } from "lodash";
import path from "path";
import { TOOLS } from "../core/globalVars";
import { AccessGithubError, WriteFileError } from "../error/common";
import {
ComponentNames,
Scenarios,
SolutionTelemetryComponentName,
SolutionTelemetryProperty,
} from "./constants";
import { DriverContext } from "./driver/interface/commonArgs";
import { fetchZipFromUrl } from "./generator/utils";
import { getComponent, getComponentByScenario } from "./workflow";

export function createContextV3(): Context {
Expand Down Expand Up @@ -111,3 +125,48 @@ export function sendErrorTelemetryThenReturnError(
reporter?.sendTelemetryErrorEvent(eventName, properties, measurements, errorProps);
return error;
}

export async function fetchAndUnzip(
component: string,
zipUrl: string,
targetDir: string,
skipRootFolder = true
): Promise<Result<undefined, FxError>> {
let zip: AdmZip;
try {
zip = await fetchZipFromUrl(zipUrl);
} catch (e: any) {
return err(new AccessGithubError(zipUrl, component, e));
}
if (!zip) {
return err(
new AccessGithubError(
zipUrl,
component,
new Error(`Failed to fetch zip from url: ${zipUrl}, result is undefined.`)
)
);
}
const entries = zip.getEntries();
let rootFolderName = "";
for (const entry of entries) {
const entryName: string = entry.entryName;
if (skipRootFolder && !rootFolderName) {
rootFolderName = entryName;
continue;
}
const rawEntryData: Buffer = entry.getData();
const entryData: string | Buffer = rawEntryData;
const targetPath = path.join(targetDir, entryName.replace(rootFolderName, ""));
try {
if (entry.isDirectory) {
await fs.ensureDir(targetPath);
} else {
await fs.writeFile(targetPath, entryData);
}
} catch (error: any) {
return err(new WriteFileError(error, component));
}
}
return ok(undefined);
}
Loading

0 comments on commit ded43fb

Please sign in to comment.