Skip to content

Commit 9a4e300

Browse files
authored
feat(core+manifest): support app manifest v1.22 and color32x32 icon (#14261)
* feat(manifest): support app manifest v1.22 * feat(core): support color32x32 in app manifest * fix: unit test * fix: readme * fix: unit test * fix: unit test * fix: ut coverage
1 parent 654b0c8 commit 9a4e300

28 files changed

+8381
-3057
lines changed

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

Lines changed: 88 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,42 @@
44
import { hooks } from "@feathersjs/hooks/lib";
55
import {
66
Colors,
7-
FxError,
8-
Result,
9-
err,
10-
ok,
11-
PluginManifestSchema,
127
DeclarativeCopilotCapabilityName,
138
EmbeddedKnowledgeCapability,
9+
err,
1410
FunctionObject,
11+
FxError,
12+
ok,
13+
PluginManifestSchema,
14+
Result,
15+
TeamsManifestV1D17,
16+
TeamsManifestV1D19,
17+
TeamsManifestV1D21,
18+
TeamsManifestV1D5,
1519
} from "@microsoft/teamsfx-api";
1620
import AdmZip from "adm-zip";
1721
import fs from "fs-extra";
1822
import * as path from "path";
19-
import * as uuid from "uuid";
23+
import semver from "semver";
2024
import { Service } from "typedi";
21-
import { getLocalizedString } from "../../../common/localizeUtils";
25+
import * as uuid from "uuid";
26+
import { featureFlagManager, FeatureFlags } from "../../../common/featureFlags";
2227
import { ErrorContextMW } from "../../../common/globalVars";
28+
import { getLocalizedString } from "../../../common/localizeUtils";
2329
import { FileNotFoundError, InvalidActionInputError, JSONSyntaxError } from "../../../error/common";
30+
import { InvalidFileOutsideOfTheDirectotryError } from "../../../error/teamsApp";
31+
import { getAbsolutePath } from "../../utils/common";
32+
import { expandVariableWithFunction, ManifestType } from "../../utils/envFunctionUtils";
2433
import { DriverContext } from "../interface/commonArgs";
2534
import { ExecutionResult, StepDriver } from "../interface/stepDriver";
2635
import { addStartAndEndTelemetry } from "../middleware/addStartAndEndTelemetry";
36+
import { updateVersionForTeamsAppYamlFile } from "../util/utils";
2737
import { WrapDriverContext } from "../util/wrapUtil";
2838
import { Constants } from "./constants";
2939
import { CreateAppPackageArgs } from "./interfaces/CreateAppPackageArgs";
40+
import { copilotGptManifestUtils } from "./utils/CopilotGptManifestUtils";
3041
import { manifestUtils } from "./utils/ManifestUtils";
31-
import { InvalidFileOutsideOfTheDirectotryError } from "../../../error/teamsApp";
3242
import { getResolvedManifest, normalizePath } from "./utils/utils";
33-
import { copilotGptManifestUtils } from "./utils/CopilotGptManifestUtils";
34-
import { expandVariableWithFunction, ManifestType } from "../../utils/envFunctionUtils";
35-
import { getAbsolutePath } from "../../utils/common";
36-
import { featureFlagManager, FeatureFlags } from "../../../common/featureFlags";
37-
import { updateVersionForTeamsAppYamlFile } from "../util/utils";
3843

3944
export const actionName = "teamsApp/zipAppPackage";
4045

@@ -111,43 +116,60 @@ export class CreateAppPackageDriver implements StepDriver {
111116

112117
const appDirectory = path.dirname(hasTTKGeneratedFolder ? generatedFolder : manifestPath);
113118

114-
const colorFile = path.resolve(appDirectory, manifest.icons.color);
115-
if (!(await fs.pathExists(colorFile))) {
116-
const error = new FileNotFoundError(
117-
actionName,
118-
colorFile,
119-
"https://aka.ms/teamsfx-actions/teamsapp-zipAppPackage"
120-
);
121-
return err(error);
122-
}
123-
const colorFileRelativePath = path.relative(appDirectory, colorFile);
124-
if (colorFileRelativePath.startsWith("..")) {
125-
return err(new InvalidFileOutsideOfTheDirectotryError(colorFile));
126-
}
127-
128-
const outlineFile = path.resolve(appDirectory, manifest.icons.outline);
129-
if (!(await fs.pathExists(outlineFile))) {
130-
const error = new FileNotFoundError(
131-
actionName,
132-
outlineFile,
133-
"https://aka.ms/teamsfx-actions/teamsapp-zipAppPackage"
134-
);
135-
return err(error);
119+
// check and include all relative file paths in manifest
120+
const relativeFiles = [manifest.icons.color, manifest.icons.outline];
121+
const manifestVersion = semver.coerce(manifest.manifestVersion); // ensure manifestVersion is a valid semver
122+
if (manifestVersion && semver.gte(manifestVersion, "1.21.0")) {
123+
const color32x32 = (manifest as TeamsManifestV1D21.TeamsManifestV1D21).icons.color32x32;
124+
if (color32x32) {
125+
relativeFiles.push(color32x32);
126+
}
136127
}
137-
const outlineFileRelativePath = path.relative(appDirectory, outlineFile);
138-
if (outlineFileRelativePath.startsWith("..")) {
139-
return err(new InvalidFileOutsideOfTheDirectotryError(outlineFile));
128+
for (const file of relativeFiles) {
129+
const filePath = path.resolve(appDirectory, file);
130+
if (!(await fs.pathExists(filePath))) {
131+
const error = new FileNotFoundError(
132+
actionName,
133+
filePath,
134+
"https://aka.ms/teamsfx-actions/teamsapp-zipAppPackage"
135+
);
136+
return err(error);
137+
}
138+
const fileRelativePath = path.relative(appDirectory, filePath);
139+
if (fileRelativePath.startsWith("..")) {
140+
return err(new InvalidFileOutsideOfTheDirectotryError(filePath));
141+
}
140142
}
141143

142144
// pre-check existence
143-
if (
144-
manifest.localizationInfo &&
145-
manifest.localizationInfo.additionalLanguages &&
146-
manifest.localizationInfo.additionalLanguages.length > 0
147-
) {
148-
for (const language of manifest.localizationInfo.additionalLanguages) {
145+
let additionalLanguages: TeamsManifestV1D5.AdditionalLanguage[] | undefined;
146+
if (manifestVersion && semver.gte(manifestVersion, "1.5.0")) {
147+
additionalLanguages = (manifest as TeamsManifestV1D5.TeamsManifestV1D5).localizationInfo
148+
?.additionalLanguages;
149+
}
150+
let composeExtensionType: string | undefined;
151+
let apiSpecificationFile: string | undefined;
152+
let commands: TeamsManifestV1D17.ComposeExtensionCommand[] | undefined;
153+
if (manifestVersion && semver.gte(manifestVersion, "1.17.0")) {
154+
composeExtensionType = (manifest as TeamsManifestV1D17.TeamsManifestV1D17)
155+
.composeExtensions?.[0]?.composeExtensionType;
156+
apiSpecificationFile = (manifest as TeamsManifestV1D17.TeamsManifestV1D17)
157+
.composeExtensions?.[0]?.apiSpecificationFile;
158+
commands = (manifest as TeamsManifestV1D17.TeamsManifestV1D17).composeExtensions?.[0]
159+
?.commands;
160+
}
161+
let defaultLanguageFile: string | undefined;
162+
let declarativeAgents: TeamsManifestV1D19.DeclarativeAgentRef[] | undefined;
163+
if (manifestVersion && semver.gte(manifestVersion, "1.19.0")) {
164+
defaultLanguageFile = (manifest as TeamsManifestV1D19.TeamsManifestV1D19).localizationInfo
165+
?.defaultLanguageFile;
166+
declarativeAgents = (manifest as TeamsManifestV1D19.TeamsManifestV1D19).copilotAgents
167+
?.declarativeAgents;
168+
}
169+
if (additionalLanguages && additionalLanguages.length > 0) {
170+
for (const language of additionalLanguages) {
149171
const file = language.file;
150-
const fileName = `${appDirectory}/${file}`;
172+
const fileName = path.join(appDirectory, file);
151173
if (!(await fs.pathExists(fileName))) {
152174
return err(
153175
new FileNotFoundError(
@@ -159,9 +181,8 @@ export class CreateAppPackageDriver implements StepDriver {
159181
}
160182
}
161183
}
162-
if (manifest.localizationInfo && manifest.localizationInfo.defaultLanguageFile) {
163-
const file = manifest.localizationInfo.defaultLanguageFile;
164-
const fileName = `${appDirectory}/${file}`;
184+
if (defaultLanguageFile) {
185+
const fileName = path.join(appDirectory, defaultLanguageFile);
165186
if (!(await fs.pathExists(fileName))) {
166187
return err(
167188
new FileNotFoundError(
@@ -176,19 +197,15 @@ export class CreateAppPackageDriver implements StepDriver {
176197
const zip = new AdmZip();
177198
zip.addFile(Constants.MANIFEST_FILE, Buffer.from(JSON.stringify(manifest, null, 4)));
178199

179-
// outline.png & color.png, relative path
180-
let dir = path.dirname(manifest.icons.color);
181-
zip.addLocalFile(colorFile, dir === "." ? "" : dir);
182-
dir = path.dirname(manifest.icons.outline);
183-
zip.addLocalFile(outlineFile, dir === "." ? "" : dir);
200+
// icon images, relative path
201+
for (const icon of relativeFiles) {
202+
const dir = path.dirname(icon);
203+
zip.addLocalFile(path.resolve(appDirectory, icon), dir === "." ? "" : dir);
204+
}
184205

185206
// localization file
186-
if (
187-
manifest.localizationInfo &&
188-
manifest.localizationInfo.additionalLanguages &&
189-
manifest.localizationInfo.additionalLanguages.length > 0
190-
) {
191-
for (const language of manifest.localizationInfo.additionalLanguages) {
207+
if (additionalLanguages && additionalLanguages.length > 0) {
208+
for (const language of additionalLanguages) {
192209
const file = language.file;
193210
const fileName = path.resolve(appDirectory, file);
194211
const relativePath = path.relative(appDirectory, fileName);
@@ -204,9 +221,8 @@ export class CreateAppPackageDriver implements StepDriver {
204221
}
205222
}
206223
}
207-
if (manifest.localizationInfo && manifest.localizationInfo.defaultLanguageFile) {
208-
const file = manifest.localizationInfo.defaultLanguageFile;
209-
const fileName = path.resolve(appDirectory, file);
224+
if (defaultLanguageFile) {
225+
const fileName = path.resolve(appDirectory, defaultLanguageFile);
210226
const relativePath = path.relative(appDirectory, fileName);
211227
if (relativePath.startsWith("..")) {
212228
return err(new InvalidFileOutsideOfTheDirectotryError(fileName));
@@ -222,18 +238,10 @@ export class CreateAppPackageDriver implements StepDriver {
222238
}
223239

224240
// API ME, API specification and Adaptive card templates
225-
if (
226-
manifest.composeExtensions &&
227-
manifest.composeExtensions.length > 0 &&
228-
manifest.composeExtensions[0].composeExtensionType == "apiBased" &&
229-
manifest.composeExtensions[0].apiSpecificationFile
230-
) {
231-
const apiSpecificationFile = path.resolve(
232-
appDirectory,
233-
manifest.composeExtensions[0].apiSpecificationFile
234-
);
241+
if (composeExtensionType == "apiBased" && apiSpecificationFile) {
242+
const apiSpecificationFilePath = path.resolve(appDirectory, apiSpecificationFile);
235243
const checkExistenceRes = await this.validateReferencedFile(
236-
apiSpecificationFile,
244+
apiSpecificationFilePath,
237245
appDirectory
238246
);
239247
if (checkExistenceRes.isErr()) {
@@ -242,17 +250,17 @@ export class CreateAppPackageDriver implements StepDriver {
242250

243251
const addFileWithVariableRes = await this.addFileWithVariable(
244252
zip,
245-
manifest.composeExtensions[0].apiSpecificationFile,
246253
apiSpecificationFile,
254+
apiSpecificationFilePath,
247255
ManifestType.ApiSpec,
248256
context
249257
);
250258
if (addFileWithVariableRes.isErr()) {
251259
return err(addFileWithVariableRes.error);
252260
}
253261

254-
if (manifest.composeExtensions[0].commands.length > 0) {
255-
for (const command of manifest.composeExtensions[0].commands) {
262+
if (commands && commands.length > 0) {
263+
for (const command of commands) {
256264
if (command.apiResponseRenderingTemplateFile) {
257265
const adaptiveCardFile = path.resolve(
258266
appDirectory,
@@ -272,31 +280,11 @@ export class CreateAppPackageDriver implements StepDriver {
272280
}
273281
}
274282

275-
const plugins = manifest.copilotExtensions
276-
? manifest.copilotExtensions.plugins
277-
: manifest.copilotAgents?.plugins;
278-
if (plugins?.length && plugins[0].file) {
279-
// API plugin
280-
const addFilesRes = await this.addPlugin(
281-
zip,
282-
plugins[0].file,
283-
appDirectory,
284-
context,
285-
!shouldwriteAllManifest ? undefined : jsonFileDir
286-
);
287-
if (addFilesRes.isErr()) {
288-
return err(addFilesRes.error);
289-
}
290-
}
291-
292-
const declarativeCopilots = manifest.copilotExtensions
293-
? manifest.copilotExtensions.declarativeCopilots
294-
: manifest.copilotAgents?.declarativeAgents;
295283
// Copilot GPT
296-
if (declarativeCopilots?.length && declarativeCopilots[0].file) {
284+
if (declarativeAgents?.length && declarativeAgents[0].file) {
297285
const declarativeAgentManifestFile = path.resolve(
298286
hasTTKGeneratedFolder ? generatedFolder : appDirectory,
299-
declarativeCopilots[0].file
287+
declarativeAgents[0].file
300288
);
301289
const checkExistenceRes = await this.validateReferencedFile(
302290
declarativeAgentManifestFile,
@@ -308,7 +296,7 @@ export class CreateAppPackageDriver implements StepDriver {
308296

309297
const addFileWithVariableRes = await this.addFileWithVariable(
310298
zip,
311-
declarativeCopilots[0].file,
299+
declarativeAgents[0].file,
312300
declarativeAgentManifestFile,
313301
ManifestType.DeclarativeCopilotManifest,
314302
context,
@@ -338,7 +326,7 @@ export class CreateAppPackageDriver implements StepDriver {
338326
hasTTKGeneratedFolder ? generatedFolder : appDirectory,
339327
pluginFileAbsolutePath
340328
);
341-
const useForwardSlash = declarativeCopilots[0].file.concat(pluginFile).includes("/");
329+
const useForwardSlash = declarativeAgents[0].file.concat(pluginFile).includes("/");
342330

343331
const addPluginRes = await this.addPlugin(
344332
zip,

packages/fx-core/src/component/driver/teamsApp/utils/CopilotGptManifestUtils.ts

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,29 @@
33

44
import {
55
Colors,
6+
DeclarativeAgentManifest,
7+
DeclarativeAgentManifestConverter,
8+
DeclarativeCopilotCapabilityName,
69
DeclarativeCopilotManifestSchema,
710
DefaultPluginManifestFileName,
11+
err,
812
FxError,
913
IDeclarativeCopilot,
1014
ManifestUtil,
15+
ok,
16+
OneDriveAndSharePointCapability,
1117
Platform,
1218
Result,
13-
err,
14-
ok,
19+
SharePointIDs,
1520
Site,
16-
DeclarativeCopilotCapabilityName,
17-
OneDriveAndSharePointCapability,
1821
WebSearchCapability,
19-
SharePointIDs,
20-
DeclarativeAgentManifest,
21-
DeclarativeAgentManifestConverter,
2222
} from "@microsoft/teamsfx-api";
2323
import fs from "fs-extra";
2424
import { EOL } from "os";
2525
import path from "path";
2626
import stripBom from "strip-bom";
27+
import { Context } from "vm";
28+
import { listAPIInfo } from "../../../../common/daSpecParser";
2729
import { getDefaultString, getLocalizedString } from "../../../../common/localizeUtils";
2830
import {
2931
FileNotFoundError,
@@ -41,8 +43,6 @@ import { AppStudioResultFactory } from "../results";
4143
import { manifestUtils } from "./ManifestUtils";
4244
import { pluginManifestUtils } from "./PluginManifestUtils";
4345
import { getResolvedManifest } from "./utils";
44-
import { Context } from "vm";
45-
import { listAPIInfo } from "../../../../common/daSpecParser";
4646

4747
export class CopilotGptManifestUtils {
4848
public async readCopilotGptManifestFile(
@@ -344,8 +344,7 @@ export class CopilotGptManifestUtils {
344344

345345
public logValidationErrors(
346346
validationRes: DeclarativeCopilotManifestValidationResult,
347-
platform: Platform,
348-
pluginPath: string
347+
platform: Platform
349348
): string | Array<{ content: string; color: Colors }> {
350349
const validationErrors = validationRes.validationResult;
351350
const filePath = validationRes.filePath;
@@ -380,15 +379,12 @@ export class CopilotGptManifestUtils {
380379
}
381380

382381
for (const actionValidationRes of validationRes.actionValidationResult) {
383-
if (!pluginPath || actionValidationRes.filePath !== pluginPath) {
384-
// do not output validation result of the Declarative Copilot if same file has been validated when validating plugin manifest.
385-
const actionValidationMessage = pluginManifestUtils.logValidationErrors(
386-
actionValidationRes,
387-
platform
388-
) as string;
389-
if (actionValidationMessage) {
390-
outputMessage += (!outputMessage ? "" : EOL) + actionValidationMessage;
391-
}
382+
const actionValidationMessage = pluginManifestUtils.logValidationErrors(
383+
actionValidationRes,
384+
platform
385+
) as string;
386+
if (actionValidationMessage) {
387+
outputMessage += (!outputMessage ? "" : EOL) + actionValidationMessage;
392388
}
393389
}
394390

@@ -414,16 +410,14 @@ export class CopilotGptManifestUtils {
414410
}
415411

416412
for (const actionValidationRes of validationRes.actionValidationResult) {
417-
if (!pluginPath || actionValidationRes.filePath !== pluginPath) {
418-
const actionValidationMessage = pluginManifestUtils.logValidationErrors(
419-
actionValidationRes,
420-
platform
413+
const actionValidationMessage = pluginManifestUtils.logValidationErrors(
414+
actionValidationRes,
415+
platform
416+
);
417+
if (actionValidationMessage) {
418+
outputMessage.push(
419+
...(actionValidationMessage as Array<{ content: string; color: Colors }>)
421420
);
422-
if (actionValidationMessage) {
423-
outputMessage.push(
424-
...(actionValidationMessage as Array<{ content: string; color: Colors }>)
425-
);
426-
}
427421
}
428422
}
429423

0 commit comments

Comments
 (0)