Skip to content

Commit 2310dd4

Browse files
feat: support package command in cli (#14206)
* feat: support package command in cli * test: fix ut
1 parent 34cd40d commit 2310dd4

File tree

8 files changed

+150
-70
lines changed

8 files changed

+150
-70
lines changed

packages/fx-core/src/common/projectTypeChecker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,10 @@ export function IsDeclarativeAgentManifest(manifest: any): boolean {
306306
);
307307
}
308308

309-
export function isTypeSpecProject(projectPath: string): boolean {
309+
export function isTypeSpecProject(projectPath: string | undefined): boolean {
310+
if (!projectPath) {
311+
return false;
312+
}
310313
const yamlFilePath = pathUtils.getYmlFilePath(projectPath);
311314
if (!yamlFilePath) {
312315
return false;

packages/fx-core/src/common/tools.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import { MetadataV3, MetadataV4 } from "./versionMetadata";
1616
import { pathUtils } from "../component/utils/pathUtils";
1717
import { TypeSpecCompileArgs } from "../component/driver/typeSpec/interface/typeSpecCompileArgs";
1818
import { parseDocument } from "yaml";
19+
import { TypeSpecCompileDriver } from "../component/driver/typeSpec/compile";
20+
import { NpmBuildDriver } from "../component/driver/script/npmBuildDriver";
21+
import { DriverContext } from "../component/driver/interface/commonArgs";
22+
import { isTypeSpecProject } from "./projectTypeChecker";
23+
import Container from "typedi";
1924

2025
export async function getSideloadingStatus(token: string): Promise<boolean | undefined> {
2126
return teamsDevPortalClient.getSideloadingStatus(token);
@@ -152,3 +157,31 @@ export function getTypeSpecArgs(projectPath: string): TypeSpecCompileArgs {
152157
typeSpecConfigPath: args.get("typeSpecConfigPath") ?? defaultArgs.typeSpecConfigPath,
153158
};
154159
}
160+
161+
export async function runForTypeSpecProject(
162+
projectPath: string | undefined,
163+
context: DriverContext
164+
): Promise<void> {
165+
const isTspProject = isTypeSpecProject(projectPath);
166+
if (isTspProject) {
167+
// Call npm/install
168+
const npmInstallDriver: NpmBuildDriver = Container.get("cli/runNpmCommand");
169+
const npmInstallArgs = {
170+
args: "install --no-audit --progress=false",
171+
};
172+
const npmInstallResult = (await npmInstallDriver.execute(npmInstallArgs, context)).result;
173+
if (npmInstallResult.isErr()) {
174+
throw err(npmInstallResult.error);
175+
}
176+
177+
// call typespec/compile
178+
const typeSpecCompileDriver: TypeSpecCompileDriver = Container.get("typeSpec/compile");
179+
const typeSpecCompileArgs: TypeSpecCompileArgs = getTypeSpecArgs(projectPath!);
180+
const typeSpecCompileResult = (
181+
await typeSpecCompileDriver.execute(typeSpecCompileArgs, context)
182+
).result;
183+
if (typeSpecCompileResult.isErr()) {
184+
throw err(typeSpecCompileResult.error);
185+
}
186+
}
187+
}

packages/fx-core/src/component/driver/teamsApp/teamsappMgr.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { manifestUtils } from "./utils/ManifestUtils";
4242
import { ValidateManifestDriver } from "./validate";
4343
import { ValidateAppPackageDriver } from "./validateAppPackage";
4444
import { ValidateWithTestCasesDriver } from "./validateTestCases";
45+
import { runForTypeSpecProject } from "../../../common/tools";
4546

4647
class TeamsAppMgr {
4748
async ensureAppPackageFile(inputs: TeamsAppInputs): Promise<Result<undefined, FxError>> {
@@ -169,6 +170,8 @@ class TeamsAppMgr {
169170
const buildDriver: CreateAppPackageDriver = Container.get(createAppPackageActionName);
170171
const driverContext: DriverContext = createDriverContext(inputs);
171172

173+
// For TSP projects
174+
await runForTypeSpecProject(inputs.projectPath, driverContext);
172175
const res = (await buildDriver.execute(packageArgs, driverContext)).result;
173176
if (res.isErr()) {
174177
return err(res.error);

packages/fx-core/src/core/FxCore.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ import {
6969
import {
7070
IsDeclarativeAgentManifest,
7171
ProjectTypeResult,
72-
isTypeSpecProject,
7372
projectTypeChecker,
7473
} from "../common/projectTypeChecker";
7574
import { TelemetryEvent, TelemetryProperty, telemetryUtils } from "../common/telemetry";
@@ -206,10 +205,7 @@ import { CoreTelemetryEvent, CoreTelemetryProperty } from "./telemetry";
206205
import { CoreHookContext, PreProvisionResForVS, VersionCheckRes } from "./types";
207206
import { InstallAppArgs } from "../component/driver/devChannel/interfaces/InstallAppArgs";
208207
import { TemplateNames } from "../component/generator/templates/templateNames";
209-
import { getTypeSpecArgs } from "../common/tools";
210-
import { NpmBuildDriver } from "../component/driver/script/npmBuildDriver";
211-
import { TypeSpecCompileDriver } from "../component/driver/typeSpec/compile";
212-
import { TypeSpecCompileArgs } from "../component/driver/typeSpec/interface/typeSpecCompileArgs";
208+
import { runForTypeSpecProject } from "../common/tools";
213209

214210
export class FxCore {
215211
constructor(tools: Tools) {
@@ -1265,28 +1261,7 @@ export class FxCore {
12651261
const context: DriverContext = createDriverContext(inputs);
12661262

12671263
// For TSP projects
1268-
const isTspProject = isTypeSpecProject(inputs.projectPath!);
1269-
if (isTspProject) {
1270-
// Call npm/install
1271-
const npmInstallDriver: NpmBuildDriver = Container.get("cli/runNpmCommand");
1272-
const npmInstallArgs = {
1273-
args: "install --no-audit --progress=false",
1274-
};
1275-
const npmInstallResult = (await npmInstallDriver.execute(npmInstallArgs, context)).result;
1276-
if (npmInstallResult.isErr()) {
1277-
throw err(npmInstallResult.error);
1278-
}
1279-
1280-
// call typespec/compile
1281-
const typeSpecCompileDriver: TypeSpecCompileDriver = Container.get("typeSpec/compile");
1282-
const typeSpecCompileArgs: TypeSpecCompileArgs = getTypeSpecArgs(inputs.projectPath!);
1283-
const typeSpecCompileResult = (
1284-
await typeSpecCompileDriver.execute(typeSpecCompileArgs, context)
1285-
).result;
1286-
if (typeSpecCompileResult.isErr()) {
1287-
throw err(typeSpecCompileResult.error);
1288-
}
1289-
}
1264+
await runForTypeSpecProject(inputs.projectPath, context);
12901265

12911266
const teamsAppManifestFilePath = inputs?.[QuestionNames.TeamsAppManifestFilePath] as string;
12921267

packages/fx-core/tests/common/projectTypeChecker.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,11 @@ describe("ProjectTypeChecker", () => {
564564
chai.expect(result).to.be.true;
565565
});
566566

567+
it("should return false if no project path", () => {
568+
const result = isTypeSpecProject(undefined);
569+
chai.expect(result).to.be.false;
570+
});
571+
567572
it("should return false if no yaml file", () => {
568573
sandbox.stub(pathUtils, "getYmlFilePath").returns(undefined);
569574
const result = isTypeSpecProject("test-project-path");

packages/fx-core/tests/common/tools.test.ts

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { ok } from "@microsoft/teamsfx-api";
4+
import { err, ok, UserError } from "@microsoft/teamsfx-api";
55
import axios, { AxiosResponse } from "axios";
66
import * as chai from "chai";
77
import chaiAsPromised from "chai-as-promised";
@@ -20,14 +20,19 @@ import {
2020
isTestToolEnabledProject,
2121
isSandboxedEnabled,
2222
getTypeSpecArgs,
23+
runForTypeSpecProject,
2324
} from "../../src/common/tools";
2425
import { PackageService } from "../../src/component/m365/packageService";
2526
import { isVideoFilterProject } from "../../src/core/middleware/videoFilterAppBlocker";
2627
import { isUserCancelError } from "../../src/error/common";
27-
import { MockedM365Provider, MockTools } from "../core/utils";
28+
import { MockedM365Provider, MockLogProvider, MockTools } from "../core/utils";
2829
import { GraphClient } from "../../src/client/graphClient";
2930
import { setTools } from "../../src/common/globalVars";
3031
import { pathUtils } from "../../src";
32+
import { WrapDriverContext } from "../../src/component/driver/util/wrapUtil";
33+
import { NpmBuildDriver } from "../../src/component/driver/script/npmBuildDriver";
34+
import { TypeSpecCompileDriver } from "../../src/component/driver/typeSpec/compile";
35+
import * as ProjecTypeChecker from "../../src/common/projectTypeChecker";
3136

3237
chai.use(chaiAsPromised);
3338

@@ -522,4 +527,96 @@ projectId: 00000000-0000-0000-0000-000000000000`;
522527
});
523528
});
524529
});
530+
531+
describe("runForTypeSpecProject", () => {
532+
const sandbox = sinon.createSandbox();
533+
let mockContext: WrapDriverContext;
534+
beforeEach(() => {
535+
mockContext = {
536+
m365TokenProvider: new MockedM365Provider(),
537+
logProvider: new MockLogProvider(),
538+
addSummary: sandbox.stub(),
539+
summaries: [],
540+
} as unknown as WrapDriverContext;
541+
});
542+
afterEach(() => {
543+
sandbox.restore();
544+
});
545+
546+
it("should call npm install and typeSpec compile for TypeSpec project", async () => {
547+
const mockProjectPath = "mock-project-path";
548+
const npmInstallStub = sandbox
549+
.stub(NpmBuildDriver.prototype, "execute")
550+
.resolves({ result: ok(new Map()), summaries: [] });
551+
const typeSpecCompileStub = sandbox
552+
.stub(TypeSpecCompileDriver.prototype, "execute")
553+
.resolves({ result: ok(new Map()), summaries: [] });
554+
sandbox.stub(ProjecTypeChecker, "isTypeSpecProject").returns(true);
555+
sandbox.stub(pathUtils, "getYmlFilePath").returns("m365agents.yml");
556+
sandbox
557+
.stub(fs, "readFileSync")
558+
.returns(
559+
"provision:\n - uses: typeSpec/compile\n with:\n path: ./custom.tsp\n manifestPath: ./customManifest.json\n outputDir: ./customOutputDir\n typeSpecConfigPath: ./customTspconfig.yaml"
560+
);
561+
await runForTypeSpecProject(mockProjectPath, mockContext);
562+
chai.expect(npmInstallStub.calledOnce).to.be.true;
563+
chai.expect(typeSpecCompileStub.calledOnce).to.be.true;
564+
});
565+
566+
it("should skip for not TypeSpec project", async () => {
567+
const mockProjectPath = "mock-project-path";
568+
const npmInstallStub = sandbox
569+
.stub(NpmBuildDriver.prototype, "execute")
570+
.resolves({ result: ok(new Map()), summaries: [] });
571+
const typeSpecCompileStub = sandbox
572+
.stub(TypeSpecCompileDriver.prototype, "execute")
573+
.resolves({ result: ok(new Map()), summaries: [] });
574+
sandbox.stub(ProjecTypeChecker, "isTypeSpecProject").returns(false);
575+
await runForTypeSpecProject(mockProjectPath, mockContext);
576+
chai.expect(npmInstallStub.notCalled).to.be.true;
577+
chai.expect(typeSpecCompileStub.notCalled).to.be.true;
578+
});
579+
580+
it("should throw error if npm install fails", async () => {
581+
const mockProjectPath = "mock-project-path";
582+
sandbox.stub(ProjecTypeChecker, "isTypeSpecProject").returns(true);
583+
const typeSpecCompileStub = sandbox
584+
.stub(TypeSpecCompileDriver.prototype, "execute")
585+
.resolves({ result: ok(new Map()), summaries: [] });
586+
const npmInstallStub = sandbox.stub(NpmBuildDriver.prototype, "execute").resolves({
587+
result: err(new UserError("source", "NpmInstallError", "NPM install failed")),
588+
summaries: [],
589+
});
590+
try {
591+
await runForTypeSpecProject(mockProjectPath, mockContext);
592+
} catch (error) {
593+
chai.expect(error.error.name).to.equal("NpmInstallError");
594+
}
595+
});
596+
597+
it("should throw error if typespec compile fails", async () => {
598+
const mockProjectPath = "mock-project-path";
599+
sandbox.stub(ProjecTypeChecker, "isTypeSpecProject").returns(true);
600+
sandbox.stub(pathUtils, "getYmlFilePath").returns("m365agents.yml");
601+
sandbox
602+
.stub(fs, "readFileSync")
603+
.returns(
604+
"provision:\n - uses: typeSpec/compile\n with:\n path: ./custom.tsp\n manifestPath: ./customManifest.json\n outputDir: ./customOutputDir\n typeSpecConfigPath: ./customTspconfig.yaml"
605+
);
606+
const typeSpecCompileStub = sandbox
607+
.stub(TypeSpecCompileDriver.prototype, "execute")
608+
.resolves({
609+
result: err(new UserError("source", "TypeSpecCompileError", "TypeSpec compile failed")),
610+
summaries: [],
611+
});
612+
const npmInstallStub = sandbox
613+
.stub(NpmBuildDriver.prototype, "execute")
614+
.resolves({ result: ok(new Map()), summaries: [] });
615+
try {
616+
await runForTypeSpecProject(mockProjectPath, mockContext);
617+
} catch (error) {
618+
chai.expect(error.error.name).to.equal("TypeSpecCompileError");
619+
}
620+
});
621+
});
525622
});

packages/fx-core/tests/component/driver/teamsApp/teamsappMgr.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { ValidateManifestDriver } from "../../../../src/component/driver/teamsAp
2121
import { ValidateAppPackageDriver } from "../../../../src/component/driver/teamsApp/validateAppPackage";
2222
import { ConfigureTeamsAppDriver } from "../../../../src/component/driver/teamsApp/configure";
2323
import { PublishAppPackageDriver } from "../../../../src/component/driver/teamsApp/publishAppPackage";
24+
import * as CommonTools from "../../../../src/common/tools";
2425

2526
describe("TeamsAppMgr", async () => {
2627
const sandbox = sinon.createSandbox();
@@ -241,6 +242,7 @@ describe("TeamsAppMgr", async () => {
241242
it("driver fail", async () => {
242243
sandbox.stub(fs, "pathExists").resolves(true);
243244
sandbox.stub(teamsappMgr, "checkAndTryToLoadEnv").resolves(ok("dev"));
245+
sandbox.stub(CommonTools, "runForTypeSpecProject").resolves();
244246
sandbox
245247
.stub(CreateAppPackageDriver.prototype, "execute")
246248
.resolves({ result: err(new UserCancelError()), summaries: [] });
@@ -254,6 +256,7 @@ describe("TeamsAppMgr", async () => {
254256
it("driver success", async () => {
255257
sandbox.stub(fs, "pathExists").resolves(true);
256258
sandbox.stub(teamsappMgr, "checkAndTryToLoadEnv").resolves(ok(undefined));
259+
sandbox.stub(CommonTools, "runForTypeSpecProject").resolves();
257260
sandbox
258261
.stub(CreateAppPackageDriver.prototype, "execute")
259262
.resolves({ result: ok(new Map()), summaries: [] });

packages/fx-core/tests/core/FxCore.test.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ import { MockTools, MockUserInteraction, randomAppName } from "./utils";
127127
import { TabCapabilityOptions } from "../../src/question/scaffold/vsc/CapabilityOptions";
128128
import { InstallAppToChannelDriver } from "../../src/component/driver/devChannel/installApp";
129129
import * as CommonTools from "../../src/common/tools";
130-
import * as ProjecTypeChecker from "../../src/common/projectTypeChecker";
131-
import { NpmBuildDriver } from "../../src/component/driver/script/npmBuildDriver";
132-
import { TypeSpecCompileDriver } from "../../src/component/driver/typeSpec/compile";
133130

134131
const tools = new MockTools();
135132

@@ -2278,6 +2275,7 @@ describe("Teams app APIs", async () => {
22782275
};
22792276

22802277
sinon.stub(process, "platform").value("win32");
2278+
sinon.stub(CommonTools, "runForTypeSpecProject").resolves();
22812279
const runStub = sinon
22822280
.stub(CreateAppPackageDriver.prototype, "execute")
22832281
.resolves({ result: ok(new Map()), summaries: [] });
@@ -2287,43 +2285,6 @@ describe("Teams app APIs", async () => {
22872285
sinon.assert.calledOnce(showMessageStub);
22882286
});
22892287

2290-
it("create app package with TypeSpec project", async () => {
2291-
setTools(tools);
2292-
const appName = await mockV3Project();
2293-
const inputs: Inputs = {
2294-
platform: Platform.VSCode,
2295-
[QuestionNames.Folder]: os.tmpdir(),
2296-
[QuestionNames.TeamsAppManifestFilePath]: ".\\appPackage\\manifest.json",
2297-
projectPath: path.join(os.tmpdir(), appName),
2298-
[QuestionNames.OutputZipPathParamName]: ".\\build\\appPackage\\appPackage.dev.zip",
2299-
};
2300-
const isTypeSpecProjectStub = sinon.stub(ProjecTypeChecker, "isTypeSpecProject").returns(true);
2301-
const getTypeSpecArgsStub = sinon.stub(CommonTools, "getTypeSpecArgs").returns({
2302-
path: "./main.tsp",
2303-
manifestPath: "./appPackage/manifest.json",
2304-
outputDir: "./appPackage/.generated",
2305-
typeSpecConfigPath: "./tspconfig.yaml",
2306-
});
2307-
const npmInstallStub = sinon
2308-
.stub(NpmBuildDriver.prototype, "execute")
2309-
.resolves({ result: ok(new Map()), summaries: [] });
2310-
const typeSpecCompileStub = sinon
2311-
.stub(TypeSpecCompileDriver.prototype, "execute")
2312-
.resolves({ result: ok(new Map()), summaries: [] });
2313-
sinon.stub(process, "platform").value("win32");
2314-
const runStub = sinon
2315-
.stub(CreateAppPackageDriver.prototype, "execute")
2316-
.resolves({ result: ok(new Map()), summaries: [] });
2317-
const showMessageStub = sinon.stub(tools.ui, "showMessage");
2318-
await core.createAppPackage(inputs);
2319-
sinon.assert.calledOnce(runStub);
2320-
sinon.assert.calledOnce(showMessageStub);
2321-
sinon.assert.calledOnce(isTypeSpecProjectStub);
2322-
sinon.assert.calledOnce(getTypeSpecArgsStub);
2323-
sinon.assert.calledOnce(npmInstallStub);
2324-
sinon.assert.calledOnce(typeSpecCompileStub);
2325-
});
2326-
23272288
it("publish application", async () => {
23282289
const appName = await mockV3Project();
23292290
const inputs: Inputs = {

0 commit comments

Comments
 (0)