diff --git a/packages/cli/src/commands/models/provision.ts b/packages/cli/src/commands/models/provision.ts index 0004e051d14..ceec62b6ddf 100644 --- a/packages/cli/src/commands/models/provision.ts +++ b/packages/cli/src/commands/models/provision.ts @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import { CLICommand, CLIContext, InputsWithProjectPath } from "@microsoft/teamsfx-api"; -import { CoreQuestionNames } from "@microsoft/teamsfx-core"; -import { newResourceGroupOption } from "@microsoft/teamsfx-core/build/question/other"; +import { CoreQuestionNames, newResourceGroupOption } from "@microsoft/teamsfx-core"; import { getFxCore } from "../../activate"; import { commands } from "../../resource"; import { TelemetryEvent } from "../../telemetry/cliTelemetryEvents"; diff --git a/packages/fx-core/resource/package.nls.json b/packages/fx-core/resource/package.nls.json index 1b46a5be65b..83a54a8f4e7 100644 --- a/packages/fx-core/resource/package.nls.json +++ b/packages/fx-core/resource/package.nls.json @@ -1,5 +1,5 @@ { - "core.addApi.confirm":"Teams Toolkit will modify files in your \"%s\" folder based on the new OpenAPI document you provided. To avoid losing unexpected changes, back up your files or use git for change tracking before proceeding.", + "core.addApi.confirm": "Teams Toolkit will modify files in your \"%s\" folder based on the new OpenAPI document you provided. To avoid losing unexpected changes, back up your files or use git for change tracking before proceeding.", "core.addApi.continue": "Add", "core.provision.provision": "Provision", "core.provision.learnMore": "More info", @@ -529,7 +529,6 @@ "core.common.OutlookDesktopClientName": "Outlook desktop client id", "core.common.OutlookWebClientName1": "Outlook web access client id 1", "core.common.OutlookWebClientName2": "Outlook web access client id 2", - "core.common.OutlookMobileClientName": "Outlook mobile client id", "core.common.CancelledMessage": "Operation is canceled.", "core.common.SwaggerNotSupported": "Swagger 2.0 is not supported. Convert it to OpenAPI 3.0 first.", "core.common.SpecVersionNotSupported": "OpenAPI version %s is not supported. Use version 3.0.x.", @@ -888,4 +887,4 @@ "driver.oauth.confirm.update": "The following parameters will be updated:\n%s\nDo you want to continue?", "driver.oauth.log.successUpdateOauth": "OAuth registration updated successfully!", "driver.oauth.info.update": "OAuth registration updated successfully! The following parameters have been updated:\n%s" -} +} \ No newline at end of file diff --git a/packages/fx-core/src/common/azureUtils.ts b/packages/fx-core/src/common/azureUtils.ts new file mode 100644 index 00000000000..4105b7e7701 --- /dev/null +++ b/packages/fx-core/src/common/azureUtils.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AzureAccountProvider, + FxError, + OptionItem, + Result, + SubscriptionInfo, + SystemError, + UserError, + UserInteraction, + err, + ok, +} from "@microsoft/teamsfx-api"; +import { getDefaultString, getLocalizedString } from "./localizeUtils"; + +export async function askSubscription( + azureAccountProvider: AzureAccountProvider, + ui: UserInteraction, + activeSubscriptionId?: string +): Promise> { + const subscriptions: SubscriptionInfo[] = await azureAccountProvider.listSubscriptions(); + + if (subscriptions.length === 0) { + return err( + new UserError( + "Core", + "NoSubscriptionFound", + getDefaultString("error.NoSubscriptionFound"), + getLocalizedString("error.NoSubscriptionFound") + ) + ); + } + let resultSub = subscriptions.find((sub) => sub.subscriptionId === activeSubscriptionId); + if (activeSubscriptionId === undefined || resultSub === undefined) { + let selectedSub: SubscriptionInfo | undefined = undefined; + if (subscriptions.length === 1) { + selectedSub = subscriptions[0]; + } else { + const options: OptionItem[] = subscriptions.map((sub) => { + return { + id: sub.subscriptionId, + label: sub.subscriptionName, + data: sub.tenantId, + } as OptionItem; + }); + const askRes = await ui.selectOption({ + name: "subscription", + title: "Select a subscription", + options: options, + returnObject: true, + }); + if (askRes.isErr()) return err(askRes.error); + const subItem = askRes.value.result as OptionItem; + selectedSub = { + subscriptionId: subItem.id, + subscriptionName: subItem.label, + tenantId: subItem.data as string, + }; + } + if (selectedSub === undefined) { + return err( + new SystemError( + "Core", + "NoSubscriptionFound", + getDefaultString("error.NoSubscriptionFound"), + getLocalizedString("error.NoSubscriptionFound") + ) + ); + } + resultSub = selectedSub; + } + return ok(resultSub); +} diff --git a/packages/fx-core/src/common/constants.ts b/packages/fx-core/src/common/constants.ts index 5ab19489968..b45b5fa61e5 100644 --- a/packages/fx-core/src/common/constants.ts +++ b/packages/fx-core/src/common/constants.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { getLocalizedString } from "./localizeUtils"; + export class ConstantString { static readonly UTF8Encoding = "utf-8"; static readonly DeploymentResourceType = "Microsoft.Resources/deployments"; @@ -55,3 +57,35 @@ export class FeatureFlagName { static readonly CopilotAuth = "API_COPILOT_PLUGIN_AUTH"; static readonly CustomizeGpt = "TEAMSFX_DECLARATIVE_COPILOT"; } + +export function getAllowedAppMaps(): Record { + return { + [TeamsClientId.MobileDesktop]: getLocalizedString("core.common.TeamsMobileDesktopClientName"), + [TeamsClientId.Web]: getLocalizedString("core.common.TeamsWebClientName"), + [OfficeClientId.Desktop]: getLocalizedString("core.common.OfficeDesktopClientName"), + [OfficeClientId.Web1]: getLocalizedString("core.common.OfficeWebClientName1"), + [OfficeClientId.Web2]: getLocalizedString("core.common.OfficeWebClientName2"), + [OutlookClientId.Desktop]: getLocalizedString("core.common.OutlookDesktopClientName"), + [OutlookClientId.Web1]: getLocalizedString("core.common.OutlookWebClientName1"), + [OutlookClientId.Web2]: getLocalizedString("core.common.OutlookWebClientName2"), + }; +} + +const AzurePortalUrl = "https://portal.azure.com"; +export function getResourceGroupInPortal( + subscriptionId?: string, + tenantId?: string, + resourceGroupName?: string +): string | undefined { + if (subscriptionId && tenantId && resourceGroupName) { + return `${AzurePortalUrl}/#@${tenantId}/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}`; + } else { + return undefined; + } +} + +export const AuthSvcScopes = ["https://api.spaces.skype.com/Region.ReadWrite"]; +export const GraphScopes = ["Application.ReadWrite.All", "TeamsAppInstallation.ReadForUser"]; +export const GraphReadUserScopes = ["https://graph.microsoft.com/User.ReadBasic.All"]; +export const SPFxScopes = (tenant: string) => [`${tenant}/Sites.FullControl.All`]; +export const AzureScopes = ["https://management.core.windows.net/user_impersonation"]; diff --git a/packages/fx-core/src/common/globalState.ts b/packages/fx-core/src/common/globalState.ts index 51ae42db9bb..e977067b573 100644 --- a/packages/fx-core/src/common/globalState.ts +++ b/packages/fx-core/src/common/globalState.ts @@ -7,7 +7,7 @@ import * as fs from "fs-extra"; import crypto from "crypto"; import { ConfigFolderName, ProductName } from "@microsoft/teamsfx-api"; import properLock from "proper-lockfile"; -import { waitSeconds } from "./tools"; +import { waitSeconds } from "./utils"; const GlobalStateFileName = "state.json"; diff --git a/packages/fx-core/src/common/local/taskDefinition.ts b/packages/fx-core/src/common/local/taskDefinition.ts index 0ceb865edd7..9dd242d321b 100644 --- a/packages/fx-core/src/common/local/taskDefinition.ts +++ b/packages/fx-core/src/common/local/taskDefinition.ts @@ -5,7 +5,7 @@ import { FolderName, npmInstallCommand } from "./constants"; import path from "path"; import { isWindows } from "../deps-checker/util/system"; -import { ProgrammingLanguage } from "../../question/create"; +import { ProgrammingLanguage } from "../../question/constants"; export interface ITaskDefinition { name: string; diff --git a/packages/fx-core/src/common/m365/launchHelper.ts b/packages/fx-core/src/common/m365/launchHelper.ts index d3be953d9c7..8dd25246981 100644 --- a/packages/fx-core/src/common/m365/launchHelper.ts +++ b/packages/fx-core/src/common/m365/launchHelper.ts @@ -3,15 +3,15 @@ import { err, FxError, LogProvider, M365TokenProvider, ok, Result } from "@microsoft/teamsfx-api"; +import { hooks } from "@feathersjs/hooks"; import { CoreSource } from "../../core/error"; -import { AppStudioScopes } from "../tools"; +import { ErrorContextMW } from "../../core/globalVars"; +import { assembleError } from "../../error/common"; +import { HubTypes } from "../../question/constants"; import { NotExtendedToM365Error } from "./errors"; import { PackageService } from "./packageService"; import { serviceEndpoint, serviceScope } from "./serviceConstant"; -import { assembleError } from "../../error/common"; -import { HubTypes } from "../../question/other"; -import { ErrorContextMW } from "../../core/globalVars"; -import { hooks } from "@feathersjs/hooks"; +import { AppStudioScopes } from "../../component/driver/teamsApp/constants"; export class LaunchHelper { private readonly m365TokenProvider: M365TokenProvider; diff --git a/packages/fx-core/src/common/m365/packageService.ts b/packages/fx-core/src/common/m365/packageService.ts index 0b49dd14e8d..3f343e6e394 100644 --- a/packages/fx-core/src/common/m365/packageService.ts +++ b/packages/fx-core/src/common/m365/packageService.ts @@ -1,19 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { hooks } from "@feathersjs/hooks"; +import { LogProvider, SystemError, UserError } from "@microsoft/teamsfx-api"; import AdmZip from "adm-zip"; import FormData from "form-data"; import fs from "fs-extra"; - -import { LogProvider, SystemError, UserError } from "@microsoft/teamsfx-api"; - -import { waitSeconds } from "../tools"; -import { NotExtendedToM365Error } from "./errors"; -import { serviceEndpoint } from "./serviceConstant"; +import { ErrorContextMW, TOOLS } from "../../core/globalVars"; import { assembleError } from "../../error/common"; import { ErrorCategory } from "../../error/types"; -import { ErrorContextMW, TOOLS } from "../../core/globalVars"; -import { hooks } from "@feathersjs/hooks"; import { Component, TelemetryEvent, @@ -21,7 +16,10 @@ import { sendTelemetryErrorEvent, sendTelemetryEvent, } from "../telemetry"; +import { waitSeconds } from "../utils"; import { WrappedAxiosClient } from "../wrappedAxiosClient"; +import { NotExtendedToM365Error } from "./errors"; +import { serviceEndpoint } from "./serviceConstant"; const M365ErrorSource = "M365"; const M365ErrorComponent = "PackageService"; diff --git a/packages/fx-core/src/common/projectSettingsHelperV3.ts b/packages/fx-core/src/common/projectSettingsHelperV3.ts deleted file mode 100644 index 0e6e3a7bf72..00000000000 --- a/packages/fx-core/src/common/projectSettingsHelperV3.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -import { ComponentNames } from "../component/constants"; -import { getComponent } from "../component/workflow"; - -export function hasFunctionBot(projectSettings: any): boolean { - const botComponent = getComponent(projectSettings, ComponentNames.TeamsBot); - if (!botComponent) return false; - return botComponent.hosting === ComponentNames.Function; -} diff --git a/packages/fx-core/src/common/requestUtils.ts b/packages/fx-core/src/common/requestUtils.ts new file mode 100644 index 00000000000..58a8e466cff --- /dev/null +++ b/packages/fx-core/src/common/requestUtils.ts @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import axios, { AxiosResponse, CancelToken } from "axios"; + +export async function sendRequestWithRetry( + requestFn: () => Promise>, + tryLimits: number +): Promise> { + // !status means network error, see https://github.com/axios/axios/issues/383 + const canTry = (status: number | undefined) => !status || (status >= 500 && status < 600); + + let status: number | undefined; + let error: Error; + + for (let i = 0; i < tryLimits && canTry(status); i++) { + try { + const res = await requestFn(); + if (res.status === 200 || res.status === 201) { + return res; + } else { + error = new Error(`HTTP Request failed: ${JSON.stringify(res)}`); + } + status = res.status; + } catch (e: any) { + error = e; + status = e?.response?.status; + } + } + + error ??= new Error(`RequestWithRetry got bad tryLimits: ${tryLimits}`); + throw error; +} +export async function sendRequestWithTimeout( + requestFn: (cancelToken: CancelToken) => Promise>, + timeoutInMs: number, + tryLimits = 1 +): Promise> { + const source = axios.CancelToken.source(); + const timeout = setTimeout(() => { + source.cancel(); + }, timeoutInMs); + try { + const res = await sendRequestWithRetry(() => requestFn(source.token), tryLimits); + clearTimeout(timeout); + return res; + } catch (err: unknown) { + if (axios.isCancel(err)) { + throw new Error("Request timeout"); + } + throw err; + } +} diff --git a/packages/fx-core/src/common/samples.ts b/packages/fx-core/src/common/samples.ts index d1937ebf4fd..433f230a111 100644 --- a/packages/fx-core/src/common/samples.ts +++ b/packages/fx-core/src/common/samples.ts @@ -2,13 +2,11 @@ // Licensed under the MIT license. import axios from "axios"; - import { hooks } from "@feathersjs/hooks"; - -import { SampleUrlInfo, sendRequestWithTimeout } from "../component/generator/utils"; import { ErrorContextMW } from "../core/globalVars"; import { AccessGithubError } from "../error/common"; import { FeatureFlagName } from "./constants"; +import { sendRequestWithTimeout } from "./requestUtils"; const packageJson = require("../../package.json"); @@ -19,6 +17,13 @@ export const SampleConfigTag = "v2.5.0"; // prerelease tag is always using a branch. export const SampleConfigBranchForPrerelease = "main"; +export type SampleUrlInfo = { + owner: string; + repository: string; + ref: string; + dir: string; +}; + export interface SampleConfig { id: string; onboardDate: Date; diff --git a/packages/fx-core/src/common/stringUtils.ts b/packages/fx-core/src/common/stringUtils.ts index b2f6f0c6eff..87e87efd486 100644 --- a/packages/fx-core/src/common/stringUtils.ts +++ b/packages/fx-core/src/common/stringUtils.ts @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { FailedToParseResourceIdError } from "../core/error"; +import * as Handlebars from "handlebars"; +import * as uuid from "uuid"; +import * as crypto from "crypto"; + const MIN_ENTROPY = 4; const SECRET_REPLACE = ""; const USER_REPLACE = ""; @@ -132,3 +137,40 @@ export function maskSecretValues(stdout: string, replace = "***"): string { } return stdout; } + +export function convertToAlphanumericOnly(appName: string): string { + return appName.replace(/[^\da-zA-Z]/g, ""); +} + +Handlebars.registerHelper("contains", (value, array) => { + array = array instanceof Array ? array : [array]; + return array.indexOf(value) > -1 ? this : ""; +}); +Handlebars.registerHelper("notContains", (value, array) => { + array = array instanceof Array ? array : [array]; + return array.indexOf(value) == -1 ? this : ""; +}); +Handlebars.registerHelper("equals", (value, target) => { + return value === target ? this : ""; +}); + +export function getResourceGroupNameFromResourceId(resourceId: string): string { + const result = parseFromResourceId(/\/resourceGroups\/([^\/]*)\//i, resourceId); + if (!result) { + throw FailedToParseResourceIdError("resource group name", resourceId); + } + return result; +} + +export function parseFromResourceId(pattern: RegExp, resourceId: string): string { + const result = resourceId.match(pattern); + return result ? result[1].trim() : ""; +} + +export function getUuid(): string { + return uuid.v4(); +} + +export function getHashedEnv(envName: string): string { + return crypto.createHash("sha256").update(envName).digest("hex"); +} diff --git a/packages/fx-core/src/common/tools.ts b/packages/fx-core/src/common/tools.ts index ac8f9282b5a..0f32a7ebeb9 100644 --- a/packages/fx-core/src/common/tools.ts +++ b/packages/fx-core/src/common/tools.ts @@ -2,240 +2,21 @@ // Licensed under the MIT license. import { Tunnel } from "@microsoft/dev-tunnels-contracts"; import { - TunnelManagementHttpClient, ManagementApiVersions, + TunnelManagementHttpClient, } from "@microsoft/dev-tunnels-management"; -import { - AzureAccountProvider, - FxError, - M365TokenProvider, - OptionItem, - Result, - SubscriptionInfo, - SystemError, - UserError, - UserInteraction, - err, - ok, -} from "@microsoft/teamsfx-api"; +import { FxError, M365TokenProvider, Result, SystemError, err, ok } from "@microsoft/teamsfx-api"; import axios from "axios"; -import * as crypto from "crypto"; import * as fs from "fs-extra"; -import * as Handlebars from "handlebars"; -import * as uuid from "uuid"; import { parse } from "yaml"; -import { SolutionError } from "../component/constants"; import { AppStudioClient } from "../component/driver/teamsApp/clients/appStudioClient"; import { AuthSvcClient } from "../component/driver/teamsApp/clients/authSvcClient"; import { getAppStudioEndpoint } from "../component/driver/teamsApp/constants"; -import { manifestUtils } from "../component/driver/teamsApp/utils/ManifestUtils"; import { AppStudioClient as BotAppStudioClient } from "../component/resource/botService/appStudio/appStudioClient"; -import { FailedToParseResourceIdError } from "../core/error"; import { getProjectSettingsPath } from "../core/middleware/projectSettingsLoader"; -import { assembleError } from "../error/common"; -import { FeatureFlagName, OfficeClientId, OutlookClientId, TeamsClientId } from "./constants"; -import { isFeatureFlagEnabled } from "./featureFlags"; -import { getDefaultString, getLocalizedString } from "./localizeUtils"; +import { GraphReadUserScopes, SPFxScopes } from "./constants"; import { PackageService } from "./m365/packageService"; -Handlebars.registerHelper("contains", (value, array) => { - array = array instanceof Array ? array : [array]; - return array.indexOf(value) > -1 ? this : ""; -}); -Handlebars.registerHelper("notContains", (value, array) => { - array = array instanceof Array ? array : [array]; - return array.indexOf(value) == -1 ? this : ""; -}); -Handlebars.registerHelper("equals", (value, target) => { - return value === target ? this : ""; -}); - -const AzurePortalUrl = "https://portal.azure.com"; - -export const deepCopy = (target: T): T => { - if (target === null) { - return target; - } - if (target instanceof Date) { - return new Date(target.getTime()) as any; - } - if (target instanceof Array) { - const cp = [] as any[]; - (target as any[]).forEach((v) => { - cp.push(v); - }); - return cp.map((n: any) => deepCopy(n)) as any; - } - if (typeof target === "object" && Object.keys(target).length) { - const cp = { ...(target as { [key: string]: any }) } as { - [key: string]: any; - }; - Object.keys(cp).forEach((k) => { - cp[k] = deepCopy(cp[k]); - }); - return cp as T; - } - return target; -}; - -export function isUserCancelError(error: Error): boolean { - const errorName = "name" in error ? (error as any)["name"] : ""; - return ( - errorName === "User Cancel" || - errorName === "CancelProvision" || - errorName === "UserCancel" || - errorName === "UserCancelError" - ); -} - -export function isCheckAccountError(error: Error): boolean { - const errorName = "name" in error ? (error as any)["name"] : ""; - return ( - errorName === SolutionError.TeamsAppTenantIdNotRight || - errorName === SolutionError.SubscriptionNotFound - ); -} - -export async function askSubscription( - azureAccountProvider: AzureAccountProvider, - ui: UserInteraction, - activeSubscriptionId?: string -): Promise> { - const subscriptions: SubscriptionInfo[] = await azureAccountProvider.listSubscriptions(); - - if (subscriptions.length === 0) { - return err( - new UserError( - "Core", - "NoSubscriptionFound", - getDefaultString("error.NoSubscriptionFound"), - getLocalizedString("error.NoSubscriptionFound") - ) - ); - } - let resultSub = subscriptions.find((sub) => sub.subscriptionId === activeSubscriptionId); - if (activeSubscriptionId === undefined || resultSub === undefined) { - let selectedSub: SubscriptionInfo | undefined = undefined; - if (subscriptions.length === 1) { - selectedSub = subscriptions[0]; - } else { - const options: OptionItem[] = subscriptions.map((sub) => { - return { - id: sub.subscriptionId, - label: sub.subscriptionName, - data: sub.tenantId, - } as OptionItem; - }); - const askRes = await ui.selectOption({ - name: "subscription", - title: "Select a subscription", - options: options, - returnObject: true, - }); - if (askRes.isErr()) return err(askRes.error); - const subItem = askRes.value.result as OptionItem; - selectedSub = { - subscriptionId: subItem.id, - subscriptionName: subItem.label, - tenantId: subItem.data as string, - }; - } - if (selectedSub === undefined) { - return err( - new SystemError( - "Core", - "NoSubscriptionFound", - getDefaultString("error.NoSubscriptionFound"), - getLocalizedString("error.NoSubscriptionFound") - ) - ); - } - resultSub = selectedSub; - } - return ok(resultSub); -} - -export function getResourceGroupInPortal( - subscriptionId?: string, - tenantId?: string, - resourceGroupName?: string -): string | undefined { - if (subscriptionId && tenantId && resourceGroupName) { - return `${AzurePortalUrl}/#@${tenantId}/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}`; - } else { - return undefined; - } -} - -export function compileHandlebarsTemplateString(templateString: string, context: any): string { - const template = Handlebars.compile(templateString); - return template(context); -} - -export function getResourceGroupNameFromResourceId(resourceId: string): string { - const result = parseFromResourceId(/\/resourceGroups\/([^\/]*)\//i, resourceId); - if (!result) { - throw FailedToParseResourceIdError("resource group name", resourceId); - } - return result; -} - -export function parseFromResourceId(pattern: RegExp, resourceId: string): string { - const result = resourceId.match(pattern); - return result ? result[1].trim() : ""; -} - -export async function waitSeconds(second: number): Promise { - return new Promise((resolve) => setTimeout(resolve, second * 1000)); -} - -export function getUuid(): string { - return uuid.v4(); -} - -export function isSPFxProject(projectSettings?: any): boolean { - const solutionSettings = projectSettings?.solutionSettings; - if (solutionSettings) { - const selectedPlugins = solutionSettings.activeResourcePlugins; - return selectedPlugins && selectedPlugins.indexOf("fx-resource-spfx") !== -1; - } - return false; -} - -export async function isVideoFilterProject(projectPath: string): Promise> { - let manifestResult; - try { - manifestResult = await manifestUtils.readAppManifest(projectPath); - } catch (e) { - return err(assembleError(e)); - } - if (manifestResult.isErr()) { - return err(manifestResult.error); - } - const manifest = manifestResult.value; - return ok( - (manifest.meetingExtensionDefinition as any)?.videoFiltersConfigurationUrl !== undefined - ); -} - -export function getHashedEnv(envName: string): string { - return crypto.createHash("sha256").update(envName).digest("hex"); -} - -export function getAllowedAppMaps(): Record { - return { - [TeamsClientId.MobileDesktop]: getLocalizedString("core.common.TeamsMobileDesktopClientName"), - [TeamsClientId.Web]: getLocalizedString("core.common.TeamsWebClientName"), - [OfficeClientId.Desktop]: getLocalizedString("core.common.OfficeDesktopClientName"), - [OfficeClientId.Web1]: getLocalizedString("core.common.OfficeWebClientName1"), - [OfficeClientId.Web2]: getLocalizedString("core.common.OfficeWebClientName2"), - [OutlookClientId.Desktop]: getLocalizedString("core.common.OutlookDesktopClientName"), - [OutlookClientId.Web1]: getLocalizedString("core.common.OutlookWebClientName1"), - [OutlookClientId.Web2]: getLocalizedString("core.common.OutlookWebClientName2"), - [OutlookClientId.Mobile]: getLocalizedString("core.common.OutlookMobileClientName"), - }; -} - export function getCopilotStatus( token: string, ensureUpToDate = false @@ -247,13 +28,6 @@ export async function getSideloadingStatus(token: string): Promise [`${tenant}/Sites.FullControl.All`]; -export const AzureScopes = ["https://management.core.windows.net/user_impersonation"]; - export async function getSPFxTenant(graphToken: string): Promise { const GRAPH_TENANT_ENDPT = "https://graph.microsoft.com/v1.0/sites/root?$select=webUrl"; if (graphToken.length > 0) { diff --git a/packages/fx-core/src/common/utils.ts b/packages/fx-core/src/common/utils.ts index a8d3a5399b0..582d1382f8d 100644 --- a/packages/fx-core/src/common/utils.ts +++ b/packages/fx-core/src/common/utils.ts @@ -2,10 +2,6 @@ // Licensed under the MIT license. import { getLocalizedString } from "./localizeUtils"; -export function convertToAlphanumericOnly(appName: string): string { - return appName.replace(/[^\da-zA-Z]/g, ""); -} - export function loadingOptionsPlaceholder(): string { return getLocalizedString("ui.select.LoadingOptionsPlaceholder"); } @@ -13,3 +9,7 @@ export function loadingOptionsPlaceholder(): string { export function loadingDefaultPlaceholder(): string { return getLocalizedString("ui.select.LoadingDefaultPlaceholder"); } + +export async function waitSeconds(second: number): Promise { + return new Promise((resolve) => setTimeout(resolve, second * 1000)); +} diff --git a/packages/fx-core/src/component/constants.ts b/packages/fx-core/src/component/constants.ts index 61e022b5611..e083e1f9a97 100644 --- a/packages/fx-core/src/component/constants.ts +++ b/packages/fx-core/src/component/constants.ts @@ -262,9 +262,9 @@ export enum SolutionTelemetrySuccess { No = "no", } -export const SolutionTelemetryComponentName = "solution"; -export const SolutionSource = "Solution"; -export const CoordinatorSource = "coordinator"; +export const SolutionTelemetryComponentName = "core"; +export const SolutionSource = "core"; +export const CoordinatorSource = "core"; export enum Language { JavaScript = "javascript", diff --git a/packages/fx-core/src/component/coordinator/index.ts b/packages/fx-core/src/component/coordinator/index.ts index e91f54de897..c9fef1d86ad 100644 --- a/packages/fx-core/src/component/coordinator/index.ts +++ b/packages/fx-core/src/component/coordinator/index.ts @@ -23,11 +23,11 @@ import { EOL } from "os"; import * as path from "path"; import * as uuid from "uuid"; import * as xml2js from "xml2js"; +import { getResourceGroupInPortal } from "../../common/constants"; import { isNewGeneratorEnabled } from "../../common/featureFlags"; import { getLocalizedString } from "../../common/localizeUtils"; +import { convertToAlphanumericOnly } from "../../common/stringUtils"; import { TelemetryEvent, TelemetryProperty } from "../../common/telemetry"; -import { getResourceGroupInPortal } from "../../common/tools"; -import { convertToAlphanumericOnly } from "../../common/utils"; import { MetadataV3 } from "../../common/versionMetadata"; import { environmentNameManager } from "../../core/environmentName"; import { ObjectIsUndefinedError } from "../../core/error"; @@ -47,9 +47,9 @@ import { MeArchitectureOptions, OfficeAddinHostOptions, ProjectTypeOptions, + QuestionNames, ScratchOptions, -} from "../../question/create"; -import { QuestionNames } from "../../question/questionNames"; +} from "../../question/constants"; import { ExecutionError, ExecutionOutput, ILifecycle } from "../configManager/interface"; import { Lifecycle } from "../configManager/lifecycle"; import { CoordinatorSource } from "../constants"; @@ -60,9 +60,12 @@ import { updateTeamsAppV3ForPublish } from "../driver/teamsApp/appStudio"; import { AppStudioScopes, Constants } from "../driver/teamsApp/constants"; import { CopilotPluginGenerator } from "../generator/copilotPlugin/generator"; import { Generator } from "../generator/generator"; +import { Generators } from "../generator/generatorProvider"; import { OfficeAddinGenerator } from "../generator/officeAddin/generator"; import { OfficeXMLAddinGenerator } from "../generator/officeXMLAddin/generator"; import { SPFxGenerator } from "../generator/spfx/spfxGenerator"; +import { Feature2TemplateName } from "../generator/templates/templateNames"; +import { convertToLangKey } from "../generator/utils"; import { ActionContext, ActionExecutionMW } from "../middleware/actionExecutionMW"; import { provisionUtils } from "../provisionUtils"; import { ResourceGroupInfo, resourceGroupHelper } from "../utils/ResourceGroupHelper"; @@ -71,9 +74,6 @@ import { metadataUtil } from "../utils/metadataUtil"; import { pathUtils } from "../utils/pathUtils"; import { settingsUtil } from "../utils/settingsUtil"; import { SummaryReporter } from "./summary"; -import { Generators } from "../generator/generatorProvider"; -import { Feature2TemplateName } from "../generator/templates/templateNames"; -import { convertToLangKey } from "../generator/utils"; const M365Actions = [ "botAadApp/create", diff --git a/packages/fx-core/src/component/developerPortalScaffoldUtils.ts b/packages/fx-core/src/component/developerPortalScaffoldUtils.ts index 2cf813af2f5..19de54d1e6b 100644 --- a/packages/fx-core/src/component/developerPortalScaffoldUtils.ts +++ b/packages/fx-core/src/component/developerPortalScaffoldUtils.ts @@ -6,14 +6,14 @@ */ import { + BotOrMeScopes, Context, FxError, + ICommandList, IStaticTab, Inputs, Result, TeamsAppManifest, - BotOrMeScopes, - ICommandList, UserError, err, ok, @@ -22,7 +22,8 @@ import fs from "fs-extra"; import * as path from "path"; import { getLocalizedString } from "../common/localizeUtils"; import { ObjectIsUndefinedError } from "../core/error"; -import { CapabilityOptions } from "../question/create"; +import { QuestionNames } from "../question/constants"; +import { getProjectTypeAndCapability } from "../question/create"; import { CoordinatorSource } from "./constants"; import * as appStudio from "./driver/teamsApp/appStudio"; import { @@ -33,15 +34,7 @@ import { } from "./driver/teamsApp/constants"; import { AppDefinition } from "./driver/teamsApp/interfaces/appdefinitions/appDefinition"; import { manifestUtils } from "./driver/teamsApp/utils/ManifestUtils"; -import { - isBot, - isBotAndBotBasedMessageExtension, - isBotBasedMessageExtension, - needTabAndBotCode, - needTabCode, -} from "./driver/teamsApp/utils/utils"; import { envUtil } from "./utils/envUtil"; -import { QuestionNames } from "../question/questionNames"; const appPackageFolderName = "appPackage"; const colorFileName = "color.png"; @@ -325,43 +318,8 @@ function findTabBasedOnName(name: string, tabs: IStaticTab[]): IStaticTab | unde return tabs.find((o) => o.name === name); } -export function getProjectTypeAndCapability( - teamsApp: AppDefinition -): { projectType: string; templateId: string } | undefined { - // tab with bot, tab with message extension, tab with bot and message extension - if (needTabAndBotCode(teamsApp)) { - return { projectType: "tab-bot-type", templateId: CapabilityOptions.nonSsoTabAndBot().id }; - } - - // tab only - if (needTabCode(teamsApp)) { - return { projectType: "tab-type", templateId: CapabilityOptions.nonSsoTab().id }; - } - - // bot and message extension - if (isBotAndBotBasedMessageExtension(teamsApp)) { - return { projectType: "bot-me-type", templateId: CapabilityOptions.botAndMe().id }; - } - - // bot based message extension - if (isBotBasedMessageExtension(teamsApp)) { - return { projectType: "me-type", templateId: CapabilityOptions.me().id }; - } - - // bot - if (isBot(teamsApp)) { - return { projectType: "bot-type", templateId: CapabilityOptions.basicBot().id }; - } - - return undefined; -} - function decapitalizeScope(scopes: string[]): string[] { return scopes.map((o) => o.toLowerCase()); } -export function isFromDevPortal(inputs: Inputs | undefined): boolean { - return !!inputs?.teamsAppFromTdp; -} - export const developerPortalScaffoldUtils = new DeveloperPortalScaffoldUtils(); diff --git a/packages/fx-core/src/component/driver/aad/create.ts b/packages/fx-core/src/component/driver/aad/create.ts index 4b4b7f2a066..be7951c1ec5 100644 --- a/packages/fx-core/src/component/driver/aad/create.ts +++ b/packages/fx-core/src/component/driver/aad/create.ts @@ -5,8 +5,8 @@ import { hooks } from "@feathersjs/hooks/lib"; import { M365TokenProvider, SystemError, UserError, err, ok } from "@microsoft/teamsfx-api"; import axios from "axios"; import { Service } from "typedi"; +import { GraphScopes } from "../../../common/constants"; import { getLocalizedString } from "../../../common/localizeUtils"; -import { GraphScopes } from "../../../common/tools"; import { HttpClientError, HttpServerError, @@ -18,15 +18,19 @@ import { DriverContext } from "../interface/commonArgs"; import { ExecutionResult, StepDriver } from "../interface/stepDriver"; import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; import { loadStateFromEnv, mapStateToEnv } from "../util/utils"; +import { WrapDriverContext } from "../util/wrapUtil"; import { AadAppNameTooLongError } from "./error/aadAppNameTooLongError"; import { MissingEnvUserError } from "./error/missingEnvError"; import { CreateAadAppArgs } from "./interface/createAadAppArgs"; import { CreateAadAppOutput, OutputKeys } from "./interface/createAadAppOutput"; import { SignInAudience } from "./interface/signInAudience"; import { AadAppClient } from "./utility/aadAppClient"; -import { constants, descriptionMessageKeys, logMessageKeys } from "./utility/constants"; -import { WrapDriverContext } from "../util/wrapUtil"; -import { telemetryKeys } from "./utility/constants"; +import { + constants, + descriptionMessageKeys, + logMessageKeys, + telemetryKeys, +} from "./utility/constants"; const actionName = "aadApp/create"; // DO NOT MODIFY the name const helpLink = "https://aka.ms/teamsfx-actions/aadapp-create"; diff --git a/packages/fx-core/src/component/driver/aad/utility/aadAppClient.ts b/packages/fx-core/src/component/driver/aad/utility/aadAppClient.ts index af02e95ac48..639eaf4fedd 100644 --- a/packages/fx-core/src/component/driver/aad/utility/aadAppClient.ts +++ b/packages/fx-core/src/component/driver/aad/utility/aadAppClient.ts @@ -5,22 +5,22 @@ import { hooks } from "@feathersjs/hooks/lib"; import { LogProvider, M365TokenProvider } from "@microsoft/teamsfx-api"; import axios, { AxiosError, AxiosInstance, AxiosRequestHeaders } from "axios"; import axiosRetry, { IAxiosRetryConfig } from "axios-retry"; +import { GraphScopes } from "../../../../common/constants"; import { getLocalizedString } from "../../../../common/localizeUtils"; import { AadOwner } from "../../../../common/permissionInterface"; -import { GraphScopes } from "../../../../common/tools"; import { ErrorContextMW } from "../../../../core/globalVars"; import { DeleteOrUpdatePermissionFailedError, HostNameNotOnVerifiedDomainError, } from "../error/aadManifestError"; +import { ClientSecretNotAllowedError } from "../error/clientSecretNotAllowedError"; +import { CredentialInvalidLifetimeError } from "../error/credentialInvalidLifetimeError"; import { AADApplication } from "../interface/AADApplication"; import { AADManifest } from "../interface/AADManifest"; import { IAADDefinition } from "../interface/IAADDefinition"; import { SignInAudience } from "../interface/signInAudience"; import { AadManifestHelper } from "./aadManifestHelper"; -import { aadErrorCode, constants } from "./constants"; -import { CredentialInvalidLifetimeError } from "../error/credentialInvalidLifetimeError"; -import { ClientSecretNotAllowedError } from "../error/clientSecretNotAllowedError"; +import { aadErrorCode } from "./constants"; // Another implementation of src\component\resource\aadApp\graph.ts to reduce call stacks // It's our internal utility so make sure pass valid parameters to it instead of relying on it to handle parameter errors diff --git a/packages/fx-core/src/component/driver/aad/utility/buildAadManifest.ts b/packages/fx-core/src/component/driver/aad/utility/buildAadManifest.ts index a1e733c7bca..5e022adc91e 100644 --- a/packages/fx-core/src/component/driver/aad/utility/buildAadManifest.ts +++ b/packages/fx-core/src/component/driver/aad/utility/buildAadManifest.ts @@ -1,23 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { UpdateAadAppOutput } from "../interface/updateAadAppOutput"; import * as fs from "fs-extra"; import * as path from "path"; -import { AadManifestHelper } from "./aadManifestHelper"; -import { MissingFieldInManifestUserError } from "../error/invalidFieldInManifestError"; import isUUID from "validator/lib/isUUID"; import { getLocalizedString } from "../../../../common/localizeUtils"; -import { logMessageKeys } from "../utility/constants"; -import { DriverContext } from "../../interface/commonArgs"; -import { AADManifest } from "../interface/AADManifest"; -import { expandEnvironmentVariable, getEnvironmentVariables } from "../../../utils/common"; -import { getUuid } from "../../../../common/tools"; +import { getUuid } from "../../../../common/stringUtils"; import { FileNotFoundError, JSONSyntaxError, MissingEnvironmentVariablesError, } from "../../../../error/common"; +import { expandEnvironmentVariable, getEnvironmentVariables } from "../../../utils/common"; +import { DriverContext } from "../../interface/commonArgs"; +import { MissingFieldInManifestUserError } from "../error/invalidFieldInManifestError"; +import { AADManifest } from "../interface/AADManifest"; +import { UpdateAadAppOutput } from "../interface/updateAadAppOutput"; +import { logMessageKeys } from "../utility/constants"; +import { AadManifestHelper } from "./aadManifestHelper"; const actionName = "aadApp/update"; // DO NOT MODIFY the name const helpLink = "https://aka.ms/teamsfx-actions/aadapp-update"; diff --git a/packages/fx-core/src/component/driver/add/addWebPart.ts b/packages/fx-core/src/component/driver/add/addWebPart.ts index 221bcdf57b8..f55839464ae 100644 --- a/packages/fx-core/src/component/driver/add/addWebPart.ts +++ b/packages/fx-core/src/component/driver/add/addWebPart.ts @@ -1,26 +1,26 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Result, FxError, IStaticTab, Inputs, Stage } from "@microsoft/teamsfx-api"; import { hooks } from "@feathersjs/hooks/lib"; +import { FxError, IStaticTab, Inputs, Result, Stage } from "@microsoft/teamsfx-api"; +import * as fs from "fs-extra"; +import path from "path"; import { Service } from "typedi"; -import { StepDriver, ExecutionResult } from "../interface/stepDriver"; +import * as util from "util"; +import { getLocalizedString } from "../../../common/localizeUtils"; +import { QuestionNames } from "../../../question/constants"; +import { SPFxGenerator } from "../../generator/spfx/spfxGenerator"; +import { ManifestTemplate } from "../../generator/spfx/utils/constants"; +import { createContextV3 } from "../../utils"; +import { wrapRun } from "../../utils/common"; import { DriverContext } from "../interface/commonArgs"; -import { WrapDriverContext } from "../util/wrapUtil"; +import { ExecutionResult, StepDriver } from "../interface/stepDriver"; import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; import { manifestUtils } from "../teamsApp/utils/ManifestUtils"; -import { getLocalizedString } from "../../../common/localizeUtils"; -import { wrapRun } from "../../utils/common"; +import { WrapDriverContext } from "../util/wrapUtil"; +import { NoConfigurationError } from "./error/noConfigurationError"; import { AddWebPartArgs } from "./interface/AddWebPartArgs"; -import path from "path"; -import * as fs from "fs-extra"; -import * as util from "util"; -import { ManifestTemplate } from "../../generator/spfx/utils/constants"; -import { SPFxGenerator } from "../../generator/spfx/spfxGenerator"; -import { createContextV3 } from "../../utils"; import { Constants } from "./utility/constants"; -import { NoConfigurationError } from "./error/noConfigurationError"; -import { QuestionNames } from "../../../question/questionNames"; @Service(Constants.ActionName) export class AddWebPartDriver implements StepDriver { diff --git a/packages/fx-core/src/component/driver/apiKey/create.ts b/packages/fx-core/src/component/driver/apiKey/create.ts index bffe1c39781..f175dacfc6f 100644 --- a/packages/fx-core/src/component/driver/apiKey/create.ts +++ b/packages/fx-core/src/component/driver/apiKey/create.ts @@ -5,9 +5,8 @@ import { hooks } from "@feathersjs/hooks"; import { M365TokenProvider, SystemError, UserError, err, ok } from "@microsoft/teamsfx-api"; import { Service } from "typedi"; import { getLocalizedString } from "../../../common/localizeUtils"; -import { AppStudioScopes, GraphScopes } from "../../../common/tools"; import { InvalidActionInputError, assembleError } from "../../../error"; -import { QuestionNames } from "../../../question"; +import { QuestionNames } from "../../../question/constants"; import { QuestionMW } from "../../middleware/questionMW"; import { OutputEnvironmentVariableUndefinedError } from "../error/outputEnvironmentVariableUndefinedError"; import { DriverContext } from "../interface/commonArgs"; @@ -27,6 +26,8 @@ import { CreateApiKeyArgs } from "./interface/createApiKeyArgs"; import { CreateApiKeyOutputs, OutputKeys } from "./interface/createApiKeyOutputs"; import { logMessageKeys, maxSecretLength, minSecretLength } from "./utility/constants"; import { getDomain, loadStateFromEnv, validateDomain } from "./utility/utility"; +import { AppStudioScopes } from "../teamsApp/constants"; +import { GraphScopes } from "../../../common/constants"; const actionName = "apiKey/register"; // DO NOT MODIFY the name const helpLink = "https://aka.ms/teamsfx-actions/apiKey-register"; diff --git a/packages/fx-core/src/component/driver/arm/util/bicepChecker.ts b/packages/fx-core/src/component/driver/arm/util/bicepChecker.ts index 1162003543e..9903e070af8 100644 --- a/packages/fx-core/src/component/driver/arm/util/bicepChecker.ts +++ b/packages/fx-core/src/component/driver/arm/util/bicepChecker.ts @@ -6,9 +6,11 @@ import * as path from "path"; import * as os from "os"; import { ConfigFolderName, + FxError, LogProvider, SystemError, TelemetryReporter, + UserError, } from "@microsoft/teamsfx-api"; import * as fs from "fs-extra"; import { cpUtils } from "../../../utils/depsChecker/cpUtils"; @@ -26,7 +28,6 @@ import { } from "../../../constants"; import { performance } from "perf_hooks"; -import { sendErrorTelemetryThenReturnError } from "../../../utils"; import { DriverContext } from "../../interface/commonArgs"; import { InstallSoftwareError } from "../../../../error/common"; import { DownloadBicepCliError } from "../../../../error/arm"; @@ -301,3 +302,33 @@ function getCommonProps(): { [key: string]: string } { properties[SolutionTelemetryProperty.Success] = SolutionTelemetrySuccess.Yes; return properties; } + +function sendErrorTelemetryThenReturnError( + eventName: string, + error: FxError, + reporter?: TelemetryReporter, + properties?: { [p: string]: string }, + measurements?: { [p: string]: number }, + errorProps?: string[] +): FxError { + if (!properties) { + properties = {}; + } + + if (SolutionTelemetryProperty.Component in properties === false) { + properties[SolutionTelemetryProperty.Component] = SolutionTelemetryComponentName; + } + + properties[SolutionTelemetryProperty.Success] = "no"; + if (error instanceof UserError) { + properties["error-type"] = "user"; + } else { + properties["error-type"] = "system"; + } + + properties["error-code"] = `${error.source}.${error.name}`; + properties["error-message"] = error.message; + + reporter?.sendTelemetryErrorEvent(eventName, properties, measurements, errorProps); + return error; +} diff --git a/packages/fx-core/src/component/driver/arm/util/handleError.ts b/packages/fx-core/src/component/driver/arm/util/handleError.ts index 04c111d29fc..3b0181a1f0b 100644 --- a/packages/fx-core/src/component/driver/arm/util/handleError.ts +++ b/packages/fx-core/src/component/driver/arm/util/handleError.ts @@ -4,7 +4,7 @@ import { ResourceManagementClient } from "@azure/arm-resources"; import { Context, err, FxError, ok, Result } from "@microsoft/teamsfx-api"; import { ConstantString } from "../../../../common/constants"; -import { getResourceGroupNameFromResourceId } from "../../../../common/tools"; +import { getResourceGroupNameFromResourceId } from "../../../../common/stringUtils"; import { ResourceGroupNotExistError } from "../../../../error/azure"; import { DeployArmError, GetArmDeploymentError } from "../../../../error/arm"; import { innerGetDeploymentError, innerGetDeploymentOperations } from "./innerHandleError"; diff --git a/packages/fx-core/src/component/driver/deploy/azure/impl/azureDeployImpl.ts b/packages/fx-core/src/component/driver/deploy/azure/impl/azureDeployImpl.ts index bea6ee672ec..c75dca89108 100644 --- a/packages/fx-core/src/component/driver/deploy/azure/impl/azureDeployImpl.ts +++ b/packages/fx-core/src/component/driver/deploy/azure/impl/azureDeployImpl.ts @@ -1,40 +1,40 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - DeployStepArgs, - AzureUploadConfig, - DeployArgs, - AxiosDeployQueryResult, - DeployResult, -} from "../../../interface/buildAndDeployArgs"; -import { checkMissingArgs } from "../../../../utils/common"; -import { LogProvider } from "@microsoft/teamsfx-api"; -import { BaseDeployImpl } from "./baseDeployImpl"; -import { Base64 } from "js-base64"; import * as appService from "@azure/arm-appservice"; -import { DeployConstant, DeployStatus } from "../../../../constant/deployConstant"; -import { default as axios } from "axios"; -import { waitSeconds } from "../../../../../common/tools"; -import { HttpStatusCode } from "../../../../constant/commonConstant"; -import { - getAzureAccountCredential, - parseAzureResourceId, -} from "../../../../utils/azureResourceOperation"; -import { AzureResourceInfo } from "../../../interface/commonArgs"; import { TokenCredential } from "@azure/identity"; +import { hooks } from "@feathersjs/hooks"; +import { LogProvider } from "@microsoft/teamsfx-api"; +import { default as axios } from "axios"; import * as fs from "fs-extra"; -import { PrerequisiteError } from "../../../../error/componentError"; -import { wrapAzureOperation } from "../../../../utils/azureSdkErrorHandler"; +import { Base64 } from "js-base64"; +import path from "path"; import { getDefaultString, getLocalizedString } from "../../../../../common/localizeUtils"; +import { waitSeconds } from "../../../../../common/utils"; +import { ErrorContextMW } from "../../../../../core/globalVars"; import { CheckDeploymentStatusError, CheckDeploymentStatusTimeoutError, GetPublishingCredentialsError, } from "../../../../../error"; -import { hooks } from "@feathersjs/hooks"; -import { ErrorContextMW } from "../../../../../core/globalVars"; -import path from "path"; +import { HttpStatusCode } from "../../../../constant/commonConstant"; +import { DeployConstant, DeployStatus } from "../../../../constant/deployConstant"; +import { PrerequisiteError } from "../../../../error/componentError"; +import { + getAzureAccountCredential, + parseAzureResourceId, +} from "../../../../utils/azureResourceOperation"; +import { wrapAzureOperation } from "../../../../utils/azureSdkErrorHandler"; +import { checkMissingArgs } from "../../../../utils/common"; +import { + AxiosDeployQueryResult, + AzureUploadConfig, + DeployArgs, + DeployResult, + DeployStepArgs, +} from "../../../interface/buildAndDeployArgs"; +import { AzureResourceInfo } from "../../../interface/commonArgs"; +import { BaseDeployImpl } from "./baseDeployImpl"; export abstract class AzureDeployImpl extends BaseDeployImpl { protected managementClient: appService.WebSiteManagementClient | undefined; diff --git a/packages/fx-core/src/component/driver/deploy/spfx/deployDriver.ts b/packages/fx-core/src/component/driver/deploy/spfx/deployDriver.ts index 75a678022b6..99c8cd745fa 100644 --- a/packages/fx-core/src/component/driver/deploy/spfx/deployDriver.ts +++ b/packages/fx-core/src/component/driver/deploy/spfx/deployDriver.ts @@ -2,14 +2,16 @@ // Licensed under the MIT license. import { hooks } from "@feathersjs/hooks/lib"; -import { Result, FxError, Platform, M365TokenProvider } from "@microsoft/teamsfx-api"; +import { FxError, M365TokenProvider, Platform, Result } from "@microsoft/teamsfx-api"; import axios from "axios"; import fs from "fs-extra"; import path from "path"; import { Service } from "typedi"; +import { GraphScopes } from "../../../../common/constants"; import { getLocalizedString } from "../../../../common/localizeUtils"; -import { getSPFxToken, GraphScopes } from "../../../../common/tools"; +import { getSPFxToken } from "../../../../common/tools"; +import { ErrorContextMW } from "../../../../core/globalVars"; import { FileNotFoundError } from "../../../../error/common"; import { asBoolean, asFactory, asString, wrapRun } from "../../../utils/common"; import { DriverContext } from "../../interface/commonArgs"; @@ -28,7 +30,6 @@ import { DeploySPFxArgs } from "./interface/deployArgs"; import { Constants, DeployProgressMessage } from "./utility/constants"; import { sleep } from "./utility/sleep"; import { SPOClient } from "./utility/spoClient"; -import { ErrorContextMW } from "../../../../core/globalVars"; @Service(Constants.DeployDriverName) export class SPFxDeployDriver implements StepDriver { diff --git a/packages/fx-core/src/component/driver/oauth/create.ts b/packages/fx-core/src/component/driver/oauth/create.ts index 69ae3b6f957..23987e29bd5 100644 --- a/packages/fx-core/src/component/driver/oauth/create.ts +++ b/packages/fx-core/src/component/driver/oauth/create.ts @@ -2,31 +2,30 @@ // Licensed under the MIT license. import { hooks } from "@feathersjs/hooks"; -import { ExecutionResult, StepDriver } from "../interface/stepDriver"; -import { getLocalizedString } from "../../../common/localizeUtils"; -import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; -import { CreateOauthArgs } from "./interface/createOauthArgs"; -import { DriverContext } from "../interface/commonArgs"; import { M365TokenProvider, SystemError, UserError, err, ok } from "@microsoft/teamsfx-api"; +import { Service } from "typedi"; +import { getLocalizedString } from "../../../common/localizeUtils"; +import { GraphScopes } from "../../../common/constants"; import { InvalidActionInputError, assembleError } from "../../../error/common"; -import { logMessageKeys, maxSecretLength, minSecretLength } from "./utility/constants"; +import { QuestionNames } from "../../../question/constants"; +import { QuestionMW } from "../../middleware/questionMW"; import { OutputEnvironmentVariableUndefinedError } from "../error/outputEnvironmentVariableUndefinedError"; -import { CreateOauthOutputs, OutputKeys } from "./interface/createOauthOutputs"; -import { loadStateFromEnv } from "../util/utils"; -import { AppStudioScopes } from "../teamsApp/constants"; +import { DriverContext } from "../interface/commonArgs"; +import { ExecutionResult, StepDriver } from "../interface/stepDriver"; +import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; import { AppStudioClient } from "../teamsApp/clients/appStudioClient"; +import { AppStudioScopes } from "../teamsApp/constants"; import { + OauthRegistration, OauthRegistrationAppType, OauthRegistrationTargetAudience, - OauthRegistration, - OauthRegistrationUserAccessType, } from "../teamsApp/interfaces/OauthRegistration"; +import { loadStateFromEnv } from "../util/utils"; import { OauthNameTooLongError } from "./error/oauthNameTooLong"; -import { GraphScopes } from "../../../common/tools"; +import { CreateOauthArgs } from "./interface/createOauthArgs"; +import { CreateOauthOutputs, OutputKeys } from "./interface/createOauthOutputs"; +import { logMessageKeys, maxSecretLength, minSecretLength } from "./utility/constants"; import { OauthInfo, getandValidateOauthInfoFromSpec } from "./utility/utility"; -import { QuestionMW } from "../../middleware/questionMW"; -import { QuestionNames } from "../../../question/questionNames"; -import { Service } from "typedi"; const actionName = "oauth/register"; // DO NOT MODIFY the name const helpLink = "https://aka.ms/teamsfx-actions/oauth-register"; diff --git a/packages/fx-core/src/component/driver/teamsApp/appStudio.ts b/packages/fx-core/src/component/driver/teamsApp/appStudio.ts index 569d82872a0..c577f128bec 100644 --- a/packages/fx-core/src/component/driver/teamsApp/appStudio.ts +++ b/packages/fx-core/src/component/driver/teamsApp/appStudio.ts @@ -3,47 +3,47 @@ import { AppPackageFolderName, BuildFolderName, - err, + Colors, + Context, FxError, InputsWithProjectPath, + LogProvider, M365TokenProvider, - ok, + ManifestUtil, + Platform, Result, TeamsAppManifest, UserError, - LogProvider, - Platform, - Colors, - ManifestUtil, - Context, + err, + ok, } from "@microsoft/teamsfx-api"; import AdmZip from "adm-zip"; import fs from "fs-extra"; -import * as path from "path"; import _ from "lodash"; +import set from "lodash/set"; +import * as path from "path"; +import { basename, extname } from "path"; +import { Container } from "typedi"; import * as util from "util"; import isUUID from "validator/lib/isUUID"; -import { Container } from "typedi"; -import { AppStudioScopes } from "../../../common/tools"; +import { getDefaultString, getLocalizedString } from "../../../common/localizeUtils"; +import { FileNotFoundError, UserCancelError } from "../../../error/common"; +import { QuestionNames } from "../../../question/constants"; +import { envUtil } from "../../utils/envUtil"; +import { DriverContext } from "../interface/commonArgs"; import { AppStudioClient } from "./clients/appStudioClient"; +import { ConfigureTeamsAppDriver, actionName as configureTeamsAppActionName } from "./configure"; +import { AppStudioScopes, Constants, supportedLanguageCodes } from "./constants"; +import { + CreateAppPackageDriver, + actionName as createAppPackageActionName, +} from "./createAppPackage"; import { AppStudioError } from "./errors"; -import { AppStudioResultFactory } from "./results"; -import { getDefaultString, getLocalizedString } from "../../../common/localizeUtils"; -import { manifestUtils } from "./utils/ManifestUtils"; -import { Constants, supportedLanguageCodes } from "./constants"; -import { CreateAppPackageDriver } from "./createAppPackage"; -import { ConfigureTeamsAppDriver } from "./configure"; -import { CreateAppPackageArgs } from "./interfaces/CreateAppPackageArgs"; import { ConfigureTeamsAppArgs } from "./interfaces/ConfigureTeamsAppArgs"; -import { DriverContext } from "../interface/commonArgs"; -import { envUtil } from "../../utils/envUtil"; +import { CreateAppPackageArgs } from "./interfaces/CreateAppPackageArgs"; import { AppPackage } from "./interfaces/appdefinitions/appPackage"; -import { basename, extname } from "path"; -import set from "lodash/set"; -import { actionName as createAppPackageActionName } from "./createAppPackage"; -import { actionName as configureTeamsAppActionName } from "./configure"; -import { FileNotFoundError, UserCancelError } from "../../../error/common"; -import { QuestionNames } from "../../../question"; +import { AppStudioResultFactory } from "./results"; +import { manifestUtils } from "./utils/ManifestUtils"; export async function checkIfAppInDifferentAcountSameTenant( teamsAppId: string, diff --git a/packages/fx-core/src/component/driver/teamsApp/clients/appStudioClient.ts b/packages/fx-core/src/component/driver/teamsApp/clients/appStudioClient.ts index 9d7d1eaf46f..c3384b35436 100644 --- a/packages/fx-core/src/component/driver/teamsApp/clients/appStudioClient.ts +++ b/packages/fx-core/src/component/driver/teamsApp/clients/appStudioClient.ts @@ -28,7 +28,7 @@ import { TelemetryEvent, TelemetryProperty, } from "../../../../common/telemetry"; -import { waitSeconds } from "../../../../common/tools"; +import { waitSeconds } from "../../../../common/utils"; import { IValidationResult } from "../../../driver/teamsApp/interfaces/appdefinitions/IValidationResult"; import { HttpStatusCode } from "../../../constant/commonConstant"; import { manifestUtils } from "../utils/ManifestUtils"; diff --git a/packages/fx-core/src/component/driver/teamsApp/configure.ts b/packages/fx-core/src/component/driver/teamsApp/configure.ts index 9cdc5e1a96f..7e613c00ff1 100644 --- a/packages/fx-core/src/component/driver/teamsApp/configure.ts +++ b/packages/fx-core/src/component/driver/teamsApp/configure.ts @@ -1,25 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { FxError, Result, err, ok, ManifestUtil } from "@microsoft/teamsfx-api"; -import fs from "fs-extra"; import { hooks } from "@feathersjs/hooks/lib"; -import isUUID from "validator/lib/isUUID"; +import { FxError, ManifestUtil, Result, err, ok } from "@microsoft/teamsfx-api"; +import fs from "fs-extra"; import { merge } from "lodash"; -import { StepDriver, ExecutionResult } from "../interface/stepDriver"; +import { Service } from "typedi"; +import isUUID from "validator/lib/isUUID"; +import { getLocalizedString } from "../../../common/localizeUtils"; +import { FileNotFoundError, InvalidActionInputError } from "../../../error/common"; +import { getAbsolutePath } from "../../utils/common"; import { DriverContext } from "../interface/commonArgs"; -import { WrapDriverContext } from "../util/wrapUtil"; -import { ConfigureTeamsAppArgs } from "./interfaces/ConfigureTeamsAppArgs"; +import { ExecutionResult, StepDriver } from "../interface/stepDriver"; import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; +import { WrapDriverContext } from "../util/wrapUtil"; import { AppStudioClient } from "./clients/appStudioClient"; +import { AppStudioScopes } from "./constants"; +import { AppStudioError } from "./errors"; +import { ConfigureTeamsAppArgs } from "./interfaces/ConfigureTeamsAppArgs"; import { AppStudioResultFactory } from "./results"; import { manifestUtils } from "./utils/ManifestUtils"; -import { AppStudioError } from "./errors"; -import { AppStudioScopes } from "../../../common/tools"; -import { getLocalizedString } from "../../../common/localizeUtils"; -import { Service } from "typedi"; -import { getAbsolutePath } from "../../utils/common"; -import { FileNotFoundError, InvalidActionInputError } from "../../../error/common"; export const actionName = "teamsApp/update"; diff --git a/packages/fx-core/src/component/driver/teamsApp/create.ts b/packages/fx-core/src/component/driver/teamsApp/create.ts index 0312e301071..586a2f6634a 100644 --- a/packages/fx-core/src/component/driver/teamsApp/create.ts +++ b/packages/fx-core/src/component/driver/teamsApp/create.ts @@ -30,9 +30,9 @@ import { DEFAULT_OUTLINE_PNG_FILENAME, COLOR_TEMPLATE, OUTLINE_TEMPLATE, + AppStudioScopes, } from "./constants"; import { AppDefinition } from "../../driver/teamsApp/interfaces/appdefinitions/appDefinition"; -import { AppStudioScopes } from "../../../common/tools"; import { getLocalizedString } from "../../../common/localizeUtils"; import { getTemplatesFolder } from "../../../folder"; import { InvalidActionInputError } from "../../../error/common"; diff --git a/packages/fx-core/src/component/driver/teamsApp/publishAppPackage.ts b/packages/fx-core/src/component/driver/teamsApp/publishAppPackage.ts index 297215d9b1e..5e5dd27f5a8 100644 --- a/packages/fx-core/src/component/driver/teamsApp/publishAppPackage.ts +++ b/packages/fx-core/src/component/driver/teamsApp/publishAppPackage.ts @@ -1,24 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { FxError, Result, err, ok, TeamsAppManifest, Platform } from "@microsoft/teamsfx-api"; -import fs from "fs-extra"; +import { hooks } from "@feathersjs/hooks/lib"; +import { FxError, Platform, Result, TeamsAppManifest, err, ok } from "@microsoft/teamsfx-api"; import AdmZip from "adm-zip"; +import fs from "fs-extra"; import { merge } from "lodash"; -import { hooks } from "@feathersjs/hooks/lib"; -import { StepDriver, ExecutionResult } from "../interface/stepDriver"; +import { Service } from "typedi"; +import { getLocalizedString } from "../../../common/localizeUtils"; +import { FileNotFoundError, InvalidActionInputError, UserCancelError } from "../../../error/common"; +import { getAbsolutePath } from "../../utils/common"; import { DriverContext } from "../interface/commonArgs"; -import { WrapDriverContext } from "../util/wrapUtil"; +import { ExecutionResult, StepDriver } from "../interface/stepDriver"; import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; -import { PublishAppPackageArgs } from "./interfaces/PublishAppPackageArgs"; +import { WrapDriverContext } from "../util/wrapUtil"; import { AppStudioClient } from "./clients/appStudioClient"; -import { Constants } from "./constants"; +import { AppStudioScopes, Constants } from "./constants"; +import { PublishAppPackageArgs } from "./interfaces/PublishAppPackageArgs"; import { TelemetryPropertyKey } from "./utils/telemetry"; -import { AppStudioScopes } from "../../../common/tools"; -import { getLocalizedString } from "../../../common/localizeUtils"; -import { Service } from "typedi"; -import { getAbsolutePath } from "../../utils/common"; -import { FileNotFoundError, InvalidActionInputError, UserCancelError } from "../../../error/common"; export const actionName = "teamsApp/publishAppPackage"; diff --git a/packages/fx-core/src/component/driver/teamsApp/teamsappMgr.ts b/packages/fx-core/src/component/driver/teamsApp/teamsappMgr.ts index f54293f482f..421fc119d4c 100644 --- a/packages/fx-core/src/component/driver/teamsApp/teamsappMgr.ts +++ b/packages/fx-core/src/component/driver/teamsApp/teamsappMgr.ts @@ -15,15 +15,14 @@ import * as path from "path"; import { Container } from "typedi"; import * as util from "util"; import { getLocalizedString } from "../../../common/localizeUtils"; -import { AppStudioScopes } from "../../../common/tools"; import { FileNotFoundError, MissingRequiredInputError } from "../../../error/common"; import { resolveString } from "../../configManager/lifecycle"; -import { createDriverContext } from "../../utils"; import { envUtil } from "../../utils/envUtil"; import { pathUtils } from "../../utils/pathUtils"; import { DriverContext } from "../interface/commonArgs"; +import { createDriverContext } from "../util/utils"; import { ConfigureTeamsAppDriver, actionName as configureTeamsAppActionName } from "./configure"; -import { Constants } from "./constants"; +import { AppStudioScopes, Constants } from "./constants"; import { CreateAppPackageDriver, actionName as createAppPackageActionName, @@ -33,6 +32,7 @@ import { CreateAppPackageArgs } from "./interfaces/CreateAppPackageArgs"; import { PublishAppPackageArgs } from "./interfaces/PublishAppPackageArgs"; import { ValidateAppPackageArgs } from "./interfaces/ValidateAppPackageArgs"; import { ValidateManifestArgs } from "./interfaces/ValidateManifestArgs"; +import { ValidateWithTestCasesArgs } from "./interfaces/ValidateWithTestCasesArgs"; import { actionName as PublishAppPackageActionName, PublishAppPackageDriver, @@ -40,7 +40,6 @@ import { import { manifestUtils } from "./utils/ManifestUtils"; import { ValidateManifestDriver } from "./validate"; import { ValidateAppPackageDriver } from "./validateAppPackage"; -import { ValidateWithTestCasesArgs } from "./interfaces/ValidateWithTestCasesArgs"; import { ValidateWithTestCasesDriver } from "./validateTestCases"; class TeamsAppMgr { diff --git a/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts b/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts index 8ff4bd31c31..1f00e25c952 100644 --- a/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts +++ b/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { hooks } from "@feathersjs/hooks"; import { FxError, + IComposeExtension, + IMessagingExtensionCommand, InputsWithProjectPath, ManifestCapability, Result, TeamsAppManifest, - IComposeExtension, - IMessagingExtensionCommand, err, ok, } from "@microsoft/teamsfx-api"; @@ -19,15 +20,14 @@ import "reflect-metadata"; import stripBom from "strip-bom"; import { v4 } from "uuid"; import isUUID from "validator/lib/isUUID"; -import { - FileNotFoundError, - JSONSyntaxError, - MissingEnvironmentVariablesError, -} from "../../../../error/common"; -import { CapabilityOptions } from "../../../../question/create"; +import { getCapabilities as checkManifestCapabilities } from "../../../../common/projectTypeChecker"; +import { ErrorContextMW } from "../../../../core/globalVars"; +import { FileNotFoundError, JSONSyntaxError } from "../../../../error/common"; +import { CapabilityOptions } from "../../../../question/constants"; import { BotScenario } from "../../../constants"; import { convertManifestTemplateToV2, convertManifestTemplateToV3 } from "../../../migrate"; -import { expandEnvironmentVariable, getEnvironmentVariables } from "../../../utils/common"; +import { expandEnvironmentVariable } from "../../../utils/common"; +import { WrapDriverContext } from "../../util/wrapUtil"; import { BOTS_TPL_EXISTING_APP, BOTS_TPL_FOR_COMMAND_AND_RESPONSE_V3, @@ -47,10 +47,6 @@ import { import { AppStudioError } from "../errors"; import { AppStudioResultFactory } from "../results"; import { TelemetryPropertyKey } from "./telemetry"; -import { WrapDriverContext } from "../../util/wrapUtil"; -import { hooks } from "@feathersjs/hooks"; -import { ErrorContextMW } from "../../../../core/globalVars"; -import { getCapabilities as checkManifestCapabilities } from "../../../../common/projectTypeChecker"; import { getResolvedManifest } from "./utils"; export class ManifestUtils { diff --git a/packages/fx-core/src/component/driver/teamsApp/utils/utils.ts b/packages/fx-core/src/component/driver/teamsApp/utils/utils.ts index 1507c7a7aed..852d500693e 100644 --- a/packages/fx-core/src/component/driver/teamsApp/utils/utils.ts +++ b/packages/fx-core/src/component/driver/teamsApp/utils/utils.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import { includes } from "lodash"; import Mustache from "mustache"; -import { TEAMS_APP_SHORT_NAME_MAX_LENGTH } from ".././constants"; import { AppDefinition } from "../interfaces/appdefinitions/appDefinition"; import { ConfigurableTab } from "../interfaces/appdefinitions/configurableTab"; import { expandEnvironmentVariable, getEnvironmentVariables } from "../../../utils/common"; diff --git a/packages/fx-core/src/component/driver/teamsApp/validateAppPackage.ts b/packages/fx-core/src/component/driver/teamsApp/validateAppPackage.ts index 6dc4f23eb78..cc731169b9e 100644 --- a/packages/fx-core/src/component/driver/teamsApp/validateAppPackage.ts +++ b/packages/fx-core/src/component/driver/teamsApp/validateAppPackage.ts @@ -5,39 +5,38 @@ * @author Ning Liu */ +import { hooks } from "@feathersjs/hooks/lib"; import { - Result, - FxError, - ok, - err, - TeamsAppManifest, - Platform, Colors, + FxError, LogLevel, ManifestUtil, + Platform, + Result, + TeamsAppManifest, + err, + ok, } from "@microsoft/teamsfx-api"; -import { hooks } from "@feathersjs/hooks/lib"; -import { Service } from "typedi"; +import AdmZip from "adm-zip"; import fs from "fs-extra"; -import * as path from "path"; -import { EOL } from "os"; import { merge } from "lodash"; -import { StepDriver, ExecutionResult } from "../interface/stepDriver"; +import { EOL } from "os"; +import * as path from "path"; +import { Service } from "typedi"; +import { getDefaultString, getLocalizedString } from "../../../common/localizeUtils"; +import { FileNotFoundError, InvalidActionInputError } from "../../../error/common"; +import { SummaryConstant } from "../../configManager/constant"; +import { metadataUtil } from "../../utils/metadataUtil"; import { DriverContext } from "../interface/commonArgs"; +import { ExecutionResult, StepDriver } from "../interface/stepDriver"; +import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; import { WrapDriverContext } from "../util/wrapUtil"; +import { AppStudioClient } from "./clients/appStudioClient"; +import { AppStudioScopes, Constants } from "./constants"; +import { AppStudioError } from "./errors"; import { ValidateAppPackageArgs } from "./interfaces/ValidateAppPackageArgs"; -import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; -import { TelemetryPropertyKey } from "./utils/telemetry"; import { AppStudioResultFactory } from "./results"; -import { AppStudioError } from "./errors"; -import { AppStudioClient } from "./clients/appStudioClient"; -import { getDefaultString, getLocalizedString } from "../../../common/localizeUtils"; -import { AppStudioScopes } from "../../../common/tools"; -import AdmZip from "adm-zip"; -import { Constants } from "./constants"; -import { metadataUtil } from "../../utils/metadataUtil"; -import { SummaryConstant } from "../../configManager/constant"; -import { FileNotFoundError, InvalidActionInputError } from "../../../error/common"; +import { TelemetryPropertyKey } from "./utils/telemetry"; const actionName = "teamsApp/validateAppPackage"; diff --git a/packages/fx-core/src/component/driver/teamsApp/validateTestCases.ts b/packages/fx-core/src/component/driver/teamsApp/validateTestCases.ts index 05f2ad6a7bf..4ffcf95ba85 100644 --- a/packages/fx-core/src/component/driver/teamsApp/validateTestCases.ts +++ b/packages/fx-core/src/component/driver/teamsApp/validateTestCases.ts @@ -24,12 +24,12 @@ import { ValidateWithTestCasesArgs } from "./interfaces/ValidateWithTestCasesArg import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry"; import { AppStudioClient } from "./clients/appStudioClient"; import { getLocalizedString } from "../../../common/localizeUtils"; -import { AppStudioScopes, waitSeconds } from "../../../common/tools"; import AdmZip from "adm-zip"; import { Constants, getAppStudioEndpoint, CEHCK_VALIDATION_RESULTS_INTERVAL_SECONDS, + AppStudioScopes, } from "./constants"; import { metadataUtil } from "../../utils/metadataUtil"; import { FileNotFoundError, InvalidActionInputError } from "../../../error/common"; @@ -39,6 +39,7 @@ import { } from "./interfaces/AsyncAppValidationResponse"; import { AsyncAppValidationResultsResponse } from "./interfaces/AsyncAppValidationResultsResponse"; import { SummaryConstant } from "../../configManager/constant"; +import { waitSeconds } from "../../../common/utils"; const actionName = "teamsApp/validateWithTestCases"; diff --git a/packages/fx-core/src/component/driver/util/utils.ts b/packages/fx-core/src/component/driver/util/utils.ts index 1585fc199d3..277cd287c07 100644 --- a/packages/fx-core/src/component/driver/util/utils.ts +++ b/packages/fx-core/src/component/driver/util/utils.ts @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { Inputs } from "@microsoft/teamsfx-api"; +import { DriverContext } from "../interface/commonArgs"; +import { TOOLS } from "../../../core/globalVars"; + // Needs to validate the parameters outside of the function export function loadStateFromEnv( outputEnvVarNames: Map @@ -26,3 +30,17 @@ export function mapStateToEnv( } return result; } + +export function createDriverContext(inputs: Inputs): DriverContext { + const driverContext: DriverContext = { + azureAccountProvider: TOOLS.tokenProvider.azureAccountProvider, + m365TokenProvider: TOOLS.tokenProvider.m365TokenProvider, + ui: TOOLS.ui, + progressBar: undefined, + logProvider: TOOLS.logProvider, + telemetryReporter: TOOLS.telemetryReporter!, + projectPath: inputs.projectPath!, + platform: inputs.platform, + }; + return driverContext; +} diff --git a/packages/fx-core/src/component/generator/copilotPlugin/generator.ts b/packages/fx-core/src/component/generator/copilotPlugin/generator.ts index 4d5d4a81c4f..0d69dc14334 100644 --- a/packages/fx-core/src/component/generator/copilotPlugin/generator.ts +++ b/packages/fx-core/src/component/generator/copilotPlugin/generator.ts @@ -42,8 +42,8 @@ import { CustomCopilotRagOptions, MeArchitectureOptions, ProgrammingLanguage, -} from "../../../question/create"; -import { QuestionNames } from "../../../question/questionNames"; + QuestionNames, +} from "../../../question/constants"; import { isValidHttpUrl } from "../../../question/util"; import { manifestUtils } from "../../driver/teamsApp/utils/ManifestUtils"; import { ActionContext, ActionExecutionMW } from "../../middleware/actionExecutionMW"; diff --git a/packages/fx-core/src/component/generator/copilotPlugin/helper.ts b/packages/fx-core/src/component/generator/copilotPlugin/helper.ts index af9bd5dc392..ebbdc3b167f 100644 --- a/packages/fx-core/src/component/generator/copilotPlugin/helper.ts +++ b/packages/fx-core/src/component/generator/copilotPlugin/helper.ts @@ -6,56 +6,59 @@ */ import { + AdaptiveCardGenerator, + ErrorResult as ApiSpecErrorResult, + ErrorType as ApiSpecErrorType, + ErrorType, + InvalidAPIInfo, + ListAPIResult, + ParseOptions, + ProjectType, + SpecParser, + SpecParserError, + Utils, + ValidationStatus, + WarningResult, + WarningType, +} from "@microsoft/m365-spec-parser"; +import { ListAPIInfo } from "@microsoft/m365-spec-parser/dist/src/interfaces"; +import { + ApiOperation, + AppPackageFolderName, Context, FxError, + IMessagingExtensionCommand, + Inputs, + ManifestTemplateFileName, + ManifestUtil, OpenAIManifestAuthType, OpenAIPluginManifest, Result, + SystemError, + TeamsAppManifest, UserError, + Warning, err, ok, - TeamsAppManifest, - ApiOperation, - ManifestTemplateFileName, - Warning, - AppPackageFolderName, - ManifestUtil, - IMessagingExtensionCommand, - SystemError, - Inputs, } from "@microsoft/teamsfx-api"; import axios, { AxiosResponse } from "axios"; -import { sendRequestWithRetry } from "../utils"; -import { - SpecParser, - ErrorType as ApiSpecErrorType, - ValidationStatus, - WarningResult, - WarningType, - SpecParserError, - ErrorType, - ErrorResult as ApiSpecErrorResult, - ListAPIResult, - ProjectType, - ParseOptions, - AdaptiveCardGenerator, - Utils, - InvalidAPIInfo, -} from "@microsoft/m365-spec-parser"; import fs from "fs-extra"; +import { OpenAPIV3 } from "openapi-types"; +import { EOL } from "os"; +import path from "path"; +import { isCopilotAuthEnabled } from "../../../common/featureFlags"; import { getLocalizedString } from "../../../common/localizeUtils"; +import { sendRequestWithRetry } from "../../../common/requestUtils"; import { MissingRequiredInputError } from "../../../error"; -import { EOL } from "os"; +import { + CustomCopilotRagOptions, + ProgrammingLanguage, + QuestionNames, + copilotPluginApiSpecOptionId, +} from "../../../question/constants"; import { SummaryConstant } from "../../configManager/constant"; import { manifestUtils } from "../../driver/teamsApp/utils/ManifestUtils"; -import path from "path"; -import { QuestionNames } from "../../../question/questionNames"; import { pluginManifestUtils } from "../../driver/teamsApp/utils/PluginManifestUtils"; -import { copilotPluginApiSpecOptionId } from "../../../question/constants"; -import { OpenAPIV3 } from "openapi-types"; -import { CustomCopilotRagOptions, ProgrammingLanguage } from "../../../question"; -import { ListAPIInfo } from "@microsoft/m365-spec-parser/dist/src/interfaces"; -import { isCopilotAuthEnabled } from "../../../common/featureFlags"; const manifestFilePath = "/.well-known/ai-plugin.json"; const componentName = "OpenAIPluginManifestHelper"; diff --git a/packages/fx-core/src/component/generator/generator.ts b/packages/fx-core/src/component/generator/generator.ts index 96f2997086e..096ceacb325 100644 --- a/packages/fx-core/src/component/generator/generator.ts +++ b/packages/fx-core/src/component/generator/generator.ts @@ -6,7 +6,6 @@ import { Context, FxError, Result, ok } from "@microsoft/teamsfx-api"; import fs from "fs-extra"; import { merge } from "lodash"; import { TelemetryEvent, TelemetryProperty } from "../../common/telemetry"; -import { convertToAlphanumericOnly } from "../../common/utils"; import { BaseComponentInnerError } from "../error/componentError"; import { LogMessages, ProgressMessages, ProgressTitles } from "../messages"; import { ActionContext, ActionExecutionMW } from "../middleware/actionExecutionMW"; @@ -41,6 +40,7 @@ import { isNewProjectTypeEnabled, } from "../../common/featureFlags"; import { Utils } from "@microsoft/m365-spec-parser"; +import { convertToAlphanumericOnly } from "../../common/stringUtils"; export class Generator { public static getDefaultVariables( diff --git a/packages/fx-core/src/component/generator/generatorAction.ts b/packages/fx-core/src/component/generator/generatorAction.ts index d75b97a2422..eda8ca7df33 100644 --- a/packages/fx-core/src/component/generator/generatorAction.ts +++ b/packages/fx-core/src/component/generator/generatorAction.ts @@ -13,12 +13,12 @@ import { downloadDirectory, fetchZipFromUrl, getSampleInfoFromName, - SampleUrlInfo, unzip, getTemplateLatestTag, } from "./utils"; import semver from "semver"; import templateConfig from "../../common/templates-config.json"; +import { SampleUrlInfo } from "../../common/samples"; export interface GeneratorContext { name: string; diff --git a/packages/fx-core/src/component/generator/officeAddin/generator.ts b/packages/fx-core/src/component/generator/officeAddin/generator.ts index 456466598a6..5b7ea3e0281 100644 --- a/packages/fx-core/src/component/generator/officeAddin/generator.ts +++ b/packages/fx-core/src/component/generator/officeAddin/generator.ts @@ -18,6 +18,7 @@ import { ok, } from "@microsoft/teamsfx-api"; import * as childProcess from "child_process"; +import { toLower } from "lodash"; import { OfficeAddinManifest } from "office-addin-manifest"; import { convertProject } from "office-addin-project"; import { join } from "path"; @@ -29,18 +30,15 @@ import { OfficeAddinHostOptions, ProgrammingLanguage, ProjectTypeOptions, - getOfficeAddinFramework, -} from "../../../question/create"; -import { QuestionNames } from "../../../question/questionNames"; + QuestionNames, +} from "../../../question/constants"; +import { getOfficeAddinFramework, getOfficeAddinTemplateConfig } from "../../../question/create"; import { ActionContext, ActionExecutionMW } from "../../middleware/actionExecutionMW"; import { Generator } from "../generator"; -import { getOfficeAddinTemplateConfig } from "../officeXMLAddin/projectConfig"; -import { HelperMethods } from "./helperMethods"; -import { toLower } from "lodash"; -import { convertToLangKey } from "../utils"; import { DefaultTemplateGenerator } from "../templates/templateGenerator"; import { TemplateInfo } from "../templates/templateInfo"; -import { fetchAndUnzip } from "../../utils"; +import { convertToLangKey } from "../utils"; +import { HelperMethods } from "./helperMethods"; const componentName = "office-addin"; const telemetryEvent = "generate"; @@ -143,7 +141,11 @@ export class OfficeAddinGenerator { // Copy project template files from project repository if (projectLink) { - const fetchRes = await fetchAndUnzip("office-addin-generator", projectLink, addinRoot); + const fetchRes = await HelperMethods.fetchAndUnzip( + "office-addin-generator", + projectLink, + addinRoot + ); if (fetchRes.isErr()) { return err(fetchRes.error); } diff --git a/packages/fx-core/src/component/generator/officeAddin/helperMethods.ts b/packages/fx-core/src/component/generator/officeAddin/helperMethods.ts index bd20a23480f..782bcaccbac 100644 --- a/packages/fx-core/src/component/generator/officeAddin/helperMethods.ts +++ b/packages/fx-core/src/component/generator/officeAddin/helperMethods.ts @@ -4,11 +4,13 @@ /** * @author darrmill@microsoft.com, yefuwang@microsoft.com */ -import { ManifestUtil, devPreview } from "@microsoft/teamsfx-api"; +import { FxError, ManifestUtil, Result, devPreview, err, ok } from "@microsoft/teamsfx-api"; import fse from "fs-extra"; import * as path from "path"; -import { ReadFileError } from "../../../error/common"; +import { AccessGithubError, ReadFileError, WriteFileError } from "../../../error/common"; import { manifestUtils } from "../../driver/teamsApp/utils/ManifestUtils"; +import AdmZip from "adm-zip"; +import { fetchZipFromUrl } from "../utils"; export class HelperMethods { static copyAddinFiles(fromFolder: string, toFolder: string): void { @@ -87,6 +89,51 @@ export class HelperMethods { } } } + + static async fetchAndUnzip( + component: string, + zipUrl: string, + targetDir: string, + skipRootFolder = true + ): Promise> { + 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 fse.ensureDir(targetPath); + } else { + await fse.writeFile(targetPath, entryData); + } + } catch (error: any) { + return err(new WriteFileError(error, component)); + } + } + return ok(undefined); + } } export function unzipErrorHandler(projectFolder: string, reject: any, error: Error): void { diff --git a/packages/fx-core/src/component/generator/officeXMLAddin/generator.ts b/packages/fx-core/src/component/generator/officeXMLAddin/generator.ts index 064e603516a..d98aee7837c 100644 --- a/packages/fx-core/src/component/generator/officeXMLAddin/generator.ts +++ b/packages/fx-core/src/component/generator/officeXMLAddin/generator.ts @@ -14,15 +14,19 @@ import { join } from "path"; import { promisify } from "util"; import { getLocalizedString } from "../../../common/localizeUtils"; import { assembleError } from "../../../error"; -import { OfficeAddinHostOptions, ProgrammingLanguage, ProjectTypeOptions } from "../../../question"; -import { QuestionNames } from "../../../question/questionNames"; +import { + OfficeAddinHostOptions, + ProgrammingLanguage, + ProjectTypeOptions, + QuestionNames, +} from "../../../question/constants"; +import { getOfficeAddinTemplateConfig } from "../../../question/create"; import { ActionContext, ActionExecutionMW } from "../../middleware/actionExecutionMW"; -import { fetchAndUnzip } from "../../utils"; import { Generator } from "../generator"; +import { HelperMethods } from "../officeAddin/helperMethods"; import { DefaultTemplateGenerator } from "../templates/templateGenerator"; import { TemplateInfo } from "../templates/templateInfo"; import { convertToLangKey } from "../utils"; -import { getOfficeAddinTemplateConfig } from "./projectConfig"; const COMPONENT_NAME = "office-xml-addin"; const TELEMETRY_EVENT = "generate"; @@ -88,7 +92,7 @@ export class OfficeXMLAddinGenerator { // [Condition]: Project have remote repo (not manifest-only proj) // -> Step: Download the project from GitHub - const fetchRes = await fetchAndUnzip( + const fetchRes = await HelperMethods.fetchAndUnzip( "office-xml-addin-generator", projectLink, destinationPath @@ -197,7 +201,11 @@ export class OfficeXmlAddinGeneratorNew extends DefaultTemplateGenerator { // [Condition]: Project have remote repo (not manifest-only proj) // -> Step: Download the project from GitHub - const fetchRes = await fetchAndUnzip(this.componentName, projectLink, destinationPath); + const fetchRes = await HelperMethods.fetchAndUnzip( + this.componentName, + projectLink, + destinationPath + ); if (fetchRes.isErr()) { return err(fetchRes.error); } diff --git a/packages/fx-core/src/component/generator/officeXMLAddin/projectConfig.ts b/packages/fx-core/src/component/generator/officeXMLAddin/projectConfig.ts index 3d0a36cd717..e0fce984aa1 100644 --- a/packages/fx-core/src/component/generator/officeXMLAddin/projectConfig.ts +++ b/packages/fx-core/src/component/generator/officeXMLAddin/projectConfig.ts @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { OfficeAddinHostOptions, ProjectTypeOptions } from "../../../question"; - /** * @author zyun@microsoft.com */ -interface IOfficeAddinHostConfig { +export interface IOfficeAddinHostConfig { [property: string]: { title: string; detail: string; @@ -22,7 +20,7 @@ interface IOfficeAddinHostConfig { }; } -interface IOfficeAddinProjectConfig { +export interface IOfficeAddinProjectConfig { [property: string]: IOfficeAddinHostConfig; } @@ -185,17 +183,3 @@ export const OfficeAddinProjectConfig: IOfficeAddinProjectConfig = { }, }, }; - -export function getOfficeAddinTemplateConfig( - projectType: string, - addinHost?: string -): IOfficeAddinHostConfig { - if ( - projectType === ProjectTypeOptions.officeXMLAddin().id && - addinHost && - addinHost !== OfficeAddinHostOptions.outlook().id - ) { - return OfficeAddinProjectConfig[addinHost]; - } - return OfficeAddinProjectConfig["json"]; -} diff --git a/packages/fx-core/src/component/generator/spfx/spfxGenerator.ts b/packages/fx-core/src/component/generator/spfx/spfxGenerator.ts index 5d45de39a96..78d0e7c9bf4 100644 --- a/packages/fx-core/src/component/generator/spfx/spfxGenerator.ts +++ b/packages/fx-core/src/component/generator/spfx/spfxGenerator.ts @@ -33,9 +33,9 @@ import { FileNotFoundError, UserCancelError } from "../../../error"; import { CapabilityOptions, ProgrammingLanguage, + QuestionNames, SPFxVersionOptionIds, -} from "../../../question/create"; -import { QuestionNames } from "../../../question/questionNames"; +} from "../../../question/constants"; import { SPFxQuestionNames } from "../../constants"; import { manifestUtils } from "../../driver/teamsApp/utils/ManifestUtils"; import { ActionContext, ActionExecutionMW } from "../../middleware/actionExecutionMW"; diff --git a/packages/fx-core/src/component/generator/templates/ssrTabGenerator.ts b/packages/fx-core/src/component/generator/templates/ssrTabGenerator.ts index d88ec3440f8..32ba3509ff8 100644 --- a/packages/fx-core/src/component/generator/templates/ssrTabGenerator.ts +++ b/packages/fx-core/src/component/generator/templates/ssrTabGenerator.ts @@ -2,9 +2,9 @@ // Licensed under the MIT license. import { Context, FxError, Inputs, Result, ok } from "@microsoft/teamsfx-api"; +import { CapabilityOptions, ProgrammingLanguage, QuestionNames } from "../../../question/constants"; import { DefaultTemplateGenerator } from "./templateGenerator"; import { TemplateInfo } from "./templateInfo"; -import { CapabilityOptions, ProgrammingLanguage, QuestionNames } from "../../../question"; import { TemplateNames } from "./templateNames"; // For the APS.NET server-side rendering tab diff --git a/packages/fx-core/src/component/generator/templates/templateGenerator.ts b/packages/fx-core/src/component/generator/templates/templateGenerator.ts index 92307857600..19e86f9fde5 100644 --- a/packages/fx-core/src/component/generator/templates/templateGenerator.ts +++ b/packages/fx-core/src/component/generator/templates/templateGenerator.ts @@ -16,7 +16,7 @@ import { TelemetryEvent, TelemetryProperty } from "../../../common/telemetry"; import { ProgressMessages, ProgressTitles } from "../../messages"; import { ActionContext, ActionExecutionMW } from "../../middleware/actionExecutionMW"; import { commonTemplateName, componentName } from "../constant"; -import { ProgrammingLanguage, QuestionNames } from "../../../question"; +import { ProgrammingLanguage, QuestionNames } from "../../../question/constants"; import { Generator, templateDefaultOnActionError } from "../generator"; import { convertToLangKey, renderTemplateFileData, renderTemplateFileName } from "../utils"; import { merge } from "lodash"; diff --git a/packages/fx-core/src/component/generator/templates/templateNames.ts b/packages/fx-core/src/component/generator/templates/templateNames.ts index 9b3dd9935f4..4bc7764a27f 100644 --- a/packages/fx-core/src/component/generator/templates/templateNames.ts +++ b/packages/fx-core/src/component/generator/templates/templateNames.ts @@ -9,8 +9,8 @@ import { MeArchitectureOptions, NotificationTriggerOptions, ProgrammingLanguage, -} from "../../../question/create"; -import { QuestionNames } from "../../../question/questionNames"; + QuestionNames, +} from "../../../question/constants"; export enum TemplateNames { Tab = "non-sso-tab", diff --git a/packages/fx-core/src/component/generator/templates/templateReplaceMap.ts b/packages/fx-core/src/component/generator/templates/templateReplaceMap.ts index 7739dc6a3b2..00c21104b54 100644 --- a/packages/fx-core/src/component/generator/templates/templateReplaceMap.ts +++ b/packages/fx-core/src/component/generator/templates/templateReplaceMap.ts @@ -6,8 +6,8 @@ import { enableTestToolByDefault, isNewProjectTypeEnabled, } from "../../../common/featureFlags"; -import { QuestionNames } from "../../../question"; -import { convertToAlphanumericOnly } from "../../../common/utils"; +import { convertToAlphanumericOnly } from "../../../common/stringUtils"; +import { QuestionNames } from "../../../question/constants"; export function getTemplateReplaceMap(inputs: Inputs): { [key: string]: string } { const appName = inputs[QuestionNames.AppName] as string; diff --git a/packages/fx-core/src/component/generator/utils.ts b/packages/fx-core/src/component/generator/utils.ts index 8d44fb7d0e5..ea2360591f4 100644 --- a/packages/fx-core/src/component/generator/utils.ts +++ b/packages/fx-core/src/component/generator/utils.ts @@ -1,27 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import AdmZip from "adm-zip"; +import axios, { AxiosError, AxiosResponse } from "axios"; +import * as fs from "fs-extra"; +import { cloneDeep } from "lodash"; import Mustache, { Context, Writer } from "mustache"; import path from "path"; -import * as fs from "fs-extra"; +import semver from "semver"; +import { sendRequestWithRetry, sendRequestWithTimeout } from "../../common/requestUtils"; +import { SampleConfig, SampleUrlInfo, sampleProvider } from "../../common/samples"; +import templateConfig from "../../common/templates-config.json"; +import { InvalidInputError } from "../../core/error"; +import { ProgrammingLanguage } from "../../question/constants"; import { defaultTimeoutInMs, defaultTryLimits, oldPlaceholderDelimiters, placeholderDelimiters, - templateFileExt, sampleConcurrencyLimits, sampleDefaultRetryLimits, + templateFileExt, } from "./constant"; -import { SampleConfig, sampleProvider } from "../../common/samples"; -import AdmZip from "adm-zip"; -import axios, { AxiosResponse, CancelToken } from "axios"; -import templateConfig from "../../common/templates-config.json"; -import semver from "semver"; -import { deepCopy } from "../../common/tools"; -import { InvalidInputError } from "../../core/error"; -import { ProgrammingLanguage } from "../../question"; -import { AxiosError } from "axios"; async function selectTemplateTag(getTags: () => Promise): Promise { const preRelease = process.env.TEAMSFX_TEMPLATE_PRERELEASE @@ -36,56 +36,6 @@ async function selectTemplateTag(getTags: () => Promise): Promise( - requestFn: () => Promise>, - tryLimits: number -): Promise> { - // !status means network error, see https://github.com/axios/axios/issues/383 - const canTry = (status: number | undefined) => !status || (status >= 500 && status < 600); - - let status: number | undefined; - let error: Error; - - for (let i = 0; i < tryLimits && canTry(status); i++) { - try { - const res = await requestFn(); - if (res.status === 200 || res.status === 201) { - return res; - } else { - error = new Error(`HTTP Request failed: ${JSON.stringify(res)}`); - } - status = res.status; - } catch (e: any) { - error = e; - status = e?.response?.status; - } - } - - error ??= new Error(`RequestWithRetry got bad tryLimits: ${tryLimits}`); - throw error; -} - -export async function sendRequestWithTimeout( - requestFn: (cancelToken: CancelToken) => Promise>, - timeoutInMs: number, - tryLimits = 1 -): Promise> { - const source = axios.CancelToken.source(); - const timeout = setTimeout(() => { - source.cancel(); - }, timeoutInMs); - try { - const res = await sendRequestWithRetry(() => requestFn(source.token), tryLimits); - clearTimeout(timeout); - return res; - } catch (err: unknown) { - if (axios.isCancel(err)) { - throw new Error("Request timeout"); - } - throw err; - } -} - async function fetchTagList(url: string, tryLimits: number, timeoutInMs: number): Promise { const res: AxiosResponse = await sendRequestWithTimeout( async (cancelToken) => { @@ -188,7 +138,7 @@ function escapeEmptyVariable( tags: [string, string] = placeholderDelimiters ): string[][] { const parsed = Mustache.parse(template, tags) as string[][]; - const tokens = deepCopy(parsed); // Mustache cache the parsed result. Modify the result in place may cause unexpected issue. + const tokens = cloneDeep(parsed); // Mustache cache the parsed result. Modify the result in place may cause unexpected issue. updateTokens(tokens, view, tags, 0); return tokens; } @@ -264,13 +214,6 @@ export async function downloadDirectory( return samplePaths; } -export type SampleUrlInfo = { - owner: string; - repository: string; - ref: string; - dir: string; -}; - type SampleFileInfo = { tree: { path: string; diff --git a/packages/fx-core/src/component/middleware/envMW.ts b/packages/fx-core/src/component/middleware/envMW.ts index 51710b79e1f..104584abce5 100644 --- a/packages/fx-core/src/component/middleware/envMW.ts +++ b/packages/fx-core/src/component/middleware/envMW.ts @@ -7,7 +7,7 @@ import { environmentNameManager } from "../../core/environmentName"; import { NoProjectOpenedError } from "../../core/error"; import { TOOLS } from "../../core/globalVars"; import { CoreHookContext } from "../../core/types"; -import { QuestionNames } from "../../question/questionNames"; +import { QuestionNames } from "../../question/constants"; import { selectTargetEnvQuestion } from "../../question/other"; import { traverse } from "../../ui/visitor"; import { envUtil } from "../utils/envUtil"; diff --git a/packages/fx-core/src/component/migrate.ts b/packages/fx-core/src/component/migrate.ts index 9cfe9609735..878732f12d5 100644 --- a/packages/fx-core/src/component/migrate.ts +++ b/packages/fx-core/src/component/migrate.ts @@ -5,10 +5,8 @@ import { pathExistsSync } from "fs-extra"; import { cloneDeep } from "lodash"; import { join } from "path"; import { isVSProject } from "../common/projectSettingsHelper"; -import { CapabilityOptions } from "../question/create"; import { ComponentNames } from "./constants"; -import { ensureComponentConnections } from "./utils"; -import { getComponent } from "./workflow"; +import { CapabilityOptions } from "../question/constants"; export const EnvStateMigrationComponentNames = [ ["solution", "solution"], @@ -204,6 +202,68 @@ export function convertProjectSettingsV2ToV3(settingsV2: any, projectPath: strin } return settingsV3; } +const ComponentConnections = { + [ComponentNames.AzureWebApp]: [ + ComponentNames.Identity, + ComponentNames.AzureSQL, + ComponentNames.KeyVault, + ComponentNames.AadApp, + ComponentNames.TeamsTab, + ComponentNames.TeamsBot, + ComponentNames.TeamsApi, + ], + [ComponentNames.Function]: [ + ComponentNames.Identity, + ComponentNames.AzureSQL, + ComponentNames.KeyVault, + ComponentNames.AadApp, + ComponentNames.TeamsTab, + ComponentNames.TeamsBot, + ComponentNames.TeamsApi, + ], + [ComponentNames.APIM]: [ComponentNames.TeamsTab, ComponentNames.TeamsBot], +}; +export function getComponent(projectSettings: any, resourceType: string): any | undefined { + return projectSettings.components?.find((r: any) => r.name === resourceType); +} +enum Scenarios { + Tab = "Tab", + Bot = "Bot", + Api = "Api", +} +export function getComponentByScenario( + projectSetting: any, + resourceType: string, + scenario?: Scenarios +): any | undefined { + return scenario + ? projectSetting.components?.find( + (r: any) => r.name === resourceType && r.scenario === scenario + ) + : getComponent(projectSetting, resourceType); +} +function ensureComponentConnections(settingsV3: any): void { + const exists = (c: string) => getComponent(settingsV3, c) !== undefined; + const existingConfigNames = Object.keys(ComponentConnections).filter(exists); + for (const configName of existingConfigNames) { + const existingResources = ComponentConnections[configName].filter(exists); + const configs = settingsV3.components.filter((c: any) => c.name === configName); + for (const config of configs) { + config.connections = cloneDeep(existingResources); + } + } + if ( + getComponent(settingsV3, ComponentNames.TeamsApi) && + getComponent(settingsV3, ComponentNames.APIM) + ) { + const functionConfig = getComponentByScenario( + settingsV3, + ComponentNames.Function, + Scenarios.Api + ); + functionConfig?.connections?.push(ComponentNames.APIM); + } +} export function convertManifestTemplateToV3(content: string): string { for (const pluginAndComponentArray of EnvStateMigrationComponentNames) { diff --git a/packages/fx-core/src/component/provisionUtils.ts b/packages/fx-core/src/component/provisionUtils.ts index 3c0b365afe2..d611356de1e 100644 --- a/packages/fx-core/src/component/provisionUtils.ts +++ b/packages/fx-core/src/component/provisionUtils.ts @@ -16,7 +16,7 @@ import { import { HelpLinks } from "../common/constants"; import { getLocalizedString } from "../common/localizeUtils"; import { TelemetryEvent, TelemetryProperty } from "../common/telemetry"; -import { getHashedEnv } from "../common/tools"; +import { getHashedEnv } from "../common/stringUtils"; import { TOOLS } from "../core/globalVars"; import { InvalidAzureCredentialError, diff --git a/packages/fx-core/src/component/resource/botService/botRegistration/botFrameworkRegistration.ts b/packages/fx-core/src/component/resource/botService/botRegistration/botFrameworkRegistration.ts index 961b65691ca..7b716bd92d0 100644 --- a/packages/fx-core/src/component/resource/botService/botRegistration/botFrameworkRegistration.ts +++ b/packages/fx-core/src/component/resource/botService/botRegistration/botFrameworkRegistration.ts @@ -4,10 +4,10 @@ /** * @author Qianhao Dong */ -import { IBotRegistration } from "../appStudio/interfaces/IBotRegistration"; -import { err, FxError, Result, ok, M365TokenProvider, LogProvider } from "@microsoft/teamsfx-api"; -import { AppStudioScopes } from "../../../../common/tools"; +import { FxError, LogProvider, M365TokenProvider, Result, err, ok } from "@microsoft/teamsfx-api"; +import { AppStudioScopes } from "../../../driver/teamsApp/constants"; import { AppStudioClient } from "../appStudio/appStudioClient"; +import { IBotRegistration } from "../appStudio/interfaces/IBotRegistration"; import { Utils } from "./utils"; export async function createOrUpdateBotRegistration( diff --git a/packages/fx-core/src/component/utils.ts b/packages/fx-core/src/component/utils.ts index 8fcab6c1161..ca48942d335 100644 --- a/packages/fx-core/src/component/utils.ts +++ b/packages/fx-core/src/component/utils.ts @@ -2,31 +2,8 @@ // Licensed under the MIT license. "use strict"; -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 { Context } from "@microsoft/teamsfx-api"; 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 { const context: Context = { @@ -37,136 +14,3 @@ export function createContextV3(): Context { }; return context; } -export function createDriverContext(inputs: Inputs): DriverContext { - const driverContext: DriverContext = { - azureAccountProvider: TOOLS.tokenProvider.azureAccountProvider, - m365TokenProvider: TOOLS.tokenProvider.m365TokenProvider, - ui: TOOLS.ui, - progressBar: undefined, - logProvider: TOOLS.logProvider, - telemetryReporter: TOOLS.telemetryReporter!, - projectPath: inputs.projectPath!, - platform: inputs.platform, - }; - return driverContext; -} - -const ComponentConnections = { - [ComponentNames.AzureWebApp]: [ - ComponentNames.Identity, - ComponentNames.AzureSQL, - ComponentNames.KeyVault, - ComponentNames.AadApp, - ComponentNames.TeamsTab, - ComponentNames.TeamsBot, - ComponentNames.TeamsApi, - ], - [ComponentNames.Function]: [ - ComponentNames.Identity, - ComponentNames.AzureSQL, - ComponentNames.KeyVault, - ComponentNames.AadApp, - ComponentNames.TeamsTab, - ComponentNames.TeamsBot, - ComponentNames.TeamsApi, - ], - [ComponentNames.APIM]: [ComponentNames.TeamsTab, ComponentNames.TeamsBot], -}; - -export function ensureComponentConnections(settingsV3: any): void { - const exists = (c: string) => getComponent(settingsV3, c) !== undefined; - const existingConfigNames = Object.keys(ComponentConnections).filter(exists); - for (const configName of existingConfigNames) { - const existingResources = ComponentConnections[configName].filter(exists); - const configs = settingsV3.components.filter((c: any) => c.name === configName); - for (const config of configs) { - config.connections = cloneDeep(existingResources); - } - } - if ( - getComponent(settingsV3, ComponentNames.TeamsApi) && - getComponent(settingsV3, ComponentNames.APIM) - ) { - const functionConfig = getComponentByScenario( - settingsV3, - ComponentNames.Function, - Scenarios.Api - ); - functionConfig?.connections?.push(ComponentNames.APIM); - } -} - -export function sendErrorTelemetryThenReturnError( - eventName: string, - error: FxError, - reporter?: TelemetryReporter, - properties?: { [p: string]: string }, - measurements?: { [p: string]: number }, - errorProps?: string[] -): FxError { - if (!properties) { - properties = {}; - } - - if (SolutionTelemetryProperty.Component in properties === false) { - properties[SolutionTelemetryProperty.Component] = SolutionTelemetryComponentName; - } - - properties[SolutionTelemetryProperty.Success] = "no"; - if (error instanceof UserError) { - properties["error-type"] = "user"; - } else { - properties["error-type"] = "system"; - } - - properties["error-code"] = `${error.source}.${error.name}`; - properties["error-message"] = error.message; - - reporter?.sendTelemetryErrorEvent(eventName, properties, measurements, errorProps); - return error; -} - -export async function fetchAndUnzip( - component: string, - zipUrl: string, - targetDir: string, - skipRootFolder = true -): Promise> { - 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); -} diff --git a/packages/fx-core/src/component/utils/ResourceGroupHelper.ts b/packages/fx-core/src/component/utils/ResourceGroupHelper.ts index 1cb0fda2fc5..c81768b97b8 100644 --- a/packages/fx-core/src/component/utils/ResourceGroupHelper.ts +++ b/packages/fx-core/src/component/utils/ResourceGroupHelper.ts @@ -6,10 +6,14 @@ import { AzureAccountProvider, err, FxError, + Inputs, InputsWithProjectPath, + IQTreeNode, ok, OptionItem, Result, + SingleSelectQuestion, + TextInputQuestion, UserError, } from "@microsoft/teamsfx-api"; import { TOOLS } from "../../core/globalVars"; @@ -22,10 +26,10 @@ import { ListResourceGroupsError, ResourceGroupConflictError, } from "../../error/azure"; -import { resourceGroupQuestionNode } from "../../question/other"; -import { QuestionNames } from "../../question/questionNames"; +import { QuestionNames, recommendedLocations } from "../../question/constants"; import { traverse } from "../../ui/visitor"; import { SolutionSource } from "../constants"; +import { getLocalizedString } from "../../common/localizeUtils"; const MsResources = "Microsoft.Resources"; const ResourceGroups = "resourceGroups"; @@ -36,39 +40,154 @@ export type ResourceGroupInfo = { location: string; }; -export const recommendedLocations = [ - "South Africa North", - "Australia East", - "Central India", - "East Asia", - "Japan East", - "Korea Central", - "Southeast Asia", - "Canada Central", - "France Central", - "Germany West Central", - "Italy North", - "North Europe", - "Norway East", - "Poland Central", - "Sweden Central", - "Switzerland North", - "UK South", - "West Europe", - "Israel Central", - "Qatar Central", - "UAE North", - "Brazil South", - "Central US", - "East US", - "East US 2", - "South Central US", - "West US 2", - "West US 3", -]; - // TODO: use the emoji plus sign like Azure Functions extension -const newResourceGroupOption = "+ New resource group"; +export const newResourceGroupOption = "+ New resource group"; +/** + * select existing resource group or create new resource group + */ +export function selectResourceGroupQuestion( + azureAccountProvider: AzureAccountProvider, + subscriptionId: string +): SingleSelectQuestion { + return { + type: "singleSelect", + name: QuestionNames.TargetResourceGroupName, + title: getLocalizedString("core.QuestionSelectResourceGroup.title"), + staticOptions: [{ id: newResourceGroupOption, label: newResourceGroupOption }], + dynamicOptions: async (inputs: Inputs): Promise => { + const rmClient = await resourceGroupHelper.createRmClient( + azureAccountProvider, + subscriptionId + ); + const listRgRes = await resourceGroupHelper.listResourceGroups(rmClient); + if (listRgRes.isErr()) throw listRgRes.error; + const rgList = listRgRes.value; + const options: OptionItem[] = rgList.map((rg) => { + return { + id: rg[0], + label: rg[0], + description: rg[1], + }; + }); + const existingResourceGroupNames = rgList.map((rg) => rg[0]); + inputs.existingResourceGroupNames = existingResourceGroupNames; // cache existing resource group names for valiation usage + return [{ id: newResourceGroupOption, label: newResourceGroupOption }, ...options]; + }, + skipSingleOption: true, + returnObject: true, + forgetLastValue: true, + }; +} + +export function selectResourceGroupLocationQuestion( + azureAccountProvider: AzureAccountProvider, + subscriptionId: string +): SingleSelectQuestion { + return { + type: "singleSelect", + name: QuestionNames.NewResourceGroupLocation, + title: getLocalizedString("core.QuestionNewResourceGroupLocation.title"), + staticOptions: [], + dynamicOptions: async (inputs: Inputs) => { + const rmClient = await resourceGroupHelper.createRmClient( + azureAccountProvider, + subscriptionId + ); + const getLocationsRes = await resourceGroupHelper.getLocations( + azureAccountProvider, + rmClient + ); + if (getLocationsRes.isErr()) { + throw getLocationsRes.error; + } + const recommended = getLocationsRes.value.filter((location) => { + return recommendedLocations.indexOf(location) >= 0; + }); + const others = getLocationsRes.value.filter((location) => { + return recommendedLocations.indexOf(location) < 0; + }); + return [ + ...recommended.map((location) => { + return { + id: location, + label: location, + groupName: getLocalizedString( + "core.QuestionNewResourceGroupLocation.group.recommended" + ), + } as OptionItem; + }), + ...others.map((location) => { + return { + id: location, + label: location, + groupName: getLocalizedString("core.QuestionNewResourceGroupLocation.group.others"), + } as OptionItem; + }), + ]; + }, + default: "Central US", + }; +} + +export function validateResourceGroupName(input: string, inputs?: Inputs): string | undefined { + const name = input; + // https://docs.microsoft.com/en-us/rest/api/resources/resource-groups/create-or-update#uri-parameters + const match = name.match(/^[-\w._()]+$/); + if (!match) { + return getLocalizedString("core.QuestionNewResourceGroupName.validation"); + } + + // To avoid the issue in CLI that using async func for validation and filter will make users input answers twice, + // we check the existence of a resource group from the list rather than call the api directly for now. + // Bug: https://msazure.visualstudio.com/Microsoft%20Teams%20Extensibility/_workitems/edit/15066282 + // GitHub issue: https://github.com/SBoudrias/Inquirer.js/issues/1136 + if (inputs?.existingResourceGroupNames) { + const maybeExist = + inputs.existingResourceGroupNames.findIndex( + (o: string) => o.toLowerCase() === input.toLowerCase() + ) >= 0; + if (maybeExist) { + return `resource group already exists: ${name}`; + } + } + return undefined; +} + +export function newResourceGroupNameQuestion(defaultResourceGroupName: string): TextInputQuestion { + return { + type: "text", + name: QuestionNames.NewResourceGroupName, + title: getLocalizedString("core.QuestionNewResourceGroupName.title"), + placeholder: getLocalizedString("core.QuestionNewResourceGroupName.placeholder"), + // default resource group name will change with env name + forgetLastValue: true, + default: defaultResourceGroupName, + validation: { + validFunc: validateResourceGroupName, + }, + }; +} + +export function resourceGroupQuestionNode( + azureAccountProvider: AzureAccountProvider, + subscriptionId: string, + defaultResourceGroupName: string +): IQTreeNode { + return { + data: selectResourceGroupQuestion(azureAccountProvider, subscriptionId), + children: [ + { + condition: { equals: newResourceGroupOption }, + data: newResourceGroupNameQuestion(defaultResourceGroupName), + children: [ + { + data: selectResourceGroupLocationQuestion(azureAccountProvider, subscriptionId), + }, + ], + }, + ], + }; +} class ResourceGroupHelper { async createNewResourceGroup( diff --git a/packages/fx-core/src/component/workflow.ts b/packages/fx-core/src/component/workflow.ts deleted file mode 100644 index f6aea6d553e..00000000000 --- a/packages/fx-core/src/component/workflow.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { Scenarios } from "./constants"; - -export function getComponent(projectSettings: any, resourceType: string): any | undefined { - return projectSettings.components?.find((r: any) => r.name === resourceType); -} - -export function getComponentByScenario( - projectSetting: any, - resourceType: string, - scenario?: Scenarios -): any | undefined { - return scenario - ? projectSetting.components?.find( - (r: any) => r.name === resourceType && r.scenario === scenario - ) - : getComponent(projectSetting, resourceType); -} diff --git a/packages/fx-core/src/core/FxCore.ts b/packages/fx-core/src/core/FxCore.ts index 7c7942252fd..d10ac49c98a 100644 --- a/packages/fx-core/src/core/FxCore.ts +++ b/packages/fx-core/src/core/FxCore.ts @@ -30,16 +30,16 @@ import { err, ok, } from "@microsoft/teamsfx-api"; -import { OpenAPIV3 } from "openapi-types"; import { DotenvParseOutput } from "dotenv"; import fs from "fs-extra"; import * as jsonschema from "jsonschema"; +import { OpenAPIV3 } from "openapi-types"; import * as os from "os"; import * as path from "path"; import "reflect-metadata"; import { Container } from "typedi"; import { pathToFileURL } from "url"; -import { parse, parseDocument } from "yaml"; +import { parse } from "yaml"; import { VSCodeExtensionCommand } from "../common/constants"; import { getLocalizedString } from "../common/localizeUtils"; import { LaunchHelper } from "../common/m365/launchHelper"; @@ -48,6 +48,7 @@ import { isValidProjectV2, isValidProjectV3 } from "../common/projectSettingsHel import { ProjectTypeResult, projectTypeChecker } from "../common/projectTypeChecker"; import { TelemetryEvent, fillinProjectTypeProperties } from "../common/telemetry"; import { MetadataV3, VersionSource, VersionState } from "../common/versionMetadata"; +import { ActionInjector } from "../component/configManager/actionInjector"; import { ILifecycle, LifecycleName } from "../component/configManager/interface"; import { YamlParser } from "../component/configManager/parser"; import { @@ -67,11 +68,14 @@ import { DriverContext } from "../component/driver/interface/commonArgs"; import "../component/driver/script/scriptDriver"; import { updateManifestV3 } from "../component/driver/teamsApp/appStudio"; import { CreateAppPackageDriver } from "../component/driver/teamsApp/createAppPackage"; +import { AppStudioError } from "../component/driver/teamsApp/errors"; import { CreateAppPackageArgs } from "../component/driver/teamsApp/interfaces/CreateAppPackageArgs"; import { ValidateAppPackageArgs } from "../component/driver/teamsApp/interfaces/ValidateAppPackageArgs"; import { ValidateManifestArgs } from "../component/driver/teamsApp/interfaces/ValidateManifestArgs"; import { ValidateWithTestCasesArgs } from "../component/driver/teamsApp/interfaces/ValidateWithTestCasesArgs"; +import { AppStudioResultFactory } from "../component/driver/teamsApp/results"; import { teamsappMgr } from "../component/driver/teamsApp/teamsappMgr"; +import { copilotGptManifestUtils } from "../component/driver/teamsApp/utils/CopilotGptManifestUtils"; import { manifestUtils } from "../component/driver/teamsApp/utils/ManifestUtils"; import { pluginManifestUtils } from "../component/driver/teamsApp/utils/PluginManifestUtils"; import { @@ -81,27 +85,28 @@ import { import { ValidateManifestDriver } from "../component/driver/teamsApp/validate"; import { ValidateAppPackageDriver } from "../component/driver/teamsApp/validateAppPackage"; import { ValidateWithTestCasesDriver } from "../component/driver/teamsApp/validateTestCases"; +import { createDriverContext } from "../component/driver/util/utils"; import "../component/feature/sso"; import { SSO } from "../component/feature/sso"; import { OpenAIPluginManifestHelper, + convertSpecParserErrorToFxError, + copilotPluginParserOptions, defaultApiSpecFolderName, defaultApiSpecJsonFileName, defaultApiSpecYamlFileName, - convertSpecParserErrorToFxError, - copilotPluginParserOptions, + defaultPluginManifestFileName, generateScaffoldingSummary, isYamlSpecFile, listOperations, listPluginExistingOperations, - defaultPluginManifestFileName, specParserGenerateResultAllSuccessTelemetryProperty, specParserGenerateResultTelemetryEvent, specParserGenerateResultWarningsTelemetryProperty, } from "../component/generator/copilotPlugin/helper"; import { EnvLoaderMW, EnvWriterMW } from "../component/middleware/envMW"; import { QuestionMW } from "../component/middleware/questionMW"; -import { createContextV3, createDriverContext } from "../component/utils"; +import { createContextV3 } from "../component/utils"; import { expandEnvironmentVariable } from "../component/utils/common"; import { envUtil } from "../component/utils/envUtil"; import { metadataUtil } from "../component/utils/metadataUtil"; @@ -119,17 +124,21 @@ import { } from "../error/common"; import { NoNeedUpgradeError } from "../error/upgrade"; import { YamlFieldMissingError } from "../error/yml"; -import { AppNamePattern, ProjectTypeOptions, ValidateTeamsAppInputs } from "../question"; -import { copilotPluginApiSpecOptionId } from "../question/constants"; -import { SPFxVersionOptionIds, ScratchOptions, createProjectCliHelpNode } from "../question/create"; import { + AppNamePattern, HubTypes, PluginAvailabilityOptions, + ProjectTypeOptions, + QuestionNames, + SPFxVersionOptionIds, + ScratchOptions, TeamsAppValidationOptions, - isAadMainifestContainsPlaceholder, -} from "../question/other"; -import { QuestionNames } from "../question/questionNames"; -import { CallbackRegistry } from "./callback"; + copilotPluginApiSpecOptionId, +} from "../question/constants"; +import { createProjectCliHelpNode } from "../question/create"; +import { ValidateTeamsAppInputs } from "../question/inputs/ValidateTeamsAppInputs"; +import { isAadMainifestContainsPlaceholder } from "../question/other"; +import { CallbackRegistry, CoreCallbackFunc } from "./callback"; import { checkPermission, grantPermission, listCollaborator } from "./collaborator"; import { LocalCrypto } from "./crypto"; import { environmentNameManager } from "./environmentName"; @@ -146,12 +155,6 @@ import { } from "./middleware/utils/v3MigrationUtils"; import { CoreTelemetryComponentName, CoreTelemetryEvent, CoreTelemetryProperty } from "./telemetry"; import { CoreHookContext, PreProvisionResForVS, VersionCheckRes } from "./types"; -import { AppStudioResultFactory } from "../component/driver/teamsApp/results"; -import { AppStudioError } from "../component/driver/teamsApp/errors"; -import { copilotGptManifestUtils } from "../component/driver/teamsApp/utils/CopilotGptManifestUtils"; -import { ActionInjector } from "../component/configManager/actionInjector"; - -export type CoreCallbackFunc = (name: string, err?: FxError, data?: any) => void | Promise; export class FxCore { constructor(tools: Tools) { diff --git a/packages/fx-core/src/core/callback.ts b/packages/fx-core/src/core/callback.ts index 3fdf8a585a1..bd7e5fed750 100644 --- a/packages/fx-core/src/core/callback.ts +++ b/packages/fx-core/src/core/callback.ts @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { CoreCallbackEvent } from "@microsoft/teamsfx-api"; -import { CoreCallbackFunc } from "./FxCore"; +import { CoreCallbackEvent, FxError } from "@microsoft/teamsfx-api"; + +export type CoreCallbackFunc = (name: string, err?: FxError, data?: any) => void | Promise; export class CallbackRegistry { private static registry: Map = new Map(); diff --git a/packages/fx-core/src/core/collaborator.ts b/packages/fx-core/src/core/collaborator.ts index 4882aa16737..e8bf69c96ca 100644 --- a/packages/fx-core/src/core/collaborator.ts +++ b/packages/fx-core/src/core/collaborator.ts @@ -29,12 +29,12 @@ import { PermissionsResult, ResourcePermission, } from "../common/permissionInterface"; -import { GraphScopes } from "../common/tools"; +import { GraphScopes } from "../common/constants"; import { SolutionError, SolutionSource, SolutionTelemetryProperty } from "../component/constants"; import { AppUser } from "../component/driver/teamsApp/interfaces/appdefinitions/appUser"; import { AadCollaboration, TeamsCollaboration } from "../component/feature/collaboration"; import { FileNotFoundError } from "../error/common"; -import { QuestionNames } from "../question/questionNames"; +import { QuestionNames } from "../question/constants"; import { CoreSource, FailedToLoadManifestId } from "./error"; export class CollaborationConstants { diff --git a/packages/fx-core/src/core/middleware/concurrentLocker.ts b/packages/fx-core/src/core/middleware/concurrentLocker.ts index 8e1c1312ea8..5b3357c1de3 100644 --- a/packages/fx-core/src/core/middleware/concurrentLocker.ts +++ b/packages/fx-core/src/core/middleware/concurrentLocker.ts @@ -6,24 +6,24 @@ import { HookContext, Middleware, NextFunction } from "@feathersjs/hooks"; import { ConfigFolderName, CoreCallbackEvent, - err, Func, Inputs, ProductName, + err, } from "@microsoft/teamsfx-api"; +import crypto from "crypto"; import * as fs from "fs-extra"; +import * as os from "os"; import * as path from "path"; import { lock, unlock } from "proper-lockfile"; -import { TOOLS } from "../globalVars"; +import { isValidProjectV2, isValidProjectV3 } from "../../common/projectSettingsHelper"; import { sendTelemetryErrorEvent } from "../../common/telemetry"; +import { waitSeconds } from "../../common/utils"; +import { ConcurrentError, FileNotFoundError, InvalidProjectError } from "../../error/common"; import { CallbackRegistry } from "../callback"; import { CoreSource, NoProjectOpenedError } from "../error"; +import { TOOLS } from "../globalVars"; import { shouldIgnored } from "./projectSettingsLoader"; -import crypto from "crypto"; -import * as os from "os"; -import { waitSeconds } from "../../common/tools"; -import { isValidProjectV2, isValidProjectV3 } from "../../common/projectSettingsHelper"; -import { ConcurrentError, FileNotFoundError, InvalidProjectError } from "../../error/common"; let doingTask: string | undefined = undefined; export const ConcurrentLockerMW: Middleware = async (ctx: HookContext, next: NextFunction) => { diff --git a/packages/fx-core/src/core/middleware/projectMigratorV3.ts b/packages/fx-core/src/core/middleware/projectMigratorV3.ts index 8bc1ee702a4..f5bf819b1a6 100644 --- a/packages/fx-core/src/core/middleware/projectMigratorV3.ts +++ b/packages/fx-core/src/core/middleware/projectMigratorV3.ts @@ -86,7 +86,6 @@ import { AppLocalYmlGenerator } from "./utils/debug/appLocalYmlGenerator"; import { EOL } from "os"; import { getTemplatesFolder } from "../../folder"; import { MetadataV2, MetadataV3, VersionSource, VersionState } from "../../common/versionMetadata"; -import { isSPFxProject } from "../../common/tools"; import { VersionForMigration } from "./types"; import { getLocalizedString } from "../../common/localizeUtils"; import { HubName, LaunchBrowser, LaunchUrl } from "./utils/debug/constants"; @@ -371,7 +370,14 @@ async function loadProjectSettings(projectPath: string): Promise { throw oldProjectSettings.error; } } - +export function isSPFxProject(projectSettings?: any): boolean { + const solutionSettings = projectSettings?.solutionSettings; + if (solutionSettings) { + const selectedPlugins = solutionSettings.activeResourcePlugins; + return selectedPlugins && selectedPlugins.indexOf("fx-resource-spfx") !== -1; + } + return false; +} export async function manifestsMigration(context: MigrationContext): Promise { // Check manifest existing const oldAppPackageFolderPath = path.join(getTemplateFolderPath(context), AppPackageFolderName); diff --git a/packages/fx-core/src/core/middleware/utils/appYmlGenerator.ts b/packages/fx-core/src/core/middleware/utils/appYmlGenerator.ts index 01f8323af0c..3684a3e9021 100644 --- a/packages/fx-core/src/core/middleware/utils/appYmlGenerator.ts +++ b/packages/fx-core/src/core/middleware/utils/appYmlGenerator.ts @@ -9,8 +9,8 @@ import * as handlebars from "handlebars"; import { getTemplatesFolder } from "../../../folder"; import { DebugPlaceholderMapping } from "./debug/debugV3MigrationUtils"; import { MetadataV3 } from "../../../common/versionMetadata"; -import { hasFunctionBot } from "../../../common/projectSettingsHelperV3"; -import { convertProjectSettingsV2ToV3 } from "../../../component/migrate"; +import { convertProjectSettingsV2ToV3, getComponent } from "../../../component/migrate"; +import { ComponentNames } from "../../../component/constants"; export abstract class BaseAppYmlGenerator { protected abstract handlebarsContext: any; constructor(protected oldProjectSettings: any) {} @@ -22,7 +22,11 @@ export abstract class BaseAppYmlGenerator { return template(this.handlebarsContext); } } - +export function hasFunctionBot(projectSettings: any): boolean { + const botComponent = getComponent(projectSettings, ComponentNames.TeamsBot); + if (!botComponent) return false; + return botComponent.hosting === ComponentNames.Function; +} export class AppYmlGenerator extends BaseAppYmlGenerator { protected handlebarsContext: { activePlugins: Record; diff --git a/packages/fx-core/src/core/middleware/videoFilterAppBlocker.ts b/packages/fx-core/src/core/middleware/videoFilterAppBlocker.ts index f4adef62df9..3e0fc42d880 100644 --- a/packages/fx-core/src/core/middleware/videoFilterAppBlocker.ts +++ b/packages/fx-core/src/core/middleware/videoFilterAppBlocker.ts @@ -3,8 +3,9 @@ "use strict"; import { NextFunction } from "@feathersjs/hooks"; -import { Inputs, err, Func } from "@microsoft/teamsfx-api"; -import { isVideoFilterProject } from "../../common/tools"; +import { Func, FxError, Inputs, Result, err, ok } from "@microsoft/teamsfx-api"; +import { manifestUtils } from "../../component/driver/teamsApp/utils/ManifestUtils"; +import { assembleError } from "../../error/common"; import { VideoFilterAppRemoteNotSupportedError } from "../error"; import { CoreHookContext } from "../types"; @@ -25,7 +26,21 @@ const userTasksToBlock: Func[] = [ method: "validateManifest", }, ]; - +export async function isVideoFilterProject(projectPath: string): Promise> { + let manifestResult; + try { + manifestResult = await manifestUtils.readAppManifest(projectPath); + } catch (e) { + return err(assembleError(e)); + } + if (manifestResult.isErr()) { + return err(manifestResult.error); + } + const manifest = manifestResult.value; + return ok( + (manifest.meetingExtensionDefinition as any)?.videoFiltersConfigurationUrl !== undefined + ); +} async function shouldBlockExecution(ctx: CoreHookContext): Promise { const inputs = ctx.arguments[ctx.arguments.length - 1] as Inputs; if (!inputs.projectPath) { diff --git a/packages/fx-core/src/error/common.ts b/packages/fx-core/src/error/common.ts index 2b3e918178a..c27383af6db 100644 --- a/packages/fx-core/src/error/common.ts +++ b/packages/fx-core/src/error/common.ts @@ -514,3 +514,13 @@ const errnoCodes: Record = { EWOULDBLOCK: "Operation would block", EXDEV: "Cross-device link", }; + +export function isUserCancelError(error: Error): boolean { + const errorName = "name" in error ? (error as any)["name"] : ""; + return ( + errorName === "User Cancel" || + errorName === "CancelProvision" || + errorName === "UserCancel" || + errorName === "UserCancelError" + ); +} diff --git a/packages/fx-core/src/index.ts b/packages/fx-core/src/index.ts index 118c17ea061..2418d7f567e 100644 --- a/packages/fx-core/src/index.ts +++ b/packages/fx-core/src/index.ts @@ -3,51 +3,59 @@ "use strict"; import "reflect-metadata"; +export * from "./common/azureUtils"; +export * from "./common/constants"; export * from "./common/correlator"; export * from "./common/deps-checker"; +export { FuncToolChecker } from "./common/deps-checker/internal/funcToolChecker"; +export { LtsNodeChecker } from "./common/deps-checker/internal/nodeChecker"; export * from "./common/featureFlags"; export * from "./common/globalState"; -export * from "./common/telemetry"; -export * from "./common/stringUtils"; export { jsonUtils } from "./common/jsonUtils"; export * from "./common/local"; +export { LocalCertificateManager } from "./common/local/localCertificateManager"; +export * from "./common/localizeUtils"; export * from "./common/m365/constants"; export { PackageService } from "./common/m365/packageService"; export * from "./common/m365/serviceConstant"; export * from "./common/permissionInterface"; export * from "./common/projectSettingsHelper"; -export * from "./common/projectSettingsHelperV3"; +export * from "./common/projectTypeChecker"; +export * from "./common/requestUtils"; +export * from "./common/samples"; +export * from "./common/stringUtils"; +export * from "./common/telemetry"; export * from "./common/tools"; -export { LocalCertificateManager } from "./common/local/localCertificateManager"; -export { FuncToolChecker } from "./common/deps-checker/internal/funcToolChecker"; -export { LtsNodeChecker } from "./common/deps-checker/internal/nodeChecker"; +export { loadingDefaultPlaceholder, loadingOptionsPlaceholder } from "./common/utils"; export { MetadataV3, VersionState } from "./common/versionMetadata"; export * from "./component/constants"; -export * from "./component/migrate"; -export { envUtil, DotenvOutput } from "./component/utils/envUtil"; -export { metadataUtil } from "./component/utils/metadataUtil"; -export { pathUtils } from "./component/utils/pathUtils"; -export { CoreCallbackFunc, FxCore } from "./core/FxCore"; -export { sampleProvider, SampleConfig } from "./common/samples"; -export { loadingOptionsPlaceholder, loadingDefaultPlaceholder } from "./common/utils"; -export { AppStudioClient } from "./component/driver/teamsApp/clients/appStudioClient"; export { getPermissionMap } from "./component/driver/aad/permissions/index"; +export { AppStudioClient } from "./component/driver/teamsApp/clients/appStudioClient"; +export * from "./component/driver/teamsApp/constants"; export { AppDefinition } from "./component/driver/teamsApp/interfaces/appdefinitions/appDefinition"; -export * from "./component/driver/teamsApp/utils/utils"; export { manifestUtils } from "./component/driver/teamsApp/utils/ManifestUtils"; export { pluginManifestUtils } from "./component/driver/teamsApp/utils/PluginManifestUtils"; +export * from "./component/driver/teamsApp/utils/utils"; +export * from "./component/generator/copilotPlugin/helper"; +export { HelperMethods } from "./component/generator/officeAddin/helperMethods"; +export { DefaultTemplateGenerator } from "./component/generator/templates/templateGenerator"; +export * from "./component/generator/utils"; +export * from "./component/migrate"; +export * from "./component/utils/ResourceGroupHelper"; +export { DotenvOutput, envUtil } from "./component/utils/envUtil"; +export { metadataUtil } from "./component/utils/metadataUtil"; +export { pathUtils } from "./component/utils/pathUtils"; +export { FxCore } from "./core/FxCore"; +export { CoreCallbackFunc } from "./core/callback"; export { CollaborationConstants } from "./core/collaborator"; export { environmentManager } from "./core/environment"; export { environmentNameManager } from "./core/environmentName"; export * from "./core/error"; -export { QuestionNames as CoreQuestionNames } from "./question/questionNames"; +export { isVideoFilterProject } from "./core/middleware/videoFilterAppBlocker"; export * from "./core/types"; export * from "./error/index"; -export * from "./ui/visitor"; -export * from "./ui/validationUtils"; export * from "./question"; -export * from "./component/generator/copilotPlugin/helper"; +export { QuestionNames as CoreQuestionNames } from "./question/constants"; export * from "./question/util"; -export * from "./common/projectTypeChecker"; -export { DefaultTemplateGenerator } from "./component/generator/templates/templateGenerator"; -export { fetchAndUnzip } from "./component/utils"; +export * from "./ui/validationUtils"; +export * from "./ui/visitor"; diff --git a/packages/fx-core/src/question/constants.ts b/packages/fx-core/src/question/constants.ts index b1763512299..b00a5fc54a7 100644 --- a/packages/fx-core/src/question/constants.ts +++ b/packages/fx-core/src/question/constants.ts @@ -1,6 +1,113 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { Inputs, OptionItem, Platform } from "@microsoft/teamsfx-api"; +import { + FeatureFlags, + featureFlagManager, + isApiCopilotPluginEnabled, + isCLIDotNetEnabled, + isCopilotPluginEnabled, + isTdpTemplateCliTestEnabled, +} from "../common/featureFlags"; +import { getLocalizedString } from "../common/localizeUtils"; +import { OfficeAddinProjectConfig } from "../component/generator/officeXMLAddin/projectConfig"; + +export enum QuestionNames { + Scratch = "scratch", + SctatchYes = "scratch-yes", + AppName = "app-name", + Folder = "folder", + ProjectPath = "projectPath", + ProgrammingLanguage = "programming-language", + ProjectType = "project-type", + Capabilities = "capabilities", + BotTrigger = "bot-host-type-trigger", + Runtime = "runtime", + SPFxSolution = "spfx-solution", + SPFxInstallPackage = "spfx-install-latest-package", + SPFxFramework = "spfx-framework-type", + SPFxWebpartName = "spfx-webpart-name", + SPFxWebpartDesc = "spfx-webpart-desp", + SPFxFolder = "spfx-folder", + OfficeAddinFolder = "addin-project-folder", + OfficeAddinManifest = "addin-project-manifest", + OfficeAddinTemplate = "addin-template-select", + OfficeAddinHost = "addin-host", + OfficeAddinImport = "addin-import", + OfficeAddinFramework = "office-addin-framework-type", + Samples = "samples", + ReplaceContentUrl = "replaceContentUrl", + ReplaceWebsiteUrl = "replaceWebsiteUrl", + ReplaceBotIds = "replaceBotIds", + SafeProjectName = "safeProjectName", + RepalceTabUrl = "tdp-tab-url", + ValidateMethod = "validate-method", + AppPackagePath = "appPackagePath", + CopilotPluginExistingApi = "copilot-plugin-existing-api", // group name for creating a Copilot plugin from existing api + ApiSpecLocation = "openapi-spec-location", + OpenAIPluginManifest = "openai-plugin-manifest", + ApiOperation = "api-operation", + MeArchitectureType = "me-architecture", + ApiSpecApiKey = "api-key", + ApiSpecApiKeyConfirm = "api-key-confirm", + ApiMEAuth = "api-me-auth", + OauthClientSecret = "oauth-client-secret", + OauthClientId = "oauth-client-id", + OauthConfirm = "oauth-confirm", + + CustomCopilotRag = "custom-copilot-rag", + CustomCopilotAssistant = "custom-copilot-agent", + LLMService = "llm-service", + OpenAIKey = "openai-key", + AzureOpenAIKey = "azure-openai-key", + AzureOpenAIEndpoint = "azure-openai-endpoint", + AzureOpenAIDeploymentName = "azure-openai-deployment-name", + + Features = "features", + Env = "env", + SourceEnvName = "sourceEnvName", + TargetEnvName = "targetEnvName", + TargetResourceGroupName = "targetResourceGroupName", + NewResourceGroupName = "newResourceGroupName", + NewResourceGroupLocation = "newResourceGroupLocation", + NewTargetEnvName = "newTargetEnvName", + ExistingTabEndpoint = "existing-tab-endpoint", + TeamsAppManifestFilePath = "manifest-path", + LocalTeamsAppManifestFilePath = "local-manifest-path", + AadAppManifestFilePath = "manifest-file-path", + TeamsAppPackageFilePath = "app-package-file-path", + ConfirmManifest = "confirmManifest", + ConfirmLocalManifest = "confirmLocalManifest", + ConfirmAadManifest = "confirmAadManifest", + OutputZipPathParamName = "output-zip-path", + OutputManifestParamName = "output-manifest-path", + M365Host = "m365-host", + + ManifestPath = "manifest-path", + + UserEmail = "email", + + collaborationAppType = "collaborationType", + DestinationApiSpecFilePath = "destination-api-spec-location", + PluginAvailability = "plugin-availability", +} + +export const AppNamePattern = + '^(?=(.*[\\da-zA-Z]){2})[a-zA-Z][^"<>:\\?/*&|\u0000-\u001F]*[^"\\s.<>:\\?/*&|\u0000-\u001F]$'; + +export enum CliQuestionName { + Capability = "capability", +} + +export enum ProgrammingLanguage { + JS = "javascript", + TS = "typescript", + CSharp = "csharp", + PY = "python", + None = "none", +} + export const copilotPluginApiSpecOptionId = "copilot-plugin-existing-api"; export const copilotPluginOpenAIPluginOptionId = "copilot-plugin-openai-plugin"; export const copilotPluginExistingApiOptionIds = [ @@ -19,3 +126,1195 @@ export const capabilitiesHavePythonOption = [ "custom-copilot-rag-customize", "custom-copilot-agent-new", ]; + +export class RuntimeOptions { + static NodeJS(): OptionItem { + return { + id: "node", + label: "Node.js", + detail: getLocalizedString("core.RuntimeOptionNodeJS.detail"), + }; + } + static DotNet(): OptionItem { + return { + id: "dotnet", + label: ".NET Core", + detail: getLocalizedString("core.RuntimeOptionDotNet.detail"), + }; + } +} + +export function getRuntime(inputs: Inputs): string { + let runtime = RuntimeOptions.NodeJS().id; + if (isCLIDotNetEnabled()) { + runtime = inputs[QuestionNames.Runtime] || runtime; + } else { + if (inputs?.platform === Platform.VS) { + runtime = RuntimeOptions.DotNet().id; + } + } + return runtime; +} + +export class ScratchOptions { + static yes(): OptionItem { + return { + id: "yes", + label: getLocalizedString("core.ScratchOptionYes.label"), + detail: getLocalizedString("core.ScratchOptionYes.detail"), + }; + } + static no(): OptionItem { + return { + id: "no", + label: getLocalizedString("core.ScratchOptionNo.label"), + detail: getLocalizedString("core.ScratchOptionNo.detail"), + }; + } + static all(): OptionItem[] { + return [ScratchOptions.yes(), ScratchOptions.no()]; + } +} + +export class ProjectTypeOptions { + static tab(platform?: Platform): OptionItem { + return { + id: "tab-type", + label: `${platform === Platform.VSCode ? "$(browser) " : ""}${getLocalizedString( + "core.TabOption.label" + )}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.tab.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static bot(platform?: Platform): OptionItem { + return { + id: "bot-type", + label: `${platform === Platform.VSCode ? "$(hubot) " : ""}${getLocalizedString( + "core.createProjectQuestion.projectType.bot.label" + )}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.bot.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static me(platform?: Platform): OptionItem { + return { + id: "me-type", + label: `${platform === Platform.VSCode ? "$(symbol-keyword) " : ""}${getLocalizedString( + "core.MessageExtensionOption.label" + )}`, + detail: isCopilotPluginEnabled() + ? getLocalizedString( + "core.createProjectQuestion.projectType.messageExtension.copilotEnabled.detail" + ) + : getLocalizedString("core.createProjectQuestion.projectType.messageExtension.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static outlookAddin(platform?: Platform): OptionItem { + return { + id: "outlook-addin-type", + label: `${platform === Platform.VSCode ? "$(mail) " : ""}${getLocalizedString( + "core.createProjectQuestion.projectType.outlookAddin.label" + )}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.outlookAddin.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static officeXMLAddin(platform?: Platform): OptionItem { + return { + id: "office-xml-addin-type", + label: `${platform === Platform.VSCode ? "$(teamsfx-m365) " : ""}${getLocalizedString( + "core.createProjectQuestion.officeXMLAddin.mainEntry.title" + )}`, + detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.mainEntry.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static officeAddin(platform?: Platform): OptionItem { + return { + id: "office-addin-type", + label: `${platform === Platform.VSCode ? "$(extensions) " : ""}${getLocalizedString( + "core.createProjectQuestion.projectType.officeAddin.label" + )}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.officeAddin.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static officeAddinAllIds(platform?: Platform): string[] { + return [ + ProjectTypeOptions.officeAddin(platform).id, + ProjectTypeOptions.officeXMLAddin(platform).id, + ProjectTypeOptions.outlookAddin(platform).id, + ]; + } + + static copilotPlugin(platform?: Platform): OptionItem { + return { + id: "copilot-plugin-type", + label: `${ + platform === Platform.VSCode ? "$(teamsfx-copilot-plugin) " : "" + }${getLocalizedString("core.createProjectQuestion.projectType.copilotPlugin.label")}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.copilotPlugin.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static customCopilot(platform?: Platform): OptionItem { + return { + id: "custom-copilot-type", + label: `${ + platform === Platform.VSCode ? "$(teamsfx-custom-copilot) " : "" + }${getLocalizedString("core.createProjectQuestion.projectType.customCopilot.label")}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.customCopilot.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } + + static startWithGithubCopilot(): OptionItem { + return { + id: "start-with-github-copilot", + label: `$(comment-discussion) ${getLocalizedString( + "core.createProjectQuestion.projectType.copilotHelp.label" + )}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.copilotHelp.detail"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.copilotGroup.title"), + }; + } + + static customizeGpt(): OptionItem { + return { + id: "customize-gpt-type", + label: getLocalizedString("core.createProjectQuestion.projectType.declarativeCopilot.label"), + detail: getLocalizedString("core.createProjectQuestion.projectType.declarativeCopilot.title"), + groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), + }; + } +} + +export class CapabilityOptions { + // bot + static basicBot(): OptionItem { + return { + id: "bot", + label: `${getLocalizedString("core.BotNewUIOption.label")}`, + detail: getLocalizedString("core.BotNewUIOption.detail"), + }; + } + static notificationBot(): OptionItem { + return { + // For default option, id and cliName must be the same + id: "notification", + label: `${getLocalizedString("core.NotificationOption.label")}`, + detail: getLocalizedString("core.NotificationOption.detail"), + data: "https://aka.ms/teamsfx-send-notification", + buttons: [ + { + iconPath: "file-symlink-file", + tooltip: getLocalizedString("core.option.github"), + command: "fx-extension.openTutorial", + }, + ], + }; + } + + static commandBot(): OptionItem { + return { + // id must match cli `yargsHelp` + id: "command-bot", + label: `${getLocalizedString("core.CommandAndResponseOption.label")}`, + detail: getLocalizedString("core.CommandAndResponseOption.detail"), + data: "https://aka.ms/teamsfx-create-command", + buttons: [ + { + iconPath: "file-symlink-file", + tooltip: getLocalizedString("core.option.github"), + command: "fx-extension.openTutorial", + }, + ], + }; + } + + static workflowBot(inputs?: Inputs): OptionItem { + const item: OptionItem = { + // id must match cli `yargsHelp` + id: "workflow-bot", + label: `${getLocalizedString("core.WorkflowOption.label")}`, + detail: getLocalizedString("core.WorkflowOption.detail"), + data: "https://aka.ms/teamsfx-create-workflow", + buttons: [ + { + iconPath: "file-symlink-file", + tooltip: getLocalizedString("core.option.github"), + command: "fx-extension.openTutorial", + }, + ], + }; + if (inputs?.inProductDoc) { + item.data = "cardActionResponse"; + item.buttons = [ + { + iconPath: "file-code", + tooltip: getLocalizedString("core.option.inProduct"), + command: "fx-extension.openTutorial", + }, + ]; + } + return item; + } + + //tab + + static nonSsoTab(): OptionItem { + return { + id: "tab-non-sso", + label: `${getLocalizedString("core.TabNonSso.label")}`, + detail: getLocalizedString("core.TabNonSso.detail"), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlookM365" + ), + }; + } + + static tab(): OptionItem { + return { + id: "tab", + label: getLocalizedString("core.TabOption.label"), + description: getLocalizedString("core.TabOption.description"), + detail: getLocalizedString("core.TabOption.detail"), + }; + } + + static m365SsoLaunchPage(): OptionItem { + return { + id: "sso-launch-page", + label: `${getLocalizedString("core.M365SsoLaunchPageOptionItem.label")}`, + detail: getLocalizedString("core.M365SsoLaunchPageOptionItem.detail"), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlookM365" + ), + }; + } + + static dashboardTab(): OptionItem { + return { + id: "dashboard-tab", + label: `${getLocalizedString("core.DashboardOption.label")}`, + detail: getLocalizedString("core.DashboardOption.detail"), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlookM365" + ), + data: "https://aka.ms/teamsfx-dashboard-app", + buttons: [ + { + iconPath: "file-symlink-file", + tooltip: getLocalizedString("core.option.github"), + command: "fx-extension.openTutorial", + }, + ], + }; + } + + static SPFxTab(): OptionItem { + return { + id: "tab-spfx", + label: getLocalizedString("core.TabSPFxOption.labelNew"), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlookM365" + ), + detail: getLocalizedString("core.TabSPFxOption.detailNew"), + }; + } + + //message extension + static linkUnfurling(): OptionItem { + return { + id: "link-unfurling", + label: `${getLocalizedString("core.LinkUnfurlingOption.label")}`, + detail: getLocalizedString("core.LinkUnfurlingOption.detail"), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlook" + ), + }; + } + + static m365SearchMe(): OptionItem { + return { + id: "search-app", + label: `${getLocalizedString("core.M365SearchAppOptionItem.label")}`, + detail: isCopilotPluginEnabled() + ? getLocalizedString("core.M365SearchAppOptionItem.copilot.detail") + : getLocalizedString("core.M365SearchAppOptionItem.detail"), + }; + } + + static SearchMe(): OptionItem { + return { + id: "search-message-extension", + label: `${getLocalizedString("core.M365SearchAppOptionItem.label")}`, + detail: getLocalizedString("core.SearchAppOptionItem.detail"), + }; + } + + static collectFormMe(): OptionItem { + return { + id: "collect-form-message-extension", + label: `${getLocalizedString("core.MessageExtensionOption.labelNew")}`, + detail: getLocalizedString("core.MessageExtensionOption.detail"), + }; + } + static me(): OptionItem { + return { + id: "message-extension", + label: getLocalizedString("core.MessageExtensionOption.label"), + description: getLocalizedString("core.MessageExtensionOption.description"), + detail: getLocalizedString("core.MessageExtensionOption.detail"), + }; + } + static bots(inputs?: Inputs): OptionItem[] { + if (inputs?.platform === Platform.VS) { + return [ + CapabilityOptions.basicBot(), + CapabilityOptions.aiBot(), + CapabilityOptions.aiAssistantBot(), + CapabilityOptions.notificationBot(), + CapabilityOptions.commandBot(), + CapabilityOptions.workflowBot(inputs), + ]; + } + return [ + CapabilityOptions.basicBot(), + CapabilityOptions.notificationBot(), + CapabilityOptions.commandBot(), + CapabilityOptions.workflowBot(inputs), + ]; + } + + static tabs(): OptionItem[] { + return [ + CapabilityOptions.nonSsoTab(), + CapabilityOptions.m365SsoLaunchPage(), + CapabilityOptions.dashboardTab(), + CapabilityOptions.SPFxTab(), + ]; + } + + static dotnetCaps(inputs?: Inputs): OptionItem[] { + const capabilities = [ + ...CapabilityOptions.copilotPlugins(), + ...CapabilityOptions.bots(inputs), + CapabilityOptions.nonSsoTab(), + CapabilityOptions.tab(), + ...CapabilityOptions.collectMECaps(), + ]; + if (isTdpTemplateCliTestEnabled()) { + capabilities.push(CapabilityOptions.me()); + } + + return capabilities; + } + + /** + * Collect all capabilities for message extension, including dotnet and nodejs. + * @returns OptionItem[] capability list + */ + static collectMECaps(): OptionItem[] { + return [ + CapabilityOptions.m365SearchMe(), + CapabilityOptions.collectFormMe(), + CapabilityOptions.SearchMe(), + CapabilityOptions.linkUnfurling(), + ]; + } + + static mes(inputs?: Inputs): OptionItem[] { + return inputs !== undefined && getRuntime(inputs) === RuntimeOptions.DotNet().id + ? [ + CapabilityOptions.SearchMe(), + CapabilityOptions.collectFormMe(), + CapabilityOptions.linkUnfurling(), + ] + : [ + CapabilityOptions.m365SearchMe(), + CapabilityOptions.collectFormMe(), + CapabilityOptions.linkUnfurling(), + ]; + } + + static officeAddinStaticCapabilities(host?: string): OptionItem[] { + const items: OptionItem[] = []; + for (const h of Object.keys(OfficeAddinProjectConfig)) { + if (host && h !== host) continue; + const hostValue = OfficeAddinProjectConfig[h]; + for (const capability of Object.keys(hostValue)) { + const capabilityValue = hostValue[capability]; + items.push({ + id: capability, + label: getLocalizedString(capabilityValue.title), + detail: getLocalizedString(capabilityValue.detail), + }); + } + } + return items; + } + + static officeAddinDynamicCapabilities(projectType: string, host?: string): OptionItem[] { + const items: OptionItem[] = []; + const isOutlookAddin = projectType === ProjectTypeOptions.outlookAddin().id; + const isOfficeAddin = projectType === ProjectTypeOptions.officeAddin().id; + const isOfficeXMLAddinForOutlook = + projectType === ProjectTypeOptions.officeXMLAddin().id && + host === OfficeAddinHostOptions.outlook().id; + + const pushToItems = (option: any) => { + const capabilityValue = OfficeAddinProjectConfig.json[option]; + items.push({ + id: option, + label: getLocalizedString(capabilityValue.title), + detail: getLocalizedString(capabilityValue.detail), + }); + }; + + if (isOutlookAddin || isOfficeAddin || isOfficeXMLAddinForOutlook) { + pushToItems("json-taskpane"); + if (isOutlookAddin || isOfficeXMLAddinForOutlook) { + items.push(CapabilityOptions.outlookAddinImport()); + } else if (isOfficeAddin) { + items.push(CapabilityOptions.officeContentAddin()); + items.push(CapabilityOptions.officeAddinImport()); + } + } else { + if (host) { + const hostValue = OfficeAddinProjectConfig[host]; + for (const capability of Object.keys(hostValue)) { + const capabilityValue = hostValue[capability]; + items.push({ + id: capability, + label: getLocalizedString(capabilityValue.title), + detail: getLocalizedString(capabilityValue.detail), + }); + } + } + } + return items; + } + + static copilotPlugins(): OptionItem[] { + return [ + CapabilityOptions.copilotPluginNewApi(), + CapabilityOptions.copilotPluginApiSpec(), + // CapabilityOptions.copilotPluginOpenAIPlugin(), + ]; + } + + static customCopilots(): OptionItem[] { + return [ + CapabilityOptions.customCopilotBasic(), + CapabilityOptions.customCopilotRag(), + CapabilityOptions.customCopilotAssistant(), + ]; + } + + static tdpIntegrationCapabilities(): OptionItem[] { + // templates that are used by TDP integration only + return [ + CapabilityOptions.me(), + CapabilityOptions.botAndMe(), + CapabilityOptions.nonSsoTabAndBot(), + ]; + } + + static customizeGptOptions(): OptionItem[] { + return [CapabilityOptions.customizeGptBasic(), CapabilityOptions.customizeGptWithPlugin()]; + } + + /** + * static capability list, which does not depend on any feature flags + */ + static staticAll(inputs?: Inputs): OptionItem[] { + const capabilityOptions = [ + ...CapabilityOptions.bots(inputs), + ...CapabilityOptions.tabs(), + ...CapabilityOptions.collectMECaps(), + ...CapabilityOptions.copilotPlugins(), + ...CapabilityOptions.customCopilots(), + ...CapabilityOptions.tdpIntegrationCapabilities(), + ...CapabilityOptions.customizeGptOptions(), + ]; + capabilityOptions.push(...CapabilityOptions.officeAddinStaticCapabilities()); + return capabilityOptions; + } + + /** + * dynamic capability list, which depends on feature flags + */ + static all(inputs?: Inputs): OptionItem[] { + const capabilityOptions = [ + ...CapabilityOptions.bots(inputs), + ...CapabilityOptions.tabs(), + ...CapabilityOptions.collectMECaps(), + ]; + if (isApiCopilotPluginEnabled()) { + capabilityOptions.push(...CapabilityOptions.copilotPlugins()); + } + if (featureFlagManager.getBooleanValue(FeatureFlags.CustomizeGpt)) { + capabilityOptions.push(...CapabilityOptions.customizeGptOptions()); + } + capabilityOptions.push(...CapabilityOptions.customCopilots()); + if (isTdpTemplateCliTestEnabled()) { + // test templates that are used by TDP integration only + capabilityOptions.push(...CapabilityOptions.tdpIntegrationCapabilities()); + } + capabilityOptions.push( + ...CapabilityOptions.officeAddinDynamicCapabilities(inputs?.projectType, inputs?.host) + ); + return capabilityOptions; + } + + static outlookAddinImport(): OptionItem { + return { + id: "outlook-addin-import", + label: getLocalizedString("core.importAddin.label"), + detail: getLocalizedString("core.importAddin.detail"), + }; + } + + static officeAddinImport(): OptionItem { + return { + id: "office-addin-import", + label: getLocalizedString("core.importOfficeAddin.label"), + detail: getLocalizedString("core.importAddin.detail"), + description: getLocalizedString( + "core.createProjectQuestion.option.description.previewOnWindow" + ), + }; + } + + static officeContentAddin(): OptionItem { + return { + id: "office-content-addin", + label: getLocalizedString("core.officeContentAddin.label"), + detail: getLocalizedString("core.officeContentAddin.detail"), + }; + } + + static nonSsoTabAndBot(): OptionItem { + return { + id: "TabNonSsoAndBot", + label: "", // No need to set display name as this option won't be shown in UI + }; + } + + static botAndMe(): OptionItem { + return { + id: "BotAndMessageExtension", + label: "", // No need to set display name as this option won't be shown in UI + }; + } + + // copilot plugin + static copilotPluginNewApi(): OptionItem { + return { + id: copilotPluginNewApiOptionId, + label: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginNewApiOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginNewApiOption.detail" + ), + }; + } + + static copilotPluginApiSpec(): OptionItem { + return { + id: copilotPluginApiSpecOptionId, + label: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginApiSpecOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginApiSpecOption.detail" + ), + }; + } + + static copilotPluginOpenAIPlugin(): OptionItem { + return { + id: copilotPluginOpenAIPluginOptionId, + label: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginAIPluginOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginAIPluginOption.detail" + ), + }; + } + + static aiBot(): OptionItem { + return { + id: "ai-bot", + label: getLocalizedString("core.aiBotOption.label"), + detail: getLocalizedString("core.aiBotOption.detail"), + }; + } + + static aiAssistantBot(): OptionItem { + return { + id: "ai-assistant-bot", + label: getLocalizedString("core.aiAssistantBotOption.label"), + detail: getLocalizedString("core.aiAssistantBotOption.detail"), + description: getLocalizedString("core.createProjectQuestion.option.description.preview"), + }; + } + + // custom copilot + static customCopilotBasic(): OptionItem { + return { + id: "custom-copilot-basic", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotBasicOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotBasicOption.detail" + ), + }; + } + + static customCopilotRag(): OptionItem { + return { + id: "custom-copilot-rag", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagOption.detail" + ), + }; + } + + static customCopilotAssistant(): OptionItem { + return { + id: "custom-copilot-agent", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotAssistantOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotAssistantOption.detail" + ), + }; + } + + // customize GPT + static customizeGptBasic(): OptionItem { + return { + id: "basic-declarative-copilot", + label: getLocalizedString( + "core.createProjectQuestion.capability.declarativeCopilotBasic.title" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.declarativeCopilotBasic.detail" + ), + }; + } + + static customizeGptWithPlugin(): OptionItem { + return { + id: "declarative-copilot-with-plugin-from-scratch", + label: getLocalizedString( + "core.createProjectQuestion.capability.declarativeCopilotWithPlugin.title" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.declarativeCopilotWithPlugin.detail" + ), + }; + } +} + +export class OfficeAddinHostOptions { + static all(platform?: Platform): OptionItem[] { + return [ + OfficeAddinHostOptions.outlook(platform), + OfficeAddinHostOptions.word(), + OfficeAddinHostOptions.excel(), + OfficeAddinHostOptions.powerpoint(), + ]; + } + static outlook(platform?: Platform): OptionItem { + return { + id: "outlook", + label: `${platform === Platform.VSCode ? "$(mail) " : ""}${getLocalizedString( + "core.createProjectQuestion.projectType.outlookAddin.label" + )}`, + detail: getLocalizedString("core.createProjectQuestion.projectType.outlookAddin.detail"), + data: "Outlook", + }; + } + static word(): OptionItem { + return { + id: "word", + label: getLocalizedString("core.createProjectQuestion.officeXMLAddin.word.title"), + detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.word.detail"), + data: "Word", + }; + } + + static excel(): OptionItem { + return { + id: "excel", + label: getLocalizedString("core.createProjectQuestion.officeXMLAddin.excel.title"), + detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.excel.detail"), + data: "Excel", + }; + } + + static powerpoint(): OptionItem { + return { + id: "powerpoint", + label: getLocalizedString("core.createProjectQuestion.officeXMLAddin.powerpoint.title"), + detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.powerpoint.detail"), + data: "PowerPoint", + }; + } +} + +export class ApiMessageExtensionAuthOptions { + static none(): OptionItem { + return { + id: "none", + label: "None", + }; + } + static apiKey(): OptionItem { + return { + id: "api-key", + label: "API Key", + }; + } + + static microsoftEntra(): OptionItem { + return { + id: "microsoft-entra", + label: "Microsoft Entra", + }; + } + + static all(): OptionItem[] { + return [ + ApiMessageExtensionAuthOptions.none(), + ApiMessageExtensionAuthOptions.apiKey(), + ApiMessageExtensionAuthOptions.microsoftEntra(), + ]; + } +} + +export class MeArchitectureOptions { + static botMe(): OptionItem { + return { + id: "bot", + label: getLocalizedString("core.createProjectQuestion.capability.botMessageExtension.label"), + detail: getLocalizedString( + "core.createProjectQuestion.capability.botMessageExtension.detail" + ), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlook" + ), + }; + } + + static botPlugin(): OptionItem { + return { + id: "bot-plugin", + label: getLocalizedString("core.createProjectQuestion.capability.botMessageExtension.label"), + detail: getLocalizedString( + "core.createProjectQuestion.capability.botMessageExtension.detail" + ), + description: getLocalizedString( + "core.createProjectQuestion.option.description.worksInOutlookCopilot" + ), + }; + } + + static newApi(): OptionItem { + return { + id: "new-api", + label: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginNewApiOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.messageExtensionNewApiOption.detail" + ), + }; + } + + static apiSpec(): OptionItem { + return { + id: "api-spec", + label: getLocalizedString( + "core.createProjectQuestion.capability.copilotPluginApiSpecOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.messageExtensionApiSpecOption.detail" + ), + }; + } + + static all(): OptionItem[] { + return [ + MeArchitectureOptions.newApi(), + MeArchitectureOptions.apiSpec(), + isCopilotPluginEnabled() ? MeArchitectureOptions.botPlugin() : MeArchitectureOptions.botMe(), + ]; + } + + static staticAll(): OptionItem[] { + return [ + MeArchitectureOptions.newApi(), + MeArchitectureOptions.apiSpec(), + MeArchitectureOptions.botPlugin(), + MeArchitectureOptions.botMe(), + ]; + } +} + +export enum HostType { + AppService = "app-service", + Functions = "azure-functions", +} + +export const NotificationTriggers = { + HTTP: "http", + TIMER: "timer", +} as const; + +type NotificationTrigger = typeof NotificationTriggers[keyof typeof NotificationTriggers]; + +interface HostTypeTriggerOptionItem extends OptionItem { + hostType: HostType; + triggers?: NotificationTrigger[]; +} + +export class NotificationTriggerOptions { + static appService(): HostTypeTriggerOptionItem { + return { + id: "http-restify", + hostType: HostType.AppService, + label: getLocalizedString("plugins.bot.triggers.http-restify.label"), + description: getLocalizedString("plugins.bot.triggers.http-restify.description"), + detail: getLocalizedString("plugins.bot.triggers.http-restify.detail"), + }; + } + static appServiceForVS(): HostTypeTriggerOptionItem { + return { + id: "http-webapi", + hostType: HostType.AppService, + label: getLocalizedString("plugins.bot.triggers.http-webapi.label"), + description: getLocalizedString("plugins.bot.triggers.http-webapi.description"), + detail: getLocalizedString("plugins.bot.triggers.http-webapi.detail"), + }; + } + // NOTE: id must be the sample as cliName to prevent parsing error for CLI default value. + static functionsTimerTrigger(): HostTypeTriggerOptionItem { + return { + id: "timer-functions", + hostType: HostType.Functions, + triggers: [NotificationTriggers.TIMER], + label: getLocalizedString("plugins.bot.triggers.timer-functions.label"), + description: getLocalizedString("plugins.bot.triggers.timer-functions.description"), + detail: getLocalizedString("plugins.bot.triggers.timer-functions.detail"), + }; + } + + static functionsTimerTriggerIsolated(): HostTypeTriggerOptionItem { + return { + id: "timer-functions-isolated", + hostType: HostType.Functions, + triggers: [NotificationTriggers.TIMER], + label: getLocalizedString("plugins.bot.triggers.timer-functions.label"), + description: getLocalizedString("plugins.bot.triggers.timer-functions.description"), + detail: getLocalizedString("plugins.bot.triggers.timer-functions.detail"), + }; + } + + static functionsHttpAndTimerTrigger(): HostTypeTriggerOptionItem { + return { + id: "http-and-timer-functions", + hostType: HostType.Functions, + triggers: [NotificationTriggers.HTTP, NotificationTriggers.TIMER], + label: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.label"), + description: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.description"), + detail: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.detail"), + }; + } + + static functionsHttpAndTimerTriggerIsolated(): HostTypeTriggerOptionItem { + return { + id: "http-and-timer-functions-isolated", + hostType: HostType.Functions, + triggers: [NotificationTriggers.HTTP, NotificationTriggers.TIMER], + label: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.label"), + description: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.description"), + detail: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.detail"), + }; + } + + static functionsHttpTrigger(): HostTypeTriggerOptionItem { + return { + id: "http-functions", + hostType: HostType.Functions, + triggers: [NotificationTriggers.HTTP], + label: getLocalizedString("plugins.bot.triggers.http-functions.label"), + description: getLocalizedString("plugins.bot.triggers.http-functions.description"), + detail: getLocalizedString("plugins.bot.triggers.http-functions.detail"), + }; + } + + static functionsHttpTriggerIsolated(): HostTypeTriggerOptionItem { + return { + id: "http-functions-isolated", + hostType: HostType.Functions, + triggers: [NotificationTriggers.HTTP], + label: getLocalizedString("plugins.bot.triggers.http-functions.label"), + description: getLocalizedString("plugins.bot.triggers.http-functions.description"), + detail: getLocalizedString("plugins.bot.triggers.http-functions.detail"), + }; + } + + static functionsTriggers(): HostTypeTriggerOptionItem[] { + return [ + NotificationTriggerOptions.functionsHttpAndTimerTrigger(), + NotificationTriggerOptions.functionsHttpTrigger(), + NotificationTriggerOptions.functionsTimerTrigger(), + ]; + } + + static all(): HostTypeTriggerOptionItem[] { + return [ + NotificationTriggerOptions.appService(), + NotificationTriggerOptions.appServiceForVS(), + NotificationTriggerOptions.functionsHttpAndTimerTrigger(), + NotificationTriggerOptions.functionsHttpTrigger(), + NotificationTriggerOptions.functionsTimerTrigger(), + ]; + } +} + +export enum SPFxVersionOptionIds { + installLocally = "true", + globalPackage = "false", +} + +export class CustomCopilotRagOptions { + static customize(): OptionItem { + return { + id: "custom-copilot-rag-customize", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagCustomizeOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagCustomizeOption.detail" + ), + }; + } + + static azureAISearch(): OptionItem { + return { + id: "custom-copilot-rag-azureAISearch", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagAzureAISearchOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagAzureAISearchOption.detail" + ), + }; + } + + static customApi(): OptionItem { + return { + id: "custom-copilot-rag-customApi", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagCustomApiOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagCustomApiOption.detail" + ), + description: getLocalizedString("core.createProjectQuestion.option.description.preview"), + }; + } + + static microsoft365(): OptionItem { + return { + id: "custom-copilot-rag-microsoft365", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagMicrosoft365Option.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotRagMicrosoft365Option.detail" + ), + }; + } + + static all(): OptionItem[] { + return [ + CustomCopilotRagOptions.customize(), + CustomCopilotRagOptions.azureAISearch(), + CustomCopilotRagOptions.customApi(), + CustomCopilotRagOptions.microsoft365(), + ]; + } +} + +export class CustomCopilotAssistantOptions { + static new(): OptionItem { + return { + id: "custom-copilot-agent-new", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotAssistantNewOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotAssistantNewOption.detail" + ), + }; + } + + static assistantsApi(): OptionItem { + return { + id: "custom-copilot-agent-assistants-api", + label: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotAssistantAssistantsApiOption.label" + ), + detail: getLocalizedString( + "core.createProjectQuestion.capability.customCopilotAssistantAssistantsApiOption.detail" + ), + description: getLocalizedString("core.createProjectQuestion.option.description.preview"), + }; + } + + static all(): OptionItem[] { + return [CustomCopilotAssistantOptions.new(), CustomCopilotAssistantOptions.assistantsApi()]; + } +} + +export const recommendedLocations = [ + "South Africa North", + "Australia East", + "Central India", + "East Asia", + "Japan East", + "Korea Central", + "Southeast Asia", + "Canada Central", + "France Central", + "Germany West Central", + "Italy North", + "North Europe", + "Norway East", + "Poland Central", + "Sweden Central", + "Switzerland North", + "UK South", + "West Europe", + "Israel Central", + "Qatar Central", + "UAE North", + "Brazil South", + "Central US", + "East US", + "East US 2", + "South Central US", + "West US 2", + "West US 3", +]; + +export class PluginAvailabilityOptions { + static action(): OptionItem { + return { + id: "action", + label: getLocalizedString("core.pluginAvailability.declarativeCopilot"), + }; + } + static copilotPlugin(): OptionItem { + return { + id: "copilot-plugin", + label: getLocalizedString("core.pluginAvailability.copilotForM365"), + }; + } + static copilotPluginAndAction(): OptionItem { + return { + id: "copilot-plugin-and-action", + label: getLocalizedString("core.pluginAvailability.declarativeCopilotAndM365"), + }; + } + + static all(): OptionItem[] { + return [ + PluginAvailabilityOptions.copilotPlugin(), + PluginAvailabilityOptions.action(), + PluginAvailabilityOptions.copilotPluginAndAction(), + ]; + } +} + +export class TeamsAppValidationOptions { + static schema(): OptionItem { + return { + id: "validateAgainstSchema", + label: getLocalizedString("core.selectValidateMethodQuestion.validate.schemaOption"), + description: getLocalizedString( + "core.selectValidateMethodQuestion.validate.schemaOptionDescription" + ), + }; + } + static package(): OptionItem { + return { + id: "validateAgainstPackage", + label: getLocalizedString("core.selectValidateMethodQuestion.validate.appPackageOption"), + description: getLocalizedString( + "core.selectValidateMethodQuestion.validate.appPackageOptionDescription" + ), + }; + } + static testCases(): OptionItem { + return { + id: "validateWithTestCases", + label: getLocalizedString("core.selectValidateMethodQuestion.validate.testCasesOption"), + description: getLocalizedString( + "core.selectValidateMethodQuestion.validate.testCasesOptionDescription" + ), + }; + } +} + +export enum HubTypes { + teams = "teams", + outlook = "outlook", + office = "office", +} + +export class HubOptions { + static teams(): OptionItem { + return { + id: "teams", + label: "Teams", + }; + } + static outlook(): OptionItem { + return { + id: "outlook", + label: "Outlook", + }; + } + static office(): OptionItem { + return { + id: "office", + label: "the Microsoft 365 app", + }; + } + static all(): OptionItem[] { + return [this.teams(), this.outlook(), this.office()]; + } +} diff --git a/packages/fx-core/src/question/create.ts b/packages/fx-core/src/question/create.ts index 35e70ee7dd1..1566ba16369 100644 --- a/packages/fx-core/src/question/create.ts +++ b/packages/fx-core/src/question/create.ts @@ -29,27 +29,29 @@ import { isApiCopilotPluginEnabled, isCLIDotNetEnabled, isChatParticipantEnabled, - isCopilotPluginEnabled, isOfficeJSONAddinEnabled, - isTdpTemplateCliTestEnabled, } from "../common/featureFlags"; import { getLocalizedString } from "../common/localizeUtils"; import { sampleProvider } from "../common/samples"; -import { convertToAlphanumericOnly } from "../common/utils"; -import { - getProjectTypeAndCapability, - isFromDevPortal, -} from "../component/developerPortalScaffoldUtils"; +import { convertToAlphanumericOnly } from "../common/stringUtils"; import { AppDefinition } from "../component/driver/teamsApp/interfaces/appdefinitions/appDefinition"; import { StaticTab } from "../component/driver/teamsApp/interfaces/appdefinitions/staticTab"; -import { isPersonalApp, needBotCode } from "../component/driver/teamsApp/utils/utils"; +import { + isBot, + isBotAndBotBasedMessageExtension, + isBotBasedMessageExtension, + isPersonalApp, + needBotCode, + needTabAndBotCode, + needTabCode, +} from "../component/driver/teamsApp/utils/utils"; import { OpenAIPluginManifestHelper, listOperations, } from "../component/generator/copilotPlugin/helper"; import { + IOfficeAddinHostConfig, OfficeAddinProjectConfig, - getOfficeAddinTemplateConfig, } from "../component/generator/officeXMLAddin/projectConfig"; import { DevEnvironmentSetupError } from "../component/generator/spfx/error"; import { Constants } from "../component/generator/spfx/utils/constants"; @@ -57,156 +59,25 @@ import { Utils } from "../component/generator/spfx/utils/utils"; import { createContextV3 } from "../component/utils"; import { EmptyOptionError, FileNotFoundError, assembleError } from "../error"; import { + ApiMessageExtensionAuthOptions, + AppNamePattern, + CapabilityOptions, + CliQuestionName, + CustomCopilotAssistantOptions, + CustomCopilotRagOptions, + MeArchitectureOptions, + NotificationTriggerOptions, + OfficeAddinHostOptions, + ProgrammingLanguage, + ProjectTypeOptions, + QuestionNames, + RuntimeOptions, + SPFxVersionOptionIds, capabilitiesHavePythonOption, - copilotPluginApiSpecOptionId, - copilotPluginNewApiOptionId, - copilotPluginOpenAIPluginOptionId, + getRuntime, } from "./constants"; -import { CliQuestionName, QuestionNames } from "./questionNames"; import { isValidHttpUrl } from "./util"; -export class ScratchOptions { - static yes(): OptionItem { - return { - id: "yes", - label: getLocalizedString("core.ScratchOptionYes.label"), - detail: getLocalizedString("core.ScratchOptionYes.detail"), - }; - } - static no(): OptionItem { - return { - id: "no", - label: getLocalizedString("core.ScratchOptionNo.label"), - detail: getLocalizedString("core.ScratchOptionNo.detail"), - }; - } - static all(): OptionItem[] { - return [ScratchOptions.yes(), ScratchOptions.no()]; - } -} - -export class ProjectTypeOptions { - static tab(platform?: Platform): OptionItem { - return { - id: "tab-type", - label: `${platform === Platform.VSCode ? "$(browser) " : ""}${getLocalizedString( - "core.TabOption.label" - )}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.tab.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static bot(platform?: Platform): OptionItem { - return { - id: "bot-type", - label: `${platform === Platform.VSCode ? "$(hubot) " : ""}${getLocalizedString( - "core.createProjectQuestion.projectType.bot.label" - )}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.bot.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static me(platform?: Platform): OptionItem { - return { - id: "me-type", - label: `${platform === Platform.VSCode ? "$(symbol-keyword) " : ""}${getLocalizedString( - "core.MessageExtensionOption.label" - )}`, - detail: isCopilotPluginEnabled() - ? getLocalizedString( - "core.createProjectQuestion.projectType.messageExtension.copilotEnabled.detail" - ) - : getLocalizedString("core.createProjectQuestion.projectType.messageExtension.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static outlookAddin(platform?: Platform): OptionItem { - return { - id: "outlook-addin-type", - label: `${platform === Platform.VSCode ? "$(mail) " : ""}${getLocalizedString( - "core.createProjectQuestion.projectType.outlookAddin.label" - )}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.outlookAddin.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static officeXMLAddin(platform?: Platform): OptionItem { - return { - id: "office-xml-addin-type", - label: `${platform === Platform.VSCode ? "$(teamsfx-m365) " : ""}${getLocalizedString( - "core.createProjectQuestion.officeXMLAddin.mainEntry.title" - )}`, - detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.mainEntry.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static officeAddin(platform?: Platform): OptionItem { - return { - id: "office-addin-type", - label: `${platform === Platform.VSCode ? "$(extensions) " : ""}${getLocalizedString( - "core.createProjectQuestion.projectType.officeAddin.label" - )}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.officeAddin.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static officeAddinAllIds(platform?: Platform): string[] { - return [ - ProjectTypeOptions.officeAddin(platform).id, - ProjectTypeOptions.officeXMLAddin(platform).id, - ProjectTypeOptions.outlookAddin(platform).id, - ]; - } - - static copilotPlugin(platform?: Platform): OptionItem { - return { - id: "copilot-plugin-type", - label: `${ - platform === Platform.VSCode ? "$(teamsfx-copilot-plugin) " : "" - }${getLocalizedString("core.createProjectQuestion.projectType.copilotPlugin.label")}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.copilotPlugin.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static customCopilot(platform?: Platform): OptionItem { - return { - id: "custom-copilot-type", - label: `${ - platform === Platform.VSCode ? "$(teamsfx-custom-copilot) " : "" - }${getLocalizedString("core.createProjectQuestion.projectType.customCopilot.label")}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.customCopilot.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } - - static startWithGithubCopilot(): OptionItem { - return { - id: "start-with-github-copilot", - label: `$(comment-discussion) ${getLocalizedString( - "core.createProjectQuestion.projectType.copilotHelp.label" - )}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.copilotHelp.detail"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.copilotGroup.title"), - }; - } - - static customizeGpt(): OptionItem { - return { - id: "customize-gpt-type", - label: getLocalizedString("core.createProjectQuestion.projectType.declarativeCopilot.label"), - detail: getLocalizedString("core.createProjectQuestion.projectType.declarativeCopilot.title"), - groupName: getLocalizedString("core.createProjectQuestion.projectType.createGroup.title"), - }; - } -} - export function projectTypeQuestion(): SingleSelectQuestion { const staticOptions: StaticOptions = [ ProjectTypeOptions.bot(Platform.CLI), @@ -275,617 +146,40 @@ export function projectTypeQuestion(): SingleSelectQuestion { }; } -export class OfficeAddinHostOptions { - static all(platform?: Platform): OptionItem[] { - return [ - OfficeAddinHostOptions.outlook(platform), - OfficeAddinHostOptions.word(), - OfficeAddinHostOptions.excel(), - OfficeAddinHostOptions.powerpoint(), - ]; - } - static outlook(platform?: Platform): OptionItem { - return { - id: "outlook", - label: `${platform === Platform.VSCode ? "$(mail) " : ""}${getLocalizedString( - "core.createProjectQuestion.projectType.outlookAddin.label" - )}`, - detail: getLocalizedString("core.createProjectQuestion.projectType.outlookAddin.detail"), - data: "Outlook", - }; - } - static word(): OptionItem { - return { - id: "word", - label: getLocalizedString("core.createProjectQuestion.officeXMLAddin.word.title"), - detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.word.detail"), - data: "Word", - }; - } - - static excel(): OptionItem { - return { - id: "excel", - label: getLocalizedString("core.createProjectQuestion.officeXMLAddin.excel.title"), - detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.excel.detail"), - data: "Excel", - }; - } - - static powerpoint(): OptionItem { - return { - id: "powerpoint", - label: getLocalizedString("core.createProjectQuestion.officeXMLAddin.powerpoint.title"), - detail: getLocalizedString("core.createProjectQuestion.officeXMLAddin.powerpoint.detail"), - data: "PowerPoint", - }; - } -} - -export class CapabilityOptions { - // bot - static basicBot(): OptionItem { - return { - id: "bot", - label: `${getLocalizedString("core.BotNewUIOption.label")}`, - detail: getLocalizedString("core.BotNewUIOption.detail"), - }; - } - static notificationBot(): OptionItem { - return { - // For default option, id and cliName must be the same - id: "notification", - label: `${getLocalizedString("core.NotificationOption.label")}`, - detail: getLocalizedString("core.NotificationOption.detail"), - data: "https://aka.ms/teamsfx-send-notification", - buttons: [ - { - iconPath: "file-symlink-file", - tooltip: getLocalizedString("core.option.github"), - command: "fx-extension.openTutorial", - }, - ], - }; - } - - static commandBot(): OptionItem { - return { - // id must match cli `yargsHelp` - id: "command-bot", - label: `${getLocalizedString("core.CommandAndResponseOption.label")}`, - detail: getLocalizedString("core.CommandAndResponseOption.detail"), - data: "https://aka.ms/teamsfx-create-command", - buttons: [ - { - iconPath: "file-symlink-file", - tooltip: getLocalizedString("core.option.github"), - command: "fx-extension.openTutorial", - }, - ], - }; - } - - static workflowBot(inputs?: Inputs): OptionItem { - const item: OptionItem = { - // id must match cli `yargsHelp` - id: "workflow-bot", - label: `${getLocalizedString("core.WorkflowOption.label")}`, - detail: getLocalizedString("core.WorkflowOption.detail"), - data: "https://aka.ms/teamsfx-create-workflow", - buttons: [ - { - iconPath: "file-symlink-file", - tooltip: getLocalizedString("core.option.github"), - command: "fx-extension.openTutorial", - }, - ], - }; - if (inputs?.inProductDoc) { - item.data = "cardActionResponse"; - item.buttons = [ - { - iconPath: "file-code", - tooltip: getLocalizedString("core.option.inProduct"), - command: "fx-extension.openTutorial", - }, - ]; - } - return item; - } - - //tab - - static nonSsoTab(): OptionItem { - return { - id: "tab-non-sso", - label: `${getLocalizedString("core.TabNonSso.label")}`, - detail: getLocalizedString("core.TabNonSso.detail"), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlookM365" - ), - }; - } - - static tab(): OptionItem { - return { - id: "tab", - label: getLocalizedString("core.TabOption.label"), - description: getLocalizedString("core.TabOption.description"), - detail: getLocalizedString("core.TabOption.detail"), - }; - } - - static m365SsoLaunchPage(): OptionItem { - return { - id: "sso-launch-page", - label: `${getLocalizedString("core.M365SsoLaunchPageOptionItem.label")}`, - detail: getLocalizedString("core.M365SsoLaunchPageOptionItem.detail"), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlookM365" - ), - }; - } - - static dashboardTab(): OptionItem { - return { - id: "dashboard-tab", - label: `${getLocalizedString("core.DashboardOption.label")}`, - detail: getLocalizedString("core.DashboardOption.detail"), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlookM365" - ), - data: "https://aka.ms/teamsfx-dashboard-app", - buttons: [ - { - iconPath: "file-symlink-file", - tooltip: getLocalizedString("core.option.github"), - command: "fx-extension.openTutorial", - }, - ], - }; - } - - static SPFxTab(): OptionItem { - return { - id: "tab-spfx", - label: getLocalizedString("core.TabSPFxOption.labelNew"), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlookM365" - ), - detail: getLocalizedString("core.TabSPFxOption.detailNew"), - }; - } - - //message extension - static linkUnfurling(): OptionItem { - return { - id: "link-unfurling", - label: `${getLocalizedString("core.LinkUnfurlingOption.label")}`, - detail: getLocalizedString("core.LinkUnfurlingOption.detail"), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlook" - ), - }; - } - - static m365SearchMe(): OptionItem { - return { - id: "search-app", - label: `${getLocalizedString("core.M365SearchAppOptionItem.label")}`, - detail: isCopilotPluginEnabled() - ? getLocalizedString("core.M365SearchAppOptionItem.copilot.detail") - : getLocalizedString("core.M365SearchAppOptionItem.detail"), - }; - } - - static SearchMe(): OptionItem { - return { - id: "search-message-extension", - label: `${getLocalizedString("core.M365SearchAppOptionItem.label")}`, - detail: getLocalizedString("core.SearchAppOptionItem.detail"), - }; - } - - static collectFormMe(): OptionItem { - return { - id: "collect-form-message-extension", - label: `${getLocalizedString("core.MessageExtensionOption.labelNew")}`, - detail: getLocalizedString("core.MessageExtensionOption.detail"), - }; - } - static me(): OptionItem { - return { - id: "message-extension", - label: getLocalizedString("core.MessageExtensionOption.label"), - description: getLocalizedString("core.MessageExtensionOption.description"), - detail: getLocalizedString("core.MessageExtensionOption.detail"), - }; - } - static bots(inputs?: Inputs): OptionItem[] { - if (inputs?.platform === Platform.VS) { - return [ - CapabilityOptions.basicBot(), - CapabilityOptions.aiBot(), - CapabilityOptions.aiAssistantBot(), - CapabilityOptions.notificationBot(), - CapabilityOptions.commandBot(), - CapabilityOptions.workflowBot(inputs), - ]; - } - return [ - CapabilityOptions.basicBot(), - CapabilityOptions.notificationBot(), - CapabilityOptions.commandBot(), - CapabilityOptions.workflowBot(inputs), - ]; - } - - static tabs(): OptionItem[] { - return [ - CapabilityOptions.nonSsoTab(), - CapabilityOptions.m365SsoLaunchPage(), - CapabilityOptions.dashboardTab(), - CapabilityOptions.SPFxTab(), - ]; - } - - static dotnetCaps(inputs?: Inputs): OptionItem[] { - const capabilities = [ - ...CapabilityOptions.copilotPlugins(), - ...CapabilityOptions.bots(inputs), - CapabilityOptions.nonSsoTab(), - CapabilityOptions.tab(), - ...CapabilityOptions.collectMECaps(), - ]; - if (isTdpTemplateCliTestEnabled()) { - capabilities.push(CapabilityOptions.me()); - } - - return capabilities; - } - - /** - * Collect all capabilities for message extension, including dotnet and nodejs. - * @returns OptionItem[] capability list - */ - static collectMECaps(): OptionItem[] { - return [ - CapabilityOptions.m365SearchMe(), - CapabilityOptions.collectFormMe(), - CapabilityOptions.SearchMe(), - CapabilityOptions.linkUnfurling(), - ]; - } - - static mes(inputs?: Inputs): OptionItem[] { - return inputs !== undefined && getRuntime(inputs) === RuntimeOptions.DotNet().id - ? [ - CapabilityOptions.SearchMe(), - CapabilityOptions.collectFormMe(), - CapabilityOptions.linkUnfurling(), - ] - : [ - CapabilityOptions.m365SearchMe(), - CapabilityOptions.collectFormMe(), - CapabilityOptions.linkUnfurling(), - ]; - } - - static officeAddinStaticCapabilities(host?: string): OptionItem[] { - const items: OptionItem[] = []; - for (const h of Object.keys(OfficeAddinProjectConfig)) { - if (host && h !== host) continue; - const hostValue = OfficeAddinProjectConfig[h]; - for (const capability of Object.keys(hostValue)) { - const capabilityValue = hostValue[capability]; - items.push({ - id: capability, - label: getLocalizedString(capabilityValue.title), - detail: getLocalizedString(capabilityValue.detail), - }); - } - } - return items; - } - - static officeAddinDynamicCapabilities(projectType: string, host?: string): OptionItem[] { - const items: OptionItem[] = []; - const isOutlookAddin = projectType === ProjectTypeOptions.outlookAddin().id; - const isOfficeAddin = projectType === ProjectTypeOptions.officeAddin().id; - const isOfficeXMLAddinForOutlook = - projectType === ProjectTypeOptions.officeXMLAddin().id && - host === OfficeAddinHostOptions.outlook().id; - - const pushToItems = (option: any) => { - const capabilityValue = OfficeAddinProjectConfig.json[option]; - items.push({ - id: option, - label: getLocalizedString(capabilityValue.title), - detail: getLocalizedString(capabilityValue.detail), - }); - }; - - if (isOutlookAddin || isOfficeAddin || isOfficeXMLAddinForOutlook) { - pushToItems("json-taskpane"); - if (isOutlookAddin || isOfficeXMLAddinForOutlook) { - items.push(CapabilityOptions.outlookAddinImport()); - } else if (isOfficeAddin) { - items.push(CapabilityOptions.officeContentAddin()); - items.push(CapabilityOptions.officeAddinImport()); - } - } else { - if (host) { - const hostValue = OfficeAddinProjectConfig[host]; - for (const capability of Object.keys(hostValue)) { - const capabilityValue = hostValue[capability]; - items.push({ - id: capability, - label: getLocalizedString(capabilityValue.title), - detail: getLocalizedString(capabilityValue.detail), - }); - } - } - } - return items; - } - - static copilotPlugins(): OptionItem[] { - return [ - CapabilityOptions.copilotPluginNewApi(), - CapabilityOptions.copilotPluginApiSpec(), - // CapabilityOptions.copilotPluginOpenAIPlugin(), - ]; - } - - static customCopilots(): OptionItem[] { - return [ - CapabilityOptions.customCopilotBasic(), - CapabilityOptions.customCopilotRag(), - CapabilityOptions.customCopilotAssistant(), - ]; +export function getProjectTypeAndCapability( + teamsApp: AppDefinition +): { projectType: string; templateId: string } | undefined { + // tab with bot, tab with message extension, tab with bot and message extension + if (needTabAndBotCode(teamsApp)) { + return { projectType: "tab-bot-type", templateId: CapabilityOptions.nonSsoTabAndBot().id }; } - static tdpIntegrationCapabilities(): OptionItem[] { - // templates that are used by TDP integration only - return [ - CapabilityOptions.me(), - CapabilityOptions.botAndMe(), - CapabilityOptions.nonSsoTabAndBot(), - ]; + // tab only + if (needTabCode(teamsApp)) { + return { projectType: "tab-type", templateId: CapabilityOptions.nonSsoTab().id }; } - static customizeGptOptions(): OptionItem[] { - return [CapabilityOptions.customizeGptBasic(), CapabilityOptions.customizeGptWithPlugin()]; + // bot and message extension + if (isBotAndBotBasedMessageExtension(teamsApp)) { + return { projectType: "bot-me-type", templateId: CapabilityOptions.botAndMe().id }; } - /** - * static capability list, which does not depend on any feature flags - */ - static staticAll(inputs?: Inputs): OptionItem[] { - const capabilityOptions = [ - ...CapabilityOptions.bots(inputs), - ...CapabilityOptions.tabs(), - ...CapabilityOptions.collectMECaps(), - ...CapabilityOptions.copilotPlugins(), - ...CapabilityOptions.customCopilots(), - ...CapabilityOptions.tdpIntegrationCapabilities(), - ...CapabilityOptions.customizeGptOptions(), - ]; - capabilityOptions.push(...CapabilityOptions.officeAddinStaticCapabilities()); - return capabilityOptions; + // bot based message extension + if (isBotBasedMessageExtension(teamsApp)) { + return { projectType: "me-type", templateId: CapabilityOptions.me().id }; } - /** - * dynamic capability list, which depends on feature flags - */ - static all(inputs?: Inputs): OptionItem[] { - const capabilityOptions = [ - ...CapabilityOptions.bots(inputs), - ...CapabilityOptions.tabs(), - ...CapabilityOptions.collectMECaps(), - ]; - if (isApiCopilotPluginEnabled()) { - capabilityOptions.push(...CapabilityOptions.copilotPlugins()); - } - if (featureFlagManager.getBooleanValue(FeatureFlags.CustomizeGpt)) { - capabilityOptions.push(...CapabilityOptions.customizeGptOptions()); - } - capabilityOptions.push(...CapabilityOptions.customCopilots()); - if (isTdpTemplateCliTestEnabled()) { - // test templates that are used by TDP integration only - capabilityOptions.push(...CapabilityOptions.tdpIntegrationCapabilities()); - } - capabilityOptions.push( - ...CapabilityOptions.officeAddinDynamicCapabilities(inputs?.projectType, inputs?.host) - ); - return capabilityOptions; - } - - static outlookAddinImport(): OptionItem { - return { - id: "outlook-addin-import", - label: getLocalizedString("core.importAddin.label"), - detail: getLocalizedString("core.importAddin.detail"), - }; - } - - static officeAddinImport(): OptionItem { - return { - id: "office-addin-import", - label: getLocalizedString("core.importOfficeAddin.label"), - detail: getLocalizedString("core.importAddin.detail"), - description: getLocalizedString( - "core.createProjectQuestion.option.description.previewOnWindow" - ), - }; - } - - static officeContentAddin(): OptionItem { - return { - id: "office-content-addin", - label: getLocalizedString("core.officeContentAddin.label"), - detail: getLocalizedString("core.officeContentAddin.detail"), - }; - } - - // static officeXMLAddinHostOptionItems(host: string): OptionItem[] { - // return getOfficeXMLAddinHostProjectOptions(host).map((x) => ({ - // id: x.proj, - // label: getLocalizedString(x.title), - // detail: getLocalizedString(x.detail), - // })); - // } - - // static jsonAddinTaskpane(): OptionItem { - // return { - // id: "json-taskpane", - // label: getLocalizedString("core.newTaskpaneAddin.label"), - // detail: getLocalizedString("core.newTaskpaneAddin.detail"), - // description: getLocalizedString( - // "core.createProjectQuestion.option.description.previewOnWindow" - // ), - // }; - // } - - // static officeAddinItems(): OptionItem[] { - // return officeAddinJsonData.getProjectTemplateNames().map((template) => ({ - // id: template, - // label: getLocalizedString(officeAddinJsonData.getProjectDisplayName(template)), - // detail: getLocalizedString(officeAddinJsonData.getProjectDetails(template)), - // })); - // } - - static nonSsoTabAndBot(): OptionItem { - return { - id: "TabNonSsoAndBot", - label: "", // No need to set display name as this option won't be shown in UI - }; - } - - static botAndMe(): OptionItem { - return { - id: "BotAndMessageExtension", - label: "", // No need to set display name as this option won't be shown in UI - }; - } - - // copilot plugin - static copilotPluginNewApi(): OptionItem { - return { - id: copilotPluginNewApiOptionId, - label: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginNewApiOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginNewApiOption.detail" - ), - }; - } - - static copilotPluginApiSpec(): OptionItem { - return { - id: copilotPluginApiSpecOptionId, - label: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginApiSpecOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginApiSpecOption.detail" - ), - }; - } - - static copilotPluginOpenAIPlugin(): OptionItem { - return { - id: copilotPluginOpenAIPluginOptionId, - label: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginAIPluginOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginAIPluginOption.detail" - ), - }; - } - - static aiBot(): OptionItem { - return { - id: "ai-bot", - label: getLocalizedString("core.aiBotOption.label"), - detail: getLocalizedString("core.aiBotOption.detail"), - }; - } - - static aiAssistantBot(): OptionItem { - return { - id: "ai-assistant-bot", - label: getLocalizedString("core.aiAssistantBotOption.label"), - detail: getLocalizedString("core.aiAssistantBotOption.detail"), - description: getLocalizedString("core.createProjectQuestion.option.description.preview"), - }; - } - - // custom copilot - static customCopilotBasic(): OptionItem { - return { - id: "custom-copilot-basic", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotBasicOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotBasicOption.detail" - ), - }; - } - - static customCopilotRag(): OptionItem { - return { - id: "custom-copilot-rag", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagOption.detail" - ), - }; - } - - static customCopilotAssistant(): OptionItem { - return { - id: "custom-copilot-agent", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotAssistantOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotAssistantOption.detail" - ), - }; - } - - // customize GPT - static customizeGptBasic(): OptionItem { - return { - id: "basic-declarative-copilot", - label: getLocalizedString( - "core.createProjectQuestion.capability.declarativeCopilotBasic.title" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.declarativeCopilotBasic.detail" - ), - }; + // bot + if (isBot(teamsApp)) { + return { projectType: "bot-type", templateId: CapabilityOptions.basicBot().id }; } - static customizeGptWithPlugin(): OptionItem { - return { - id: "declarative-copilot-with-plugin-from-scratch", - label: getLocalizedString( - "core.createProjectQuestion.capability.declarativeCopilotWithPlugin.title" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.declarativeCopilotWithPlugin.detail" - ), - }; - } + return undefined; } +export function isFromDevPortal(inputs: Inputs | undefined): boolean { + return !!inputs?.teamsAppFromTdp; +} export function capabilityQuestion(): SingleSelectQuestion { return { name: QuestionNames.Capabilities, @@ -997,75 +291,6 @@ export function capabilityQuestion(): SingleSelectQuestion { }; } -export class MeArchitectureOptions { - static botMe(): OptionItem { - return { - id: "bot", - label: getLocalizedString("core.createProjectQuestion.capability.botMessageExtension.label"), - detail: getLocalizedString( - "core.createProjectQuestion.capability.botMessageExtension.detail" - ), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlook" - ), - }; - } - - static botPlugin(): OptionItem { - return { - id: "bot-plugin", - label: getLocalizedString("core.createProjectQuestion.capability.botMessageExtension.label"), - detail: getLocalizedString( - "core.createProjectQuestion.capability.botMessageExtension.detail" - ), - description: getLocalizedString( - "core.createProjectQuestion.option.description.worksInOutlookCopilot" - ), - }; - } - - static newApi(): OptionItem { - return { - id: "new-api", - label: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginNewApiOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.messageExtensionNewApiOption.detail" - ), - }; - } - - static apiSpec(): OptionItem { - return { - id: "api-spec", - label: getLocalizedString( - "core.createProjectQuestion.capability.copilotPluginApiSpecOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.messageExtensionApiSpecOption.detail" - ), - }; - } - - static all(): OptionItem[] { - return [ - MeArchitectureOptions.newApi(), - MeArchitectureOptions.apiSpec(), - isCopilotPluginEnabled() ? MeArchitectureOptions.botPlugin() : MeArchitectureOptions.botMe(), - ]; - } - - static staticAll(): OptionItem[] { - return [ - MeArchitectureOptions.newApi(), - MeArchitectureOptions.apiSpec(), - MeArchitectureOptions.botPlugin(), - MeArchitectureOptions.botMe(), - ]; - } -} - export function meArchitectureQuestion(): SingleSelectQuestion { return { name: QuestionNames.MeArchitectureType, @@ -1086,140 +311,6 @@ export function meArchitectureQuestion(): SingleSelectQuestion { }; } -enum HostType { - AppService = "app-service", - Functions = "azure-functions", -} - -const NotificationTriggers = { - HTTP: "http", - TIMER: "timer", -} as const; - -type NotificationTrigger = typeof NotificationTriggers[keyof typeof NotificationTriggers]; - -interface HostTypeTriggerOptionItem extends OptionItem { - hostType: HostType; - triggers?: NotificationTrigger[]; -} - -export class NotificationTriggerOptions { - static appService(): HostTypeTriggerOptionItem { - return { - id: "http-restify", - hostType: HostType.AppService, - label: getLocalizedString("plugins.bot.triggers.http-restify.label"), - description: getLocalizedString("plugins.bot.triggers.http-restify.description"), - detail: getLocalizedString("plugins.bot.triggers.http-restify.detail"), - }; - } - static appServiceForVS(): HostTypeTriggerOptionItem { - return { - id: "http-webapi", - hostType: HostType.AppService, - label: getLocalizedString("plugins.bot.triggers.http-webapi.label"), - description: getLocalizedString("plugins.bot.triggers.http-webapi.description"), - detail: getLocalizedString("plugins.bot.triggers.http-webapi.detail"), - }; - } - // NOTE: id must be the sample as cliName to prevent parsing error for CLI default value. - static functionsTimerTrigger(): HostTypeTriggerOptionItem { - return { - id: "timer-functions", - hostType: HostType.Functions, - triggers: [NotificationTriggers.TIMER], - label: getLocalizedString("plugins.bot.triggers.timer-functions.label"), - description: getLocalizedString("plugins.bot.triggers.timer-functions.description"), - detail: getLocalizedString("plugins.bot.triggers.timer-functions.detail"), - }; - } - - static functionsTimerTriggerIsolated(): HostTypeTriggerOptionItem { - return { - id: "timer-functions-isolated", - hostType: HostType.Functions, - triggers: [NotificationTriggers.TIMER], - label: getLocalizedString("plugins.bot.triggers.timer-functions.label"), - description: getLocalizedString("plugins.bot.triggers.timer-functions.description"), - detail: getLocalizedString("plugins.bot.triggers.timer-functions.detail"), - }; - } - - static functionsHttpAndTimerTrigger(): HostTypeTriggerOptionItem { - return { - id: "http-and-timer-functions", - hostType: HostType.Functions, - triggers: [NotificationTriggers.HTTP, NotificationTriggers.TIMER], - label: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.label"), - description: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.description"), - detail: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.detail"), - }; - } - - static functionsHttpAndTimerTriggerIsolated(): HostTypeTriggerOptionItem { - return { - id: "http-and-timer-functions-isolated", - hostType: HostType.Functions, - triggers: [NotificationTriggers.HTTP, NotificationTriggers.TIMER], - label: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.label"), - description: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.description"), - detail: getLocalizedString("plugins.bot.triggers.http-and-timer-functions.detail"), - }; - } - - static functionsHttpTrigger(): HostTypeTriggerOptionItem { - return { - id: "http-functions", - hostType: HostType.Functions, - triggers: [NotificationTriggers.HTTP], - label: getLocalizedString("plugins.bot.triggers.http-functions.label"), - description: getLocalizedString("plugins.bot.triggers.http-functions.description"), - detail: getLocalizedString("plugins.bot.triggers.http-functions.detail"), - }; - } - - static functionsHttpTriggerIsolated(): HostTypeTriggerOptionItem { - return { - id: "http-functions-isolated", - hostType: HostType.Functions, - triggers: [NotificationTriggers.HTTP], - label: getLocalizedString("plugins.bot.triggers.http-functions.label"), - description: getLocalizedString("plugins.bot.triggers.http-functions.description"), - detail: getLocalizedString("plugins.bot.triggers.http-functions.detail"), - }; - } - - static functionsTriggers(): HostTypeTriggerOptionItem[] { - return [ - NotificationTriggerOptions.functionsHttpAndTimerTrigger(), - NotificationTriggerOptions.functionsHttpTrigger(), - NotificationTriggerOptions.functionsTimerTrigger(), - ]; - } - - static all(): HostTypeTriggerOptionItem[] { - return [ - NotificationTriggerOptions.appService(), - NotificationTriggerOptions.appServiceForVS(), - NotificationTriggerOptions.functionsHttpAndTimerTrigger(), - NotificationTriggerOptions.functionsHttpTrigger(), - NotificationTriggerOptions.functionsTimerTrigger(), - ]; - } -} - -function getRuntime(inputs: Inputs): string { - let runtime = RuntimeOptions.NodeJS().id; - if (isCLIDotNetEnabled()) { - runtime = inputs[QuestionNames.Runtime] || runtime; - } else { - if (inputs?.platform === Platform.VS) { - runtime = RuntimeOptions.DotNet().id; - } - } - return runtime; -} - function botTriggerQuestion(): SingleSelectQuestion { return { name: QuestionNames.BotTrigger, @@ -1402,10 +493,6 @@ export function SPFxWebpartNameQuestion(): TextInputQuestion { }, }; } -export enum SPFxVersionOptionIds { - installLocally = "true", - globalPackage = "false", -} export function SPFxImportFolderQuestion(hasDefaultFunc = false): FolderQuestion { return { @@ -1497,7 +584,19 @@ export function getOfficeAddinFramework(inputs: Inputs): string { return "default"; } } - +export function getOfficeAddinTemplateConfig( + projectType: string, + addinHost?: string +): IOfficeAddinHostConfig { + if ( + projectType === ProjectTypeOptions.officeXMLAddin().id && + addinHost && + addinHost !== OfficeAddinHostOptions.outlook().id + ) { + return OfficeAddinProjectConfig[addinHost]; + } + return OfficeAddinProjectConfig["json"]; +} export function getLanguageOptions(inputs: Inputs): OptionItem[] { const runtime = getRuntime(inputs); // dotnet runtime only supports C# @@ -1564,14 +663,6 @@ export function getLanguageOptions(inputs: Inputs): OptionItem[] { } } -export enum ProgrammingLanguage { - JS = "javascript", - TS = "typescript", - CSharp = "csharp", - PY = "python", - None = "none", -} - export function programmingLanguageQuestion(): SingleSelectQuestion { const programmingLanguageQuestion: SingleSelectQuestion = { name: QuestionNames.ProgrammingLanguage, @@ -1635,9 +726,6 @@ export function folderQuestion(): FolderQuestion { }; } -export const AppNamePattern = - '^(?=(.*[\\da-zA-Z]){2})[a-zA-Z][^"<>:\\?/*&|\u0000-\u001F]*[^"\\s.<>:\\?/*&|\u0000-\u001F]$'; - export async function getSolutionName(spfxFolder: string): Promise { const yoInfoPath = path.join(spfxFolder, Constants.YO_RC_FILE); if (await fs.pathExists(yoInfoPath)) { @@ -1776,22 +864,6 @@ function sampleSelectQuestion(): SingleSelectQuestion { ], }; } -export class RuntimeOptions { - static NodeJS(): OptionItem { - return { - id: "node", - label: "Node.js", - detail: getLocalizedString("core.RuntimeOptionNodeJS.detail"), - }; - } - static DotNet(): OptionItem { - return { - id: "dotnet", - label: ".NET Core", - detail: getLocalizedString("core.RuntimeOptionDotNet.detail"), - }; - } -} function runtimeQuestion(): SingleSelectQuestion { return { @@ -1919,36 +991,6 @@ function getBotOptions(inputs: Inputs): OptionItem[] { return options; } -export class ApiMessageExtensionAuthOptions { - static none(): OptionItem { - return { - id: "none", - label: "None", - }; - } - static apiKey(): OptionItem { - return { - id: "api-key", - label: "API Key", - }; - } - - static microsoftEntra(): OptionItem { - return { - id: "microsoft-entra", - label: "Microsoft Entra", - }; - } - - static all(): OptionItem[] { - return [ - ApiMessageExtensionAuthOptions.none(), - ApiMessageExtensionAuthOptions.apiKey(), - ApiMessageExtensionAuthOptions.microsoftEntra(), - ]; - } -} - function selectBotIdsQuestion(): MultiSelectQuestion { // const statcOptions: OptionItem[] = []; // statcOptions.push(botOptionItem(false, "000000-0000-0000")); @@ -2254,97 +1296,6 @@ export function apiOperationQuestion( }; } -export class CustomCopilotRagOptions { - static customize(): OptionItem { - return { - id: "custom-copilot-rag-customize", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagCustomizeOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagCustomizeOption.detail" - ), - }; - } - - static azureAISearch(): OptionItem { - return { - id: "custom-copilot-rag-azureAISearch", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagAzureAISearchOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagAzureAISearchOption.detail" - ), - }; - } - - static customApi(): OptionItem { - return { - id: "custom-copilot-rag-customApi", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagCustomApiOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagCustomApiOption.detail" - ), - description: getLocalizedString("core.createProjectQuestion.option.description.preview"), - }; - } - - static microsoft365(): OptionItem { - return { - id: "custom-copilot-rag-microsoft365", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagMicrosoft365Option.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotRagMicrosoft365Option.detail" - ), - }; - } - - static all(): OptionItem[] { - return [ - CustomCopilotRagOptions.customize(), - CustomCopilotRagOptions.azureAISearch(), - CustomCopilotRagOptions.customApi(), - CustomCopilotRagOptions.microsoft365(), - ]; - } -} - -export class CustomCopilotAssistantOptions { - static new(): OptionItem { - return { - id: "custom-copilot-agent-new", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotAssistantNewOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotAssistantNewOption.detail" - ), - }; - } - - static assistantsApi(): OptionItem { - return { - id: "custom-copilot-agent-assistants-api", - label: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotAssistantAssistantsApiOption.label" - ), - detail: getLocalizedString( - "core.createProjectQuestion.capability.customCopilotAssistantAssistantsApiOption.detail" - ), - description: getLocalizedString("core.createProjectQuestion.option.description.preview"), - }; - } - - static all(): OptionItem[] { - return [CustomCopilotAssistantOptions.new(), CustomCopilotAssistantOptions.assistantsApi()]; - } -} - function customCopilotRagQuestion(): SingleSelectQuestion { return { type: "singleSelect", diff --git a/packages/fx-core/src/question/index.ts b/packages/fx-core/src/question/index.ts index e59a8ff3cc8..0d31eb62bb1 100644 --- a/packages/fx-core/src/question/index.ts +++ b/packages/fx-core/src/question/index.ts @@ -21,13 +21,10 @@ import { selectTeamsAppManifestQuestionNode, validateTeamsAppQuestionNode, } from "./other"; -export { HubTypes, HubOptions } from "./other"; +export * from "./constants"; export * from "./create"; -export * from "./questionNames"; - export * from "./inputs"; export * from "./options"; -export * from "./constants"; export class QuestionNodes { createProject(): IQTreeNode { diff --git a/packages/fx-core/src/question/other.ts b/packages/fx-core/src/question/other.ts index 558a82f60a8..880dc0c58f0 100644 --- a/packages/fx-core/src/question/other.ts +++ b/packages/fx-core/src/question/other.ts @@ -3,7 +3,6 @@ import { AppPackageFolderName, - AzureAccountProvider, BuildFolderName, ConfirmQuestion, DynamicPlatforms, @@ -11,24 +10,32 @@ import { Inputs, ManifestUtil, MultiSelectQuestion, - OptionItem, Platform, SingleFileQuestion, SingleSelectQuestion, TextInputQuestion, - UserError, } from "@microsoft/teamsfx-api"; import fs from "fs-extra"; import * as path from "path"; import { ConstantString } from "../common/constants"; +import { isAsyncAppValidationEnabled } from "../common/featureFlags"; import { getLocalizedString } from "../common/localizeUtils"; -import { AppStudioScopes } from "../common/tools"; import { Constants } from "../component/driver/add/utility/constants"; -import { recommendedLocations, resourceGroupHelper } from "../component/utils/ResourceGroupHelper"; +import { AppStudioScopes } from "../component/driver/teamsApp/constants"; +import { AppStudioError } from "../component/driver/teamsApp/errors"; +import { AppStudioResultFactory } from "../component/driver/teamsApp/results"; +import { manifestUtils } from "../component/driver/teamsApp/utils/ManifestUtils"; +import { getAbsolutePath } from "../component/utils/common"; import { envUtil } from "../component/utils/envUtil"; import { CollaborationConstants, CollaborationUtil } from "../core/collaborator"; import { environmentNameManager } from "../core/environmentName"; import { TOOLS } from "../core/globalVars"; +import { + HubOptions, + PluginAvailabilityOptions, + QuestionNames, + TeamsAppValidationOptions, +} from "./constants"; import { SPFxFrameworkQuestion, SPFxImportFolderQuestion, @@ -36,12 +43,6 @@ import { apiOperationQuestion, apiSpecLocationQuestion, } from "./create"; -import { QuestionNames } from "./questionNames"; -import { isAsyncAppValidationEnabled } from "../common/featureFlags"; -import { getAbsolutePath } from "../component/utils/common"; -import { manifestUtils } from "../component/driver/teamsApp/utils/ManifestUtils"; -import { AppStudioResultFactory } from "../component/driver/teamsApp/results"; -import { AppStudioError } from "../component/driver/teamsApp/errors"; export function listCollaboratorQuestionNode(): IQTreeNode { const selectTeamsAppNode = selectTeamsAppManifestQuestionNode(); @@ -396,36 +397,6 @@ export function copilotPluginAddAPIQuestionNode(): IQTreeNode { }; } -export class TeamsAppValidationOptions { - static schema(): OptionItem { - return { - id: "validateAgainstSchema", - label: getLocalizedString("core.selectValidateMethodQuestion.validate.schemaOption"), - description: getLocalizedString( - "core.selectValidateMethodQuestion.validate.schemaOptionDescription" - ), - }; - } - static package(): OptionItem { - return { - id: "validateAgainstPackage", - label: getLocalizedString("core.selectValidateMethodQuestion.validate.appPackageOption"), - description: getLocalizedString( - "core.selectValidateMethodQuestion.validate.appPackageOptionDescription" - ), - }; - } - static testCases(): OptionItem { - return { - id: "validateWithTestCases", - label: getLocalizedString("core.selectValidateMethodQuestion.validate.testCasesOption"), - description: getLocalizedString( - "core.selectValidateMethodQuestion.validate.testCasesOptionDescription" - ), - }; - } -} - function selectTeamsAppPackageQuestion(): SingleFileQuestion { return { name: QuestionNames.TeamsAppPackageFilePath, @@ -458,36 +429,6 @@ export function selectTeamsAppPackageQuestionNode(): IQTreeNode { }; } -export enum HubTypes { - teams = "teams", - outlook = "outlook", - office = "office", -} - -export class HubOptions { - static teams(): OptionItem { - return { - id: "teams", - label: "Teams", - }; - } - static outlook(): OptionItem { - return { - id: "outlook", - label: "Outlook", - }; - } - static office(): OptionItem { - return { - id: "office", - label: "the Microsoft 365 app", - }; - } - static all(): OptionItem[] { - return [this.teams(), this.outlook(), this.office()]; - } -} - function selectM365HostQuestion(): SingleSelectQuestion { return { name: QuestionNames.M365Host, @@ -796,184 +737,6 @@ export function createNewEnvQuestionNode(): IQTreeNode { }; } -export const newResourceGroupOption = "+ New resource group"; - -/** - * select existing resource group or create new resource group - */ -function selectResourceGroupQuestion( - azureAccountProvider: AzureAccountProvider, - subscriptionId: string -): SingleSelectQuestion { - return { - type: "singleSelect", - name: QuestionNames.TargetResourceGroupName, - title: getLocalizedString("core.QuestionSelectResourceGroup.title"), - staticOptions: [{ id: newResourceGroupOption, label: newResourceGroupOption }], - dynamicOptions: async (inputs: Inputs): Promise => { - const rmClient = await resourceGroupHelper.createRmClient( - azureAccountProvider, - subscriptionId - ); - const listRgRes = await resourceGroupHelper.listResourceGroups(rmClient); - if (listRgRes.isErr()) throw listRgRes.error; - const rgList = listRgRes.value; - const options: OptionItem[] = rgList.map((rg) => { - return { - id: rg[0], - label: rg[0], - description: rg[1], - }; - }); - const existingResourceGroupNames = rgList.map((rg) => rg[0]); - inputs.existingResourceGroupNames = existingResourceGroupNames; // cache existing resource group names for valiation usage - return [{ id: newResourceGroupOption, label: newResourceGroupOption }, ...options]; - }, - skipSingleOption: true, - returnObject: true, - forgetLastValue: true, - }; -} - -export function validateResourceGroupName(input: string, inputs?: Inputs): string | undefined { - const name = input; - // https://docs.microsoft.com/en-us/rest/api/resources/resource-groups/create-or-update#uri-parameters - const match = name.match(/^[-\w._()]+$/); - if (!match) { - return getLocalizedString("core.QuestionNewResourceGroupName.validation"); - } - - // To avoid the issue in CLI that using async func for validation and filter will make users input answers twice, - // we check the existence of a resource group from the list rather than call the api directly for now. - // Bug: https://msazure.visualstudio.com/Microsoft%20Teams%20Extensibility/_workitems/edit/15066282 - // GitHub issue: https://github.com/SBoudrias/Inquirer.js/issues/1136 - if (inputs?.existingResourceGroupNames) { - const maybeExist = - inputs.existingResourceGroupNames.findIndex( - (o: string) => o.toLowerCase() === input.toLowerCase() - ) >= 0; - if (maybeExist) { - return `resource group already exists: ${name}`; - } - } - return undefined; -} - -export function newResourceGroupNameQuestion(defaultResourceGroupName: string): TextInputQuestion { - return { - type: "text", - name: QuestionNames.NewResourceGroupName, - title: getLocalizedString("core.QuestionNewResourceGroupName.title"), - placeholder: getLocalizedString("core.QuestionNewResourceGroupName.placeholder"), - // default resource group name will change with env name - forgetLastValue: true, - default: defaultResourceGroupName, - validation: { - validFunc: validateResourceGroupName, - }, - }; -} - -function selectResourceGroupLocationQuestion( - azureAccountProvider: AzureAccountProvider, - subscriptionId: string -): SingleSelectQuestion { - return { - type: "singleSelect", - name: QuestionNames.NewResourceGroupLocation, - title: getLocalizedString("core.QuestionNewResourceGroupLocation.title"), - staticOptions: [], - dynamicOptions: async (inputs: Inputs) => { - const rmClient = await resourceGroupHelper.createRmClient( - azureAccountProvider, - subscriptionId - ); - const getLocationsRes = await resourceGroupHelper.getLocations( - azureAccountProvider, - rmClient - ); - if (getLocationsRes.isErr()) { - throw getLocationsRes.error; - } - const recommended = getLocationsRes.value.filter((location) => { - return recommendedLocations.indexOf(location) >= 0; - }); - const others = getLocationsRes.value.filter((location) => { - return recommendedLocations.indexOf(location) < 0; - }); - return [ - ...recommended.map((location) => { - return { - id: location, - label: location, - groupName: getLocalizedString( - "core.QuestionNewResourceGroupLocation.group.recommended" - ), - } as OptionItem; - }), - ...others.map((location) => { - return { - id: location, - label: location, - groupName: getLocalizedString("core.QuestionNewResourceGroupLocation.group.others"), - } as OptionItem; - }), - ]; - }, - default: "Central US", - }; -} - -export function resourceGroupQuestionNode( - azureAccountProvider: AzureAccountProvider, - subscriptionId: string, - defaultResourceGroupName: string -): IQTreeNode { - return { - data: selectResourceGroupQuestion(azureAccountProvider, subscriptionId), - children: [ - { - condition: { equals: newResourceGroupOption }, - data: newResourceGroupNameQuestion(defaultResourceGroupName), - children: [ - { - data: selectResourceGroupLocationQuestion(azureAccountProvider, subscriptionId), - }, - ], - }, - ], - }; -} - -export class PluginAvailabilityOptions { - static action(): OptionItem { - return { - id: "action", - label: getLocalizedString("core.pluginAvailability.declarativeCopilot"), - }; - } - static copilotPlugin(): OptionItem { - return { - id: "copilot-plugin", - label: getLocalizedString("core.pluginAvailability.copilotForM365"), - }; - } - static copilotPluginAndAction(): OptionItem { - return { - id: "copilot-plugin-and-action", - label: getLocalizedString("core.pluginAvailability.declarativeCopilotAndM365"), - }; - } - - static all(): OptionItem[] { - return [ - PluginAvailabilityOptions.copilotPlugin(), - PluginAvailabilityOptions.action(), - PluginAvailabilityOptions.copilotPluginAndAction(), - ]; - } -} - export function selectPluginAvailabilityQuestion(): SingleSelectQuestion { return { name: QuestionNames.PluginAvailability, diff --git a/packages/fx-core/src/question/questionNames.ts b/packages/fx-core/src/question/questionNames.ts deleted file mode 100644 index 40a8088ea05..00000000000 --- a/packages/fx-core/src/question/questionNames.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export enum QuestionNames { - Scratch = "scratch", - SctatchYes = "scratch-yes", - AppName = "app-name", - Folder = "folder", - ProjectPath = "projectPath", - ProgrammingLanguage = "programming-language", - ProjectType = "project-type", - Capabilities = "capabilities", - BotTrigger = "bot-host-type-trigger", - Runtime = "runtime", - SPFxSolution = "spfx-solution", - SPFxInstallPackage = "spfx-install-latest-package", - SPFxFramework = "spfx-framework-type", - SPFxWebpartName = "spfx-webpart-name", - SPFxWebpartDesc = "spfx-webpart-desp", - SPFxFolder = "spfx-folder", - OfficeAddinFolder = "addin-project-folder", - OfficeAddinManifest = "addin-project-manifest", - OfficeAddinTemplate = "addin-template-select", - OfficeAddinHost = "addin-host", - OfficeAddinImport = "addin-import", - OfficeAddinFramework = "office-addin-framework-type", - Samples = "samples", - ReplaceContentUrl = "replaceContentUrl", - ReplaceWebsiteUrl = "replaceWebsiteUrl", - ReplaceBotIds = "replaceBotIds", - SafeProjectName = "safeProjectName", - RepalceTabUrl = "tdp-tab-url", - ValidateMethod = "validate-method", - AppPackagePath = "appPackagePath", - CopilotPluginExistingApi = "copilot-plugin-existing-api", // group name for creating a Copilot plugin from existing api - ApiSpecLocation = "openapi-spec-location", - OpenAIPluginManifest = "openai-plugin-manifest", - ApiOperation = "api-operation", - MeArchitectureType = "me-architecture", - ApiSpecApiKey = "api-key", - ApiSpecApiKeyConfirm = "api-key-confirm", - ApiMEAuth = "api-me-auth", - OauthClientSecret = "oauth-client-secret", - OauthClientId = "oauth-client-id", - OauthConfirm = "oauth-confirm", - - CustomCopilotRag = "custom-copilot-rag", - CustomCopilotAssistant = "custom-copilot-agent", - LLMService = "llm-service", - OpenAIKey = "openai-key", - AzureOpenAIKey = "azure-openai-key", - AzureOpenAIEndpoint = "azure-openai-endpoint", - AzureOpenAIDeploymentName = "azure-openai-deployment-name", - - Features = "features", - Env = "env", - SourceEnvName = "sourceEnvName", - TargetEnvName = "targetEnvName", - TargetResourceGroupName = "targetResourceGroupName", - NewResourceGroupName = "newResourceGroupName", - NewResourceGroupLocation = "newResourceGroupLocation", - NewTargetEnvName = "newTargetEnvName", - ExistingTabEndpoint = "existing-tab-endpoint", - TeamsAppManifestFilePath = "manifest-path", - LocalTeamsAppManifestFilePath = "local-manifest-path", - AadAppManifestFilePath = "manifest-file-path", - TeamsAppPackageFilePath = "app-package-file-path", - ConfirmManifest = "confirmManifest", - ConfirmLocalManifest = "confirmLocalManifest", - ConfirmAadManifest = "confirmAadManifest", - OutputZipPathParamName = "output-zip-path", - OutputManifestParamName = "output-manifest-path", - M365Host = "m365-host", - - ManifestPath = "manifest-path", - - UserEmail = "email", - - collaborationAppType = "collaborationType", - DestinationApiSpecFilePath = "destination-api-spec-location", - PluginAvailability = "plugin-availability", -} - -export enum CliQuestionName { - Capability = "capability", -} diff --git a/packages/fx-core/tests/common/tools.test.ts b/packages/fx-core/tests/common/tools.test.ts index 718eb6869f0..d0cd358ec9c 100644 --- a/packages/fx-core/tests/common/tools.test.ts +++ b/packages/fx-core/tests/common/tools.test.ts @@ -19,14 +19,13 @@ import { getSPFxToken, getCopilotStatus, getSideloadingStatus, - isVideoFilterProject, listDevTunnels, setRegion, - deepCopy, - isUserCancelError, } from "../../src/common/tools"; import { AuthSvcClient } from "../../src/component/driver/teamsApp/clients/authSvcClient"; import { MockTools } from "../core/utils"; +import { isUserCancelError } from "../../src/error/common"; +import { isVideoFilterProject } from "../../src/core/middleware/videoFilterAppBlocker"; chai.use(chaiAsPromised); @@ -365,25 +364,6 @@ projectId: 00000000-0000-0000-0000-000000000000`; }); }); - describe("deepCopy", async () => { - it("should deep copy", async () => { - const obj = { - a: "a", - b: { - c: "c", - }, - }; - const copy = deepCopy(obj); - chai.expect(copy).deep.equal(obj); - chai.expect(copy).not.equal(obj); - }); - it("should not deep copy obj", async () => { - const obj = {}; - const copy = deepCopy(obj); - chai.expect(copy).equal(obj); - }); - }); - describe("isUserCancelError()", () => { it("should return true if error is UserCancelError", () => { const error = new Error(); diff --git a/packages/fx-core/tests/common/utils.test.ts b/packages/fx-core/tests/common/utils.test.ts index 3a59de49fa2..c4aa3ede741 100644 --- a/packages/fx-core/tests/common/utils.test.ts +++ b/packages/fx-core/tests/common/utils.test.ts @@ -1,6 +1,6 @@ import "mocha"; import chai from "chai"; -import { convertToAlphanumericOnly } from "../../src/common/utils"; +import { convertToAlphanumericOnly } from "../../src/common/stringUtils"; import { jsonUtils } from "../../src/common/jsonUtils"; import { FileNotFoundError, diff --git a/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts b/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts index 7b5a389bdc5..40375827d6c 100644 --- a/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts +++ b/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts @@ -4,11 +4,10 @@ import { err, Inputs, ok, Platform, SystemError, UserError } from "@microsoft/te import { assert } from "chai"; import fs from "fs-extra"; import { glob } from "glob"; -import mockedEnv, { RestoreFn } from "mocked-env"; +import { RestoreFn } from "mocked-env"; import * as sinon from "sinon"; import { CreateSampleProjectInputs, validationUtils } from "../../../src"; import * as FeatureFlags from "../../../src/common/featureFlags"; -import { FeatureFlagName } from "../../../src/common/constants"; import { MetadataV3 } from "../../../src/common/versionMetadata"; import { coordinator } from "../../../src/component/coordinator"; import { developerPortalScaffoldUtils } from "../../../src/component/developerPortalScaffoldUtils"; @@ -19,7 +18,10 @@ import { OfficeAddinGenerator, OfficeAddinGeneratorNew, } from "../../../src/component/generator/officeAddin/generator"; +import { OfficeXMLAddinGenerator } from "../../../src/component/generator/officeXMLAddin/generator"; import { SPFxGenerator } from "../../../src/component/generator/spfx/spfxGenerator"; +import { DefaultTemplateGenerator } from "../../../src/component/generator/templates/templateGenerator"; +import { TemplateNames } from "../../../src/component/generator/templates/templateNames"; import { createContextV3 } from "../../../src/component/utils"; import { settingsUtil } from "../../../src/component/utils/settingsUtil"; import { FxCore } from "../../../src/core/FxCore"; @@ -33,14 +35,11 @@ import { MeArchitectureOptions, OfficeAddinHostOptions, ProjectTypeOptions, + QuestionNames, ScratchOptions, -} from "../../../src/question/create"; -import { QuestionNames } from "../../../src/question/questionNames"; +} from "../../../src/question/constants"; import { MockTools, randomAppName } from "../../core/utils"; import { MockedUserInteraction } from "../../plugins/solution/util"; -import { OfficeXMLAddinGenerator } from "../../../src/component/generator/officeXMLAddin/generator"; -import { DefaultTemplateGenerator } from "../../../src/component/generator/templates/templateGenerator"; -import { TemplateNames } from "../../../src/component/generator/templates/templateNames"; const V3Version = MetadataV3.projectVersion; diff --git a/packages/fx-core/tests/component/coordinator/coordinator.deploy.test.ts b/packages/fx-core/tests/component/coordinator/coordinator.deploy.test.ts index c05d487b8e7..bd2d171d218 100644 --- a/packages/fx-core/tests/component/coordinator/coordinator.deploy.test.ts +++ b/packages/fx-core/tests/component/coordinator/coordinator.deploy.test.ts @@ -16,7 +16,6 @@ import { import { MetadataV3, VersionInfo, VersionSource } from "../../../src/common/versionMetadata"; import { ExecutionResult, ProjectModel } from "../../../src/component/configManager/interface"; -import { SolutionSource } from "../../../src/component/constants"; import { deployUtils } from "../../../src/component/deployUtils"; import { DriverContext } from "../../../src/component/driver/interface/commonArgs"; import { envUtil } from "../../../src/component/utils/envUtil"; @@ -26,9 +25,9 @@ import { settingsUtil } from "../../../src/component/utils/settingsUtil"; import { FxCore } from "../../../src/core/FxCore"; import { setTools } from "../../../src/core/globalVars"; import * as v3MigrationUtils from "../../../src/core/middleware/utils/v3MigrationUtils"; +import { UserCancelError } from "../../../src/error"; import { MockTools } from "../../core/utils"; import { mockedResolveDriverInstances } from "./coordinator.test"; -import { UserCancelError } from "../../../src/error"; const versionInfo: VersionInfo = { version: MetadataV3.projectVersion, diff --git a/packages/fx-core/tests/component/coordinator/coordinator.publish.test.ts b/packages/fx-core/tests/component/coordinator/coordinator.publish.test.ts index cd97bf33040..ce41e8afce6 100644 --- a/packages/fx-core/tests/component/coordinator/coordinator.publish.test.ts +++ b/packages/fx-core/tests/component/coordinator/coordinator.publish.test.ts @@ -22,7 +22,7 @@ import { } from "../../../src/component/configManager/interface"; import { coordinator } from "../../../src/component/coordinator"; import { DriverContext } from "../../../src/component/driver/interface/commonArgs"; -import { createDriverContext } from "../../../src/component/utils"; +import { createDriverContext } from "../../../src/component/driver/util/utils"; import { envUtil } from "../../../src/component/utils/envUtil"; import { metadataUtil } from "../../../src/component/utils/metadataUtil"; import { pathUtils } from "../../../src/component/utils/pathUtils"; diff --git a/packages/fx-core/tests/component/developerPortalScaffoldUtils.test.ts b/packages/fx-core/tests/component/developerPortalScaffoldUtils.test.ts index a0536be5681..ed3e07400ee 100644 --- a/packages/fx-core/tests/component/developerPortalScaffoldUtils.test.ts +++ b/packages/fx-core/tests/component/developerPortalScaffoldUtils.test.ts @@ -8,10 +8,8 @@ import { merge } from "lodash"; import "mocha"; import path from "path"; import * as sinon from "sinon"; -import { - developerPortalScaffoldUtils, - getProjectTypeAndCapability, -} from "../../src/component/developerPortalScaffoldUtils"; +import { CapabilityOptions, getProjectTypeAndCapability } from "../../src"; +import { developerPortalScaffoldUtils } from "../../src/component/developerPortalScaffoldUtils"; import * as appStudio from "../../src/component/driver/teamsApp/appStudio"; import { BOTS_TPL_V3, @@ -30,10 +28,9 @@ import { createContextV3 } from "../../src/component/utils"; import { DotenvOutput, envUtil } from "../../src/component/utils/envUtil"; import { ObjectIsUndefinedError } from "../../src/core/error"; import { setTools } from "../../src/core/globalVars"; -import { QuestionNames } from "../../src/question/questionNames"; +import { QuestionNames } from "../../src/question/constants"; import { MockTools } from "../core/utils"; import { MockedAzureAccountProvider, MockedM365Provider } from "../plugins/solution/util"; -import { CapabilityOptions } from "../../src"; describe("developPortalScaffoldUtils", () => { setTools(new MockTools()); diff --git a/packages/fx-core/tests/component/driver/deploy/azure/AzureDeployImpl.test.ts b/packages/fx-core/tests/component/driver/deploy/azure/AzureDeployImpl.test.ts index e3e20de32de..e085e2d1e08 100644 --- a/packages/fx-core/tests/component/driver/deploy/azure/AzureDeployImpl.test.ts +++ b/packages/fx-core/tests/component/driver/deploy/azure/AzureDeployImpl.test.ts @@ -10,7 +10,7 @@ import { TestAzureAccountProvider } from "../../../util/azureAccountMock"; import { TestLogProvider } from "../../../util/logProviderMock"; import { MockTelemetryReporter, MockUserInteraction } from "../../../../core/utils"; import { AzureZipDeployImpl } from "../../../../../src/component/driver/deploy/azure/impl/AzureZipDeployImpl"; -import * as tools from "../../../../../src/common/tools"; +import * as tools from "../../../../../src/common/utils"; import * as sinon from "sinon"; import { AzureDeployImpl } from "../../../../../src/component/driver/deploy/azure/impl/azureDeployImpl"; import { diff --git a/packages/fx-core/tests/component/driver/deploy/azure/azureAppServiceDeployDriver.test.ts b/packages/fx-core/tests/component/driver/deploy/azure/azureAppServiceDeployDriver.test.ts index 3bdcd197d66..1eb1a2d4bb7 100644 --- a/packages/fx-core/tests/component/driver/deploy/azure/azureAppServiceDeployDriver.test.ts +++ b/packages/fx-core/tests/component/driver/deploy/azure/azureAppServiceDeployDriver.test.ts @@ -6,7 +6,7 @@ */ import "mocha"; import * as sinon from "sinon"; -import * as tools from "../../../../../src/common/tools"; +import * as tools from "../../../../../src/common/utils"; import { DeployArgs } from "../../../../../src/component/driver/interface/buildAndDeployArgs"; import { TestAzureAccountProvider } from "../../../util/azureAccountMock"; import { TestLogProvider } from "../../../util/logProviderMock"; diff --git a/packages/fx-core/tests/component/driver/deploy/azure/azureFunctionDeployDriver.test.ts b/packages/fx-core/tests/component/driver/deploy/azure/azureFunctionDeployDriver.test.ts index 4f2359d0cc3..350632338b6 100644 --- a/packages/fx-core/tests/component/driver/deploy/azure/azureFunctionDeployDriver.test.ts +++ b/packages/fx-core/tests/component/driver/deploy/azure/azureFunctionDeployDriver.test.ts @@ -6,7 +6,7 @@ */ import "mocha"; import * as sinon from "sinon"; -import * as tools from "../../../../../src/common/tools"; +import * as tools from "../../../../../src/common/utils"; import { DeployArgs } from "../../../../../src/component/driver/interface/buildAndDeployArgs"; import { TestAzureAccountProvider } from "../../../util/azureAccountMock"; import { TestLogProvider } from "../../../util/logProviderMock"; diff --git a/packages/fx-core/tests/component/driver/deploy/azure/azureStorageDeployDriver.test.ts b/packages/fx-core/tests/component/driver/deploy/azure/azureStorageDeployDriver.test.ts index d819603ab2e..70b6a38562a 100644 --- a/packages/fx-core/tests/component/driver/deploy/azure/azureStorageDeployDriver.test.ts +++ b/packages/fx-core/tests/component/driver/deploy/azure/azureStorageDeployDriver.test.ts @@ -6,7 +6,7 @@ */ import "mocha"; import * as sinon from "sinon"; -import * as tools from "../../../../../src/common/tools"; +import * as tools from "../../../../../src/common/utils"; import { AzureStorageDeployDriver } from "../../../../../src/component/driver/deploy/azure/azureStorageDeployDriver"; import { DeployArgs } from "../../../../../src/component/driver/interface/buildAndDeployArgs"; import { TestAzureAccountProvider } from "../../../util/azureAccountMock"; diff --git a/packages/fx-core/tests/component/driver/deploy/azure/azureStorageStaticWebsiteConfigDriver.test.ts b/packages/fx-core/tests/component/driver/deploy/azure/azureStorageStaticWebsiteConfigDriver.test.ts index 9dbf850d398..a704c05026e 100644 --- a/packages/fx-core/tests/component/driver/deploy/azure/azureStorageStaticWebsiteConfigDriver.test.ts +++ b/packages/fx-core/tests/component/driver/deploy/azure/azureStorageStaticWebsiteConfigDriver.test.ts @@ -7,7 +7,7 @@ import "mocha"; import * as chai from "chai"; import * as sinon from "sinon"; -import * as tools from "../../../../../src/common/tools"; +import * as tools from "../../../../../src/common/utils"; import { AzureStorageStaticWebsiteConfigDriver } from "../../../../../src/component/driver/deploy/azure/azureStorageStaticWebsiteConfigDriver"; import { TestAzureAccountProvider } from "../../../util/azureAccountMock"; import { TestLogProvider } from "../../../util/logProviderMock"; diff --git a/packages/fx-core/tests/component/driver/script/dotnetBuildDriver.test.ts b/packages/fx-core/tests/component/driver/script/dotnetBuildDriver.test.ts index afd5d5c21f5..f9f82d8e305 100644 --- a/packages/fx-core/tests/component/driver/script/dotnetBuildDriver.test.ts +++ b/packages/fx-core/tests/component/driver/script/dotnetBuildDriver.test.ts @@ -4,7 +4,7 @@ import "mocha"; import * as chai from "chai"; import * as sinon from "sinon"; -import * as tools from "../../../../src/common/tools"; +import * as tools from "../../../../src/common/utils"; import * as utils from "../../../../src/component/driver/script/scriptDriver"; import { DotnetBuildDriver } from "../../../../src/component/driver/script/dotnetBuildDriver"; import { TestAzureAccountProvider } from "../../util/azureAccountMock"; diff --git a/packages/fx-core/tests/component/driver/script/npmBuildDriver.test.ts b/packages/fx-core/tests/component/driver/script/npmBuildDriver.test.ts index d7a02a04924..febb82d3fe1 100644 --- a/packages/fx-core/tests/component/driver/script/npmBuildDriver.test.ts +++ b/packages/fx-core/tests/component/driver/script/npmBuildDriver.test.ts @@ -7,7 +7,7 @@ import "mocha"; import * as chai from "chai"; import * as sinon from "sinon"; -import * as tools from "../../../../src/common/tools"; +import * as tools from "../../../../src/common/utils"; import * as utils from "../../../../src/component/driver/script/scriptDriver"; import { TestAzureAccountProvider } from "../../util/azureAccountMock"; import { TestLogProvider } from "../../util/logProviderMock"; diff --git a/packages/fx-core/tests/component/driver/script/npxBuildDriver.test.ts b/packages/fx-core/tests/component/driver/script/npxBuildDriver.test.ts index 8b623902f6e..4aed5c8aa6e 100644 --- a/packages/fx-core/tests/component/driver/script/npxBuildDriver.test.ts +++ b/packages/fx-core/tests/component/driver/script/npxBuildDriver.test.ts @@ -4,18 +4,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "mocha"; import { assert } from "chai"; +import "mocha"; import * as sinon from "sinon"; - -import * as tools from "../../../../src/common/tools"; +import { err, ok, UserError } from "@microsoft/teamsfx-api"; +import chai from "chai"; +import * as tools from "../../../../src/common/utils"; +import { NpxBuildDriver } from "../../../../src/component/driver/script/npxBuildDriver"; import * as utils from "../../../../src/component/driver/script/scriptDriver"; +import { MockUserInteraction } from "../../../core/utils"; import { TestAzureAccountProvider } from "../../util/azureAccountMock"; import { TestLogProvider } from "../../util/logProviderMock"; -import { NpxBuildDriver } from "../../../../src/component/driver/script/npxBuildDriver"; -import { MockUserInteraction } from "../../../core/utils"; -import { err, ok, UserError } from "@microsoft/teamsfx-api"; -import chai from "chai"; describe("NPX Build Driver test", () => { const sandbox = sinon.createSandbox(); diff --git a/packages/fx-core/tests/component/driver/script/scriptDriver.test.ts b/packages/fx-core/tests/component/driver/script/scriptDriver.test.ts index f696339fbdb..2a426c6ed18 100644 --- a/packages/fx-core/tests/component/driver/script/scriptDriver.test.ts +++ b/packages/fx-core/tests/component/driver/script/scriptDriver.test.ts @@ -6,9 +6,10 @@ import { assert } from "chai"; import child_process from "child_process"; import fs from "fs-extra"; import "mocha"; +import mockedEnv, { RestoreFn } from "mocked-env"; import os from "os"; import * as sinon from "sinon"; -import * as tools from "../../../../src/common/tools"; +import * as tools from "../../../../src/common/utils"; import { convertScriptErrorToFxError, defaultShell, @@ -22,7 +23,6 @@ import { ScriptExecutionError, ScriptTimeoutError } from "../../../../src/error/ import { MockLogProvider, MockUserInteraction } from "../../../core/utils"; import { TestAzureAccountProvider } from "../../util/azureAccountMock"; import { TestLogProvider } from "../../util/logProviderMock"; -import mockedEnv, { RestoreFn } from "mocked-env"; describe("Script Driver test", () => { const sandbox = sinon.createSandbox(); diff --git a/packages/fx-core/tests/component/driver/teamsApp/validate.test.ts b/packages/fx-core/tests/component/driver/teamsApp/validate.test.ts index 633e2b1d7e6..ce751f1f1f4 100644 --- a/packages/fx-core/tests/component/driver/teamsApp/validate.test.ts +++ b/packages/fx-core/tests/component/driver/teamsApp/validate.test.ts @@ -1,48 +1,48 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "mocha"; -import * as sinon from "sinon"; -import chai from "chai"; -import fs from "fs-extra"; import { ManifestUtil, + Platform, SystemError, + TeamsAppManifest, err, ok, - Platform, - TeamsAppManifest, } from "@microsoft/teamsfx-api"; -import * as commonTools from "../../../../src/common/tools"; -import { ValidateManifestDriver } from "../../../../src/component/driver/teamsApp/validate"; -import { ValidateManifestArgs } from "../../../../src/component/driver/teamsApp/interfaces/ValidateManifestArgs"; -import { IAppValidationNote } from "../../../../src/component/driver/teamsApp/interfaces/appdefinitions/IValidationResult"; -import { AsyncAppValidationResultsResponse } from "../../../../src/component/driver/teamsApp/interfaces/AsyncAppValidationResultsResponse"; +import AdmZip from "adm-zip"; +import chai from "chai"; +import fs from "fs-extra"; +import "mocha"; +import * as sinon from "sinon"; +import * as commonTools from "../../../../src/common/utils"; +import { AppStudioClient } from "../../../../src/component/driver/teamsApp/clients/appStudioClient"; +import { Constants } from "../../../../src/component/driver/teamsApp/constants"; +import { AppStudioError } from "../../../../src/component/driver/teamsApp/errors"; import { AsyncAppValidationResponse, AsyncAppValidationStatus, } from "../../../../src/component/driver/teamsApp/interfaces/AsyncAppValidationResponse"; -import { ValidateAppPackageDriver } from "../../../../src/component/driver/teamsApp/validateAppPackage"; +import { AsyncAppValidationResultsResponse } from "../../../../src/component/driver/teamsApp/interfaces/AsyncAppValidationResultsResponse"; import { ValidateAppPackageArgs } from "../../../../src/component/driver/teamsApp/interfaces/ValidateAppPackageArgs"; -import { ValidateWithTestCasesDriver } from "../../../../src/component/driver/teamsApp/validateTestCases"; +import { ValidateManifestArgs } from "../../../../src/component/driver/teamsApp/interfaces/ValidateManifestArgs"; import { ValidateWithTestCasesArgs } from "../../../../src/component/driver/teamsApp/interfaces/ValidateWithTestCasesArgs"; -import { AppStudioError } from "../../../../src/component/driver/teamsApp/errors"; -import { AppStudioClient } from "../../../../src/component/driver/teamsApp/clients/appStudioClient"; +import { IAppValidationNote } from "../../../../src/component/driver/teamsApp/interfaces/appdefinitions/IValidationResult"; +import { teamsappMgr } from "../../../../src/component/driver/teamsApp/teamsappMgr"; +import { copilotGptManifestUtils } from "../../../../src/component/driver/teamsApp/utils/CopilotGptManifestUtils"; +import { manifestUtils } from "../../../../src/component/driver/teamsApp/utils/ManifestUtils"; +import { pluginManifestUtils } from "../../../../src/component/driver/teamsApp/utils/PluginManifestUtils"; +import { ValidateManifestDriver } from "../../../../src/component/driver/teamsApp/validate"; +import { ValidateAppPackageDriver } from "../../../../src/component/driver/teamsApp/validateAppPackage"; +import { ValidateWithTestCasesDriver } from "../../../../src/component/driver/teamsApp/validateTestCases"; +import { metadataUtil } from "../../../../src/component/utils/metadataUtil"; +import { setTools } from "../../../../src/core/globalVars"; +import { InvalidActionInputError, UserCancelError } from "../../../../src/error/common"; +import { MockTools } from "../../../core/utils"; import { MockedLogProvider, MockedM365Provider, MockedUserInteraction, } from "../../../plugins/solution/util"; -import AdmZip from "adm-zip"; -import { Constants } from "../../../../src/component/driver/teamsApp/constants"; -import { metadataUtil } from "../../../../src/component/utils/metadataUtil"; -import { InvalidActionInputError, UserCancelError } from "../../../../src/error/common"; -import { teamsappMgr } from "../../../../src/component/driver/teamsApp/teamsappMgr"; -import { setTools } from "../../../../src/core/globalVars"; -import { MockTools } from "../../../core/utils"; -import { manifestUtils } from "../../../../src/component/driver/teamsApp/utils/ManifestUtils"; -import { pluginManifestUtils } from "../../../../src/component/driver/teamsApp/utils/PluginManifestUtils"; -import { copilotGptManifestUtils } from "../../../../src/component/driver/teamsApp/utils/CopilotGptManifestUtils"; describe("teamsApp/validateManifest", async () => { const teamsAppDriver = new ValidateManifestDriver(); diff --git a/packages/fx-core/tests/component/generator/generator.test.ts b/packages/fx-core/tests/component/generator/generator.test.ts index ba3ddab20e4..4c1498b22f7 100644 --- a/packages/fx-core/tests/component/generator/generator.test.ts +++ b/packages/fx-core/tests/component/generator/generator.test.ts @@ -28,8 +28,9 @@ import { TemplateActionSeq, } from "../../../src/component/generator/generatorAction"; import * as generatorUtils from "../../../src/component/generator/utils"; +import * as requestUtils from "../../../src/common/requestUtils"; import mockedEnv, { RestoreFn } from "mocked-env"; -import { sampleProvider, SampleConfig } from "../../../src/common/samples"; +import { sampleProvider, SampleConfig, SampleUrlInfo } from "../../../src/common/samples"; import templateConfig from "../../../src/common/templates-config.json"; import { commonTemplateName, @@ -46,11 +47,12 @@ import { import { ActionContext } from "../../../src/component/middleware/actionExecutionMW"; import * as featurefalgs from "../../../src/common/featureFlags"; import { QuestionNames } from "../../../src/question"; -import { CapabilityOptions, ProgrammingLanguage } from "../../../src/question/create"; +import { CapabilityOptions, ProgrammingLanguage } from "../../../src/question"; import { DefaultTemplateGenerator } from "../../../src/component/generator/templates/templateGenerator"; import { Inputs, Platform } from "@microsoft/teamsfx-api"; import { TemplateNames } from "../../../src/component/generator/templates/templateNames"; import { getTemplateReplaceMap } from "../../../src/component/generator/templates/templateReplaceMap"; +import { sendRequestWithRetry, sendRequestWithTimeout } from "../../../src/common/requestUtils"; const mockedSampleInfo: SampleConfig = { id: "test-id", @@ -178,7 +180,7 @@ describe("Generator utils", () => { return { status: 400 } as AxiosResponse; }; try { - await generatorUtils.sendRequestWithRetry(requestFn, 1); + await sendRequestWithRetry(requestFn, 1); } catch (e) { assert.exists(e); return; @@ -191,7 +193,7 @@ describe("Generator utils", () => { throw new Error("test"); }; try { - await generatorUtils.sendRequestWithRetry(requestFn, 1); + await sendRequestWithRetry(requestFn, 1); } catch (e) { assert.exists(e); return; @@ -204,7 +206,7 @@ describe("Generator utils", () => { throw new Error("test"); }; try { - await generatorUtils.sendRequestWithTimeout(requestFn, 1000, 1); + await sendRequestWithTimeout(requestFn, 1000, 1); } catch (e) { assert.exists(e); return; @@ -218,7 +220,7 @@ describe("Generator utils", () => { }; sandbox.stub(axios, "isCancel").returns(true); try { - await generatorUtils.sendRequestWithTimeout(requestFn, 1000, 2); + await sendRequestWithTimeout(requestFn, 1000, 2); } catch (e) { assert.exists(e); return; @@ -406,7 +408,7 @@ describe("Generator utils", () => { }); it("convert sample info to url", async () => { - const sampleInfo: generatorUtils.SampleUrlInfo = { + const sampleInfo: SampleUrlInfo = { owner: "OfficeDev", repository: "TeamsFx-Samples", ref: "dev", @@ -579,7 +581,7 @@ describe("Generator error", async () => { sandbox.stub(generatorUtils, "getSampleInfoFromName").resolves(mockedSampleInfo); sandbox.stub(generatorUtils, "downloadDirectory").resolves([] as string[]); sandbox - .stub(generatorUtils, "sendRequestWithTimeout") + .stub(requestUtils, "sendRequestWithTimeout") .resolves({ data: sampleConfigV3 } as AxiosResponse); const result = await Generator.generateSample(ctx, tmpDir, "test"); diff --git a/packages/fx-core/tests/component/generator/officeAddinGenerator.test.ts b/packages/fx-core/tests/component/generator/officeAddinGenerator.test.ts index ee480b5c80a..580072c4329 100644 --- a/packages/fx-core/tests/component/generator/officeAddinGenerator.test.ts +++ b/packages/fx-core/tests/component/generator/officeAddinGenerator.test.ts @@ -36,7 +36,6 @@ import { OfficeAddinGeneratorNew, } from "../../../src/component/generator/officeAddin/generator"; import { HelperMethods } from "../../../src/component/generator/officeAddin/helperMethods"; -import * as componentUtils from "../../../src/component/utils"; import { createContextV3 } from "../../../src/component/utils"; import { setTools } from "../../../src/core/globalVars"; import { UserCancelError } from "../../../src/error"; @@ -189,7 +188,7 @@ describe("OfficeAddinGenerator for Outlook Addin", function () { inputs[QuestionNames.ProgrammingLanguage] = "typescript"; sinon.stub(OfficeAddinGenerator, "childProcessExec").resolves(); - sinon.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sinon.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeAddinGenerator.doScaffolding(context, inputs, testFolder); @@ -209,7 +208,7 @@ describe("OfficeAddinGenerator for Outlook Addin", function () { inputs[QuestionNames.ProgrammingLanguage] = "typescript"; sinon.stub(OfficeAddinGenerator, "childProcessExec").resolves(); - sinon.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sinon.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeAddinGenerator.doScaffolding(context, inputs, testFolder); @@ -228,7 +227,7 @@ describe("OfficeAddinGenerator for Outlook Addin", function () { inputs[QuestionNames.ProgrammingLanguage] = "typescript"; sinon.stub(OfficeAddinGenerator, "childProcessExec").resolves(); - sinon.stub(componentUtils, "fetchAndUnzip").rejects(new UserCancelError()); + sinon.stub(HelperMethods, "fetchAndUnzip").rejects(new UserCancelError()); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeAddinGenerator.doScaffolding(context, inputs, testFolder); @@ -683,7 +682,7 @@ describe("OfficeAddinGenerator for Office Addin", function () { inputs[QuestionNames.ProgrammingLanguage] = "typescript"; sinon.stub(OfficeAddinGenerator, "childProcessExec").resolves(); - sinon.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sinon.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeAddinGenerator.doScaffolding(context, inputs, testFolder); @@ -702,7 +701,7 @@ describe("OfficeAddinGenerator for Office Addin", function () { inputs[QuestionNames.ProgrammingLanguage] = "typescript"; sinon.stub(OfficeAddinGenerator, "childProcessExec").resolves(); - sinon.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sinon.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeAddinGenerator.doScaffolding(context, inputs, testFolder); @@ -722,7 +721,7 @@ describe("OfficeAddinGenerator for Office Addin", function () { inputs[QuestionNames.OfficeAddinFramework] = "default"; sinon.stub(OfficeAddinGenerator, "childProcessExec").resolves(); - sinon.stub(componentUtils, "fetchAndUnzip").rejects(new UserCancelError()); + sinon.stub(HelperMethods, "fetchAndUnzip").rejects(new UserCancelError()); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeAddinGenerator.doScaffolding(context, inputs, testFolder); diff --git a/packages/fx-core/tests/component/generator/officeXMLAddinGenerator.test.ts b/packages/fx-core/tests/component/generator/officeXMLAddinGenerator.test.ts index 13c55b7893d..1c45f1373ba 100644 --- a/packages/fx-core/tests/component/generator/officeXMLAddinGenerator.test.ts +++ b/packages/fx-core/tests/component/generator/officeXMLAddinGenerator.test.ts @@ -18,12 +18,11 @@ import * as sinon from "sinon"; import * as uuid from "uuid"; import { cpUtils } from "../../../src/common/deps-checker"; import { Generator } from "../../../src/component/generator/generator"; +import { HelperMethods } from "../../../src/component/generator/officeAddin/helperMethods"; import { OfficeXMLAddinGenerator, OfficeXmlAddinGeneratorNew, } from "../../../src/component/generator/officeXMLAddin/generator"; -import { getOfficeAddinTemplateConfig } from "../../../src/component/generator/officeXMLAddin/projectConfig"; -import * as componentUtils from "../../../src/component/utils"; import { createContextV3 } from "../../../src/component/utils"; import { setTools } from "../../../src/core/globalVars"; import { @@ -31,6 +30,7 @@ import { ProgrammingLanguage, ProjectTypeOptions, QuestionNames, + getOfficeAddinTemplateConfig, } from "../../../src/question"; import { MockTools } from "../../core/utils"; @@ -93,7 +93,7 @@ describe("OfficeXMLAddinGenerator", function () { [QuestionNames.ProgrammingLanguage]: "typescript", }; - sinon.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sinon.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sinon.stub(OfficeXMLAddinGenerator, "childProcessExec").resolves(); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); sinon.stub(Generator, "generateTemplate").resolves(ok(undefined)); @@ -133,7 +133,7 @@ describe("OfficeXMLAddinGenerator", function () { [QuestionNames.ProgrammingLanguage]: "typescript", }; - sinon.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sinon.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sinon.stub(OfficeAddinManifest, "modifyManifestFile").resolves({}); const result = await OfficeXMLAddinGenerator.generate(context, inputs, testFolder); @@ -247,7 +247,7 @@ describe("OfficeXmlAddinGeneratorNew", () => { sandbox.restore(); }); it("happy path for word-taskpane", async () => { - sandbox.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sandbox.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sandbox.stub(OfficeXMLAddinGenerator, "childProcessExec").resolves(); const inputs: Inputs = { platform: Platform.CLI, @@ -264,7 +264,7 @@ describe("OfficeXmlAddinGeneratorNew", () => { } }); it("happy path for word-manifest", async () => { - sandbox.stub(componentUtils, "fetchAndUnzip").resolves(ok(undefined)); + sandbox.stub(HelperMethods, "fetchAndUnzip").resolves(ok(undefined)); sandbox.stub(OfficeXMLAddinGenerator, "childProcessExec").resolves(); const inputs: Inputs = { platform: Platform.CLI, diff --git a/packages/fx-core/tests/component/generator/templateGenerator.test.ts b/packages/fx-core/tests/component/generator/templateGenerator.test.ts index 19ec7998154..6daea74a7c0 100644 --- a/packages/fx-core/tests/component/generator/templateGenerator.test.ts +++ b/packages/fx-core/tests/component/generator/templateGenerator.test.ts @@ -1,22 +1,21 @@ +import { Inputs, Platform } from "@microsoft/teamsfx-api"; import { assert } from "chai"; import "mocha"; -import sinon from "sinon"; -import { Inputs, Platform } from "@microsoft/teamsfx-api"; -import { createContextV3 } from "../../../src/component/utils"; import path from "path"; -import { createSandbox } from "sinon"; -import { Generators } from "../../../src/component/generator/generatorProvider"; -import { ProgrammingLanguage } from "../../../src/question/create"; -import { CapabilityOptions, QuestionNames } from "../../../src/question"; -import { MockTools, randomAppName } from "../../core/utils"; +import sinon, { createSandbox } from "sinon"; import { Generator } from "../../../src/component/generator/generator"; +import { Generators } from "../../../src/component/generator/generatorProvider"; +import { DefaultTemplateGenerator } from "../../../src/component/generator/templates/templateGenerator"; +import { TemplateInfo } from "../../../src/component/generator/templates/templateInfo"; import { TemplateNames, inputsToTemplateName, } from "../../../src/component/generator/templates/templateNames"; +import { createContextV3 } from "../../../src/component/utils"; import { setTools } from "../../../src/core/globalVars"; -import { DefaultTemplateGenerator } from "../../../src/component/generator/templates/templateGenerator"; -import { TemplateInfo } from "../../../src/component/generator/templates/templateInfo"; +import { CapabilityOptions, QuestionNames } from "../../../src/question"; +import { ProgrammingLanguage } from "../../../src/question/constants"; +import { MockTools, randomAppName } from "../../core/utils"; describe("TemplateGenerator", () => { const testInputsToTemplateName = new Map([ diff --git a/packages/fx-core/tests/component/generatorUtils.test.ts b/packages/fx-core/tests/component/generatorUtils.test.ts index 5a954945dd6..3bcdefe47f7 100644 --- a/packages/fx-core/tests/component/generatorUtils.test.ts +++ b/packages/fx-core/tests/component/generatorUtils.test.ts @@ -10,7 +10,7 @@ import fse from "fs-extra"; import "mocha"; import * as sinon from "sinon"; import * as generatorUtils from "../../src/component/generator/utils"; -import { fetchAndUnzip } from "../../src/component/utils"; +import { HelperMethods } from "../../src/component/generator/officeAddin/helperMethods"; describe("Generator related Utils", function () { describe("fetchAndUnzip", async () => { @@ -45,7 +45,7 @@ describe("Generator related Utils", function () { sandbox.stub(generatorUtils, "fetchZipFromUrl").resolves(new MockAdmZip() as any); const stub1 = sandbox.stub(fse, "ensureDir").resolves(); const stub2 = sandbox.stub(fse, "writeFile").resolves(); - const res = await fetchAndUnzip("test", "url", "dest"); + const res = await HelperMethods.fetchAndUnzip("test", "url", "dest"); chai.assert.isTrue(res.isOk()); chai.assert.isTrue(stub1.calledOnce); chai.assert.isTrue(stub2.calledOnce); @@ -53,20 +53,20 @@ describe("Generator related Utils", function () { it("fail case: fetch zip throw error", async () => { sandbox.stub(generatorUtils, "fetchZipFromUrl").rejects(new Error()); - const res = await fetchAndUnzip("test", "url", "dest"); + const res = await HelperMethods.fetchAndUnzip("test", "url", "dest"); chai.assert.isTrue(res.isErr()); }); it("fail case: fetch zip returns undefined", async () => { sandbox.stub(generatorUtils, "fetchZipFromUrl").resolves(undefined); - const res = await fetchAndUnzip("test", "url", "dest"); + const res = await HelperMethods.fetchAndUnzip("test", "url", "dest"); chai.assert.isTrue(res.isErr()); }); it("fail case: ensureDir throws error", async () => { sandbox.stub(generatorUtils, "fetchZipFromUrl").resolves(new MockAdmZip() as any); sandbox.stub(fse, "ensureDir").rejects(new Error()); - const res = await fetchAndUnzip("test", "url", "dest"); + const res = await HelperMethods.fetchAndUnzip("test", "url", "dest"); chai.assert.isTrue(res.isErr()); }); }); diff --git a/packages/fx-core/tests/component/util/azureResourceOperation.test.ts b/packages/fx-core/tests/component/util/azureResourceOperation.test.ts index cae0ce9288b..14fcebcb35b 100644 --- a/packages/fx-core/tests/component/util/azureResourceOperation.test.ts +++ b/packages/fx-core/tests/component/util/azureResourceOperation.test.ts @@ -1,14 +1,14 @@ -import * as sinon from "sinon"; +import { ListAccountSasResponse, StorageAccounts } from "@azure/arm-storage"; +import chai from "chai"; +import chaiAsPromised from "chai-as-promised"; import "mocha"; -import * as tools from "../../../src/common/tools"; +import * as sinon from "sinon"; +import * as tools from "../../../src/common/utils"; import { - getAzureAccountCredential, generateSasToken, + getAzureAccountCredential, } from "../../../src/component/utils/azureResourceOperation"; import { TestAzureAccountProvider } from "./azureAccountMock"; -import chai from "chai"; -import chaiAsPromised from "chai-as-promised"; -import { ListAccountSasResponse, StorageAccounts } from "@azure/arm-storage"; chai.use(chaiAsPromised); describe("Azure Resource Operation test", () => { diff --git a/packages/fx-core/tests/component/utils.test.ts b/packages/fx-core/tests/component/utils.test.ts index 610937f597b..cbfc5b87e4e 100644 --- a/packages/fx-core/tests/component/utils.test.ts +++ b/packages/fx-core/tests/component/utils.test.ts @@ -14,7 +14,7 @@ import mockedEnv, { RestoreFn } from "mocked-env"; import sinon from "sinon"; import { getLocalizedString } from "../../src/common/localizeUtils"; import { deployUtils } from "../../src/component/deployUtils"; -import { createDriverContext } from "../../src/component/utils"; +import { createDriverContext } from "../../src/component/driver/util/utils"; import { expandEnvironmentVariable } from "../../src/component/utils/common"; import { TeamsFxTelemetryReporter } from "../../src/component/utils/teamsFxTelemetryReporter"; import { setTools } from "../../src/core/globalVars"; diff --git a/packages/fx-core/tests/core/FxCore.create.test.ts b/packages/fx-core/tests/core/FxCore.create.test.ts index c702bbe9ffd..03c50ae76ab 100644 --- a/packages/fx-core/tests/core/FxCore.create.test.ts +++ b/packages/fx-core/tests/core/FxCore.create.test.ts @@ -15,16 +15,20 @@ import { UserError, } from "@microsoft/teamsfx-api"; import { assert } from "chai"; +import fs from "fs-extra"; import "mocha"; import * as os from "os"; import sinon from "sinon"; -import { AppDefinition, DefaultTemplateGenerator, FxCore, UserCancelError } from "../../src"; +import { AppDefinition, FxCore, UserCancelError } from "../../src"; import { coordinator } from "../../src/component/coordinator"; import { setTools } from "../../src/core/globalVars"; -import { CapabilityOptions, ProjectTypeOptions, ScratchOptions } from "../../src/question/create"; -import { QuestionNames } from "../../src/question/questionNames"; +import { + CapabilityOptions, + ProjectTypeOptions, + QuestionNames, + ScratchOptions, +} from "../../src/question/constants"; import { MockTools, randomAppName } from "./utils"; -import fs from "fs-extra"; describe("FxCore.createProject", () => { const sandbox = sinon.createSandbox(); diff --git a/packages/fx-core/tests/core/FxCore.test.ts b/packages/fx-core/tests/core/FxCore.test.ts index 6720f66bf99..fb3e11647a6 100644 --- a/packages/fx-core/tests/core/FxCore.test.ts +++ b/packages/fx-core/tests/core/FxCore.test.ts @@ -1,6 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + ErrorType, + ListAPIResult, + SpecParser, + SpecParserError, + ValidationStatus, + WarningType, +} from "@microsoft/m365-spec-parser"; import { DeclarativeCopilotManifestSchema, FxError, @@ -34,14 +42,6 @@ import { TeamsfxVersionState, projectTypeChecker, } from "../../src/common/projectTypeChecker"; -import { - ErrorType, - ListAPIResult, - SpecParser, - SpecParserError, - ValidationStatus, - WarningType, -} from "@microsoft/m365-spec-parser"; import { DriverDefinition, DriverInstance, @@ -58,14 +58,18 @@ import * as buildAadManifest from "../../src/component/driver/aad/utility/buildA import { AddWebPartDriver } from "../../src/component/driver/add/addWebPart"; import { DriverContext } from "../../src/component/driver/interface/commonArgs"; import { CreateAppPackageDriver } from "../../src/component/driver/teamsApp/createAppPackage"; +import { AppStudioError } from "../../src/component/driver/teamsApp/errors"; import { teamsappMgr } from "../../src/component/driver/teamsApp/teamsappMgr"; +import { copilotGptManifestUtils } from "../../src/component/driver/teamsApp/utils/CopilotGptManifestUtils"; import { manifestUtils } from "../../src/component/driver/teamsApp/utils/ManifestUtils"; +import { pluginManifestUtils } from "../../src/component/driver/teamsApp/utils/PluginManifestUtils"; import { ValidateManifestDriver } from "../../src/component/driver/teamsApp/validate"; import { ValidateAppPackageDriver } from "../../src/component/driver/teamsApp/validateAppPackage"; +import { ValidateWithTestCasesDriver } from "../../src/component/driver/teamsApp/validateTestCases"; +import { createDriverContext } from "../../src/component/driver/util/utils"; import "../../src/component/feature/sso"; import * as CopilotPluginHelper from "../../src/component/generator/copilotPlugin/helper"; import { OpenAIPluginManifestHelper } from "../../src/component/generator/copilotPlugin/helper"; -import { createDriverContext } from "../../src/component/utils"; import { envUtil } from "../../src/component/utils/envUtil"; import { metadataUtil } from "../../src/component/utils/metadataUtil"; import { pathUtils } from "../../src/component/utils/pathUtils"; @@ -87,13 +91,9 @@ import { ScratchOptions, questionNodes, } from "../../src/question"; -import { HubOptions, PluginAvailabilityOptions } from "../../src/question/other"; +import { HubOptions, PluginAvailabilityOptions } from "../../src/question/constants"; import { validationUtils } from "../../src/ui/validationUtils"; import { MockTools, randomAppName } from "./utils"; -import { ValidateWithTestCasesDriver } from "../../src/component/driver/teamsApp/validateTestCases"; -import { pluginManifestUtils } from "../../src/component/driver/teamsApp/utils/PluginManifestUtils"; -import { copilotGptManifestUtils } from "../../src/component/driver/teamsApp/utils/CopilotGptManifestUtils"; -import { AppStudioError } from "../../src/component/driver/teamsApp/errors"; const tools = new MockTools(); diff --git a/packages/fx-core/tests/core/middleware/ConcurrentLockerMW.test.ts b/packages/fx-core/tests/core/middleware/ConcurrentLockerMW.test.ts index aa08fff1024..1f95338848b 100644 --- a/packages/fx-core/tests/core/middleware/ConcurrentLockerMW.test.ts +++ b/packages/fx-core/tests/core/middleware/ConcurrentLockerMW.test.ts @@ -15,21 +15,21 @@ import { import { assert, expect } from "chai"; import fs from "fs-extra"; import "mocha"; -import * as sinon from "sinon"; import * as os from "os"; import * as path from "path"; -import { getLockFolder, ConcurrentLockerMW } from "../../../src/core/middleware/concurrentLocker"; +import * as sinon from "sinon"; +import * as projectSettingsHelper from "../../../src/common/projectSettingsHelper"; +import * as tools from "../../../src/common/utils"; import { CallbackRegistry } from "../../../src/core/callback"; import { CoreSource, NoProjectOpenedError } from "../../../src/core/error"; -import { randomAppName } from "../utils"; -import * as tools from "../../../src/common/tools"; -import * as projectSettingsHelper from "../../../src/common/projectSettingsHelper"; +import { ConcurrentLockerMW, getLockFolder } from "../../../src/core/middleware/concurrentLocker"; import { ConcurrentError, FileNotFoundError, InvalidProjectError, UserCancelError, } from "../../../src/error/common"; +import { randomAppName } from "../utils"; describe("Middleware - ConcurrentLockerMW", () => { afterEach(() => { diff --git a/packages/fx-core/tests/question/create.test.ts b/packages/fx-core/tests/question/create.test.ts index 6cfb2f46dd8..d7a8fd88ddf 100644 --- a/packages/fx-core/tests/question/create.test.ts +++ b/packages/fx-core/tests/question/create.test.ts @@ -34,6 +34,7 @@ import { OfficeAddinProjectConfig } from "../../src/component/generator/officeXM import { convertToLangKey } from "../../src/component/generator/utils"; import * as utils from "../../src/component/utils"; import { setTools } from "../../src/core/globalVars"; +import { FileNotFoundError } from "../../src/error"; import { ApiMessageExtensionAuthOptions, CapabilityOptions, @@ -44,6 +45,7 @@ import { OfficeAddinHostOptions, ProgrammingLanguage, ProjectTypeOptions, + QuestionNames, RuntimeOptions, SPFxVersionOptionIds, apiOperationQuestion, @@ -60,12 +62,10 @@ import { openAIPluginManifestLocationQuestion, programmingLanguageQuestion, projectTypeQuestion, -} from "../../src/question/create"; -import { QuestionNames } from "../../src/question/questionNames"; +} from "../../src/question"; import { QuestionTreeVisitor, traverse } from "../../src/ui/visitor"; import { MockTools, MockUserInteraction, randomAppName } from "../core/utils"; import { MockedLogProvider, MockedUserInteraction } from "../plugins/solution/util"; -import { FileNotFoundError } from "../../src/error"; export async function callFuncs(question: Question, inputs: Inputs, answer?: string) { try { diff --git a/packages/fx-core/tests/question/other.test.ts b/packages/fx-core/tests/question/other.test.ts index 0b6318e7414..64e4db26bc3 100644 --- a/packages/fx-core/tests/question/other.test.ts +++ b/packages/fx-core/tests/question/other.test.ts @@ -3,9 +3,9 @@ import { Inputs, Platform } from "@microsoft/teamsfx-api"; import { assert } from "chai"; import "mocha"; -import { QuestionNames } from "../../src/question/questionNames"; -import { selectTargetEnvQuestion } from "../../src/question/other"; import { environmentNameManager } from "../../src/core/environmentName"; +import { QuestionNames } from "../../src/question/constants"; +import { selectTargetEnvQuestion } from "../../src/question/other"; describe("env question", () => { it("should not show testtool env", async () => { diff --git a/packages/fx-core/tests/question/question.spfx.test.ts b/packages/fx-core/tests/question/question.spfx.test.ts index d575840088a..3578c322d48 100644 --- a/packages/fx-core/tests/question/question.spfx.test.ts +++ b/packages/fx-core/tests/question/question.spfx.test.ts @@ -1,11 +1,7 @@ import "mocha"; import * as chai from "chai"; import * as sinon from "sinon"; -import { - SPFxPackageSelectQuestion, - SPFxVersionOptionIds, - SPFxWebpartNameQuestion, -} from "../../src/question/create"; +import { SPFxPackageSelectQuestion, SPFxWebpartNameQuestion } from "../../src/question/create"; import mockedEnv, { RestoreFn } from "mocked-env"; import { FuncValidation, @@ -21,6 +17,7 @@ import * as path from "path"; import fs from "fs-extra"; import { Utils } from "../../src/component/generator/spfx/utils/utils"; import { getValidationFunction } from "../../src/ui/validationUtils"; +import { SPFxVersionOptionIds } from "../../src"; describe("SPFx question-helpers", () => { describe("SPFxWebpartNameQuestion", () => { let mockedEnvRestore: RestoreFn; diff --git a/packages/fx-core/tests/question/question.test.ts b/packages/fx-core/tests/question/question.test.ts index 74fa9acb8a5..778015f82bb 100644 --- a/packages/fx-core/tests/question/question.test.ts +++ b/packages/fx-core/tests/question/question.test.ts @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { ResourceManagementClient } from "@azure/arm-resources"; import { ConditionFunc, FuncValidation, @@ -23,35 +24,40 @@ import "mocha"; import mockedEnv, { RestoreFn } from "mocked-env"; import * as path from "path"; import sinon from "sinon"; -import { CollaborationConstants, QuestionTreeVisitor, envUtil, traverse } from "../../src"; -import { CollaborationUtil } from "../../src/core/collaborator"; +import { FeatureFlagName } from "../../src/common/constants"; +import { manifestUtils } from "../../src/component/driver/teamsApp/utils/ManifestUtils"; +import { + newResourceGroupOption, + resourceGroupHelper, + resourceGroupQuestionNode, + validateResourceGroupName, +} from "../../src/component/utils/ResourceGroupHelper"; +import { envUtil } from "../../src/component/utils/envUtil"; +import { CollaborationConstants, CollaborationUtil } from "../../src/core/collaborator"; import { setTools } from "../../src/core/globalVars"; -import { QuestionNames, SPFxImportFolderQuestion, questionNodes } from "../../src/question"; +import { SPFxImportFolderQuestion, questionNodes } from "../../src/question"; import { PluginAvailabilityOptions, + QuestionNames, TeamsAppValidationOptions, +} from "../../src/question/constants"; +import { apiSpecApiKeyQuestion, createNewEnvQuestionNode, envQuestionCondition, isAadMainifestContainsPlaceholder, newEnvNameValidation, - newResourceGroupOption, oauthQuestion, - resourceGroupQuestionNode, selectAadAppManifestQuestionNode, selectAadManifestQuestion, selectLocalTeamsAppManifestQuestion, selectPluginAvailabilityQuestion, selectTeamsAppManifestQuestion, - validateResourceGroupName, } from "../../src/question/other"; +import { QuestionTreeVisitor, traverse } from "../../src/ui/visitor"; +import { MockedAzureTokenProvider } from "../core/other.test"; import { MockTools, MockUserInteraction } from "../core/utils"; import { callFuncs } from "./create.test"; -import { MockedAzureTokenProvider } from "../core/other.test"; -import { ResourceManagementClient } from "@azure/arm-resources"; -import { resourceGroupHelper } from "../../src/component/utils/ResourceGroupHelper"; -import { FeatureFlagName } from "../../src/common/constants"; -import { manifestUtils } from "../../src/component/driver/teamsApp/utils/ManifestUtils"; const ui = new MockUserInteraction(); diff --git a/packages/tests/src/commonlib/aadManager.ts b/packages/tests/src/commonlib/aadManager.ts index 1d4f4d2b873..e8e84048b8b 100644 --- a/packages/tests/src/commonlib/aadManager.ts +++ b/packages/tests/src/commonlib/aadManager.ts @@ -8,7 +8,7 @@ import axios, { AxiosInstance } from "axios"; import { M365TokenProvider } from "@microsoft/teamsfx-api"; import MockM365TokenProvider from "@microsoft/teamsapp-cli/src/commonlib/m365LoginUserPassword"; -import { GraphScopes } from "@microsoft/teamsfx-core/build/common/tools"; +import { GraphScopes } from "@microsoft/teamsfx-core"; interface IAadAppInfo { displayName: string; diff --git a/packages/tests/src/commonlib/aadValidate.ts b/packages/tests/src/commonlib/aadValidate.ts index a19f1bd955b..091382611c9 100644 --- a/packages/tests/src/commonlib/aadValidate.ts +++ b/packages/tests/src/commonlib/aadValidate.ts @@ -3,19 +3,19 @@ "use strict"; -import * as chai from "chai"; import axios from "axios"; +import * as chai from "chai"; import { M365TokenProvider } from "@microsoft/teamsfx-api"; import MockM365TokenProvider from "@microsoft/teamsapp-cli/src/commonlib/m365LoginUserPassword"; +import { GraphScopes } from "@microsoft/teamsfx-core"; +import { EnvConstants } from "../commonlib/constants"; import { IAADDefinition, IAadObject, IAadObjectLocal, } from "./interfaces/IAADDefinition"; -import { GraphScopes } from "@microsoft/teamsfx-core/build/common/tools"; -import { EnvConstants } from "../commonlib/constants"; const baseUrl = "https://graph.microsoft.com/v1.0"; diff --git a/packages/tests/src/commonlib/sharepointValidator.ts b/packages/tests/src/commonlib/sharepointValidator.ts index 59d049130f9..17f61968e14 100644 --- a/packages/tests/src/commonlib/sharepointValidator.ts +++ b/packages/tests/src/commonlib/sharepointValidator.ts @@ -10,7 +10,7 @@ import { getSPFxTenant, GraphReadUserScopes, SPFxScopes, -} from "@microsoft/teamsfx-core/build/common/tools"; +} from "@microsoft/teamsfx-core"; export class SharepointValidator { public static provider: M365TokenProvider; diff --git a/packages/vscode-extension/src/chat/commands/create/helper.ts b/packages/vscode-extension/src/chat/commands/create/helper.ts index c1708553402..44a06180fb9 100644 --- a/packages/vscode-extension/src/chat/commands/create/helper.ts +++ b/packages/vscode-extension/src/chat/commands/create/helper.ts @@ -7,12 +7,12 @@ import { includes } from "lodash"; import * as path from "path"; import * as tmp from "tmp"; -import { sampleProvider } from "@microsoft/teamsfx-core"; import { getSampleFileInfo, runWithLimitedConcurrency, + sampleProvider, sendRequestWithRetry, -} from "@microsoft/teamsfx-core/build/component/generator/utils"; +} from "@microsoft/teamsfx-core"; import { CancellationToken, ChatRequest, diff --git a/packages/vscode-extension/src/officeChat/commands/create/officeSamples.ts b/packages/vscode-extension/src/officeChat/commands/create/officeSamples.ts index 421d9f0907e..30f54d7c080 100644 --- a/packages/vscode-extension/src/officeChat/commands/create/officeSamples.ts +++ b/packages/vscode-extension/src/officeChat/commands/create/officeSamples.ts @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { AccessGithubError, SampleConfig, sendRequestWithTimeout } from "@microsoft/teamsfx-core"; import axios from "axios"; -import { sendRequestWithTimeout } from "@microsoft/teamsfx-core/build/component/generator/utils"; -import { SampleConfig } from "@microsoft/teamsfx-core"; -import { AccessGithubError } from "@microsoft/teamsfx-core"; const OfficeSampleCofigOwner = "OfficeDev"; const OfficeSampleRepo = "Office-Samples"; diff --git a/packages/vscode-extension/src/officeChat/common/utils.ts b/packages/vscode-extension/src/officeChat/common/utils.ts index 0478405f129..e5a308bcf4a 100644 --- a/packages/vscode-extension/src/officeChat/common/utils.ts +++ b/packages/vscode-extension/src/officeChat/common/utils.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import axios from "axios"; -import { sendRequestWithTimeout } from "@microsoft/teamsfx-core/build/component/generator/utils"; +import { sendRequestWithTimeout } from "@microsoft/teamsfx-core"; export async function fetchRawFileContent(url: string): Promise { try { diff --git a/packages/vscode-extension/test/chat/telemetry.test.ts b/packages/vscode-extension/test/chat/telemetry.test.ts index b00eacce94c..af59438f0d6 100644 --- a/packages/vscode-extension/test/chat/telemetry.test.ts +++ b/packages/vscode-extension/test/chat/telemetry.test.ts @@ -9,7 +9,7 @@ import sinon from "ts-sinon"; import { Correlator } from "@microsoft/teamsfx-core"; import * as vscodeMocks from "../mocks/vsc"; import * as utils from "../../src/chat/utils"; -import * as coreTools from "@microsoft/teamsfx-core/build/common/tools"; +import * as coreTools from "@microsoft/teamsfx-core/build/common/stringUtils"; const ChatLocation = vscodeMocks.chat.ChatLocation; diff --git a/packages/vscode-extension/test/extension/hoverProvider.test.ts b/packages/vscode-extension/test/extension/hoverProvider.test.ts index 8ea86f8a481..9c1bb5396c1 100644 --- a/packages/vscode-extension/test/extension/hoverProvider.test.ts +++ b/packages/vscode-extension/test/extension/hoverProvider.test.ts @@ -1,16 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { ok } from "@microsoft/teamsfx-api"; +import { envUtil } from "@microsoft/teamsfx-core"; import * as chai from "chai"; import * as sinon from "sinon"; -import * as vscode from "vscode"; import { v4 } from "uuid"; -import { ok } from "@microsoft/teamsfx-api"; -import { envUtil } from "@microsoft/teamsfx-core"; -import * as commonTools from "@microsoft/teamsfx-core/build/common/tools"; -import { ManifestTemplateHoverProvider } from "../../src/hoverProvider"; +import * as vscode from "vscode"; import { environmentVariableRegex } from "../../src/constants"; import * as handlers from "../../src/handlers"; +import { ManifestTemplateHoverProvider } from "../../src/hoverProvider"; import { MockCore } from "../mocks/mockCore"; describe("Manifest template hover - V3", async () => { diff --git a/packages/vscode-extension/test/officeChat/common/utils.test.ts b/packages/vscode-extension/test/officeChat/common/utils.test.ts index 91368516145..2ee1b2cd323 100644 --- a/packages/vscode-extension/test/officeChat/common/utils.test.ts +++ b/packages/vscode-extension/test/officeChat/common/utils.test.ts @@ -3,7 +3,7 @@ import * as sinon from "sinon"; import * as fs from "fs"; import * as chaipromised from "chai-as-promised"; import * as commonUtils from "../../../src/officeChat/common/utils"; -import * as generatorUtils from "@microsoft/teamsfx-core/build/component/generator/utils"; +import * as requestUtils from "@microsoft/teamsfx-core/build/common/requestUtils"; import { AxiosResponse } from "axios"; chai.use(chaipromised); @@ -17,7 +17,7 @@ describe("File: officeChat/common/utils", () => { }); it("return file response data", async () => { - sandbox.stub(generatorUtils, "sendRequestWithTimeout").resolves({ + sandbox.stub(requestUtils, "sendRequestWithTimeout").resolves({ data: "testData", } as AxiosResponse); const result = await commonUtils.fetchRawFileContent("test"); @@ -25,13 +25,13 @@ describe("File: officeChat/common/utils", () => { }); it("return empty string", async () => { - sandbox.stub(generatorUtils, "sendRequestWithTimeout").resolves(undefined); + sandbox.stub(requestUtils, "sendRequestWithTimeout").resolves(undefined); const result = await commonUtils.fetchRawFileContent("test"); chai.assert.equal(result, ""); }); it("throw error", async () => { - sandbox.stub(generatorUtils, "sendRequestWithTimeout").rejects(); + sandbox.stub(requestUtils, "sendRequestWithTimeout").rejects(); try { await commonUtils.fetchRawFileContent("test"); chai.assert.fail("should not reach here");