Skip to content

Commit 5e96d3e

Browse files
Copilottecton
andauthored
feat(cli): cherry-pick PR #15402 — use alias name for template id (#15407)
* Initial plan * feat(cli): cherry-pick PR #15402 - use alias name for template id Co-authored-by: tecton <886116+tecton@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tecton <886116+tecton@users.noreply.github.com>
1 parent a3dcf77 commit 5e96d3e

File tree

15 files changed

+304
-38
lines changed

15 files changed

+304
-38
lines changed

packages/cli/.vscode/launch.json

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,34 @@
1010
"name": "Launch new command (non-interactive)",
1111
"skipFiles": ["<node_internals>/**"],
1212
"program": "${workspaceFolder}/cli.js",
13-
"args": ["new", "-i", "false"],
13+
"args": ["new", "-c", "declarative-agent", "-n", "da","-i", "false"],
14+
"env": {
15+
"TEAMSFX_GENERATE_CONFIG_FILES": "true",
16+
"TEMPLATE_VERSION": "local"
17+
},
18+
"outFiles": [
19+
"${workspaceFolder}/lib/**/*.js",
20+
"${workspaceFolder}/../fx-core/build/**/*.js",
21+
"${workspaceFolder}/../api/build/**/*.js"
22+
],
23+
"resolveSourceMapLocations": [
24+
"${workspaceFolder}/lib/**/*.js",
25+
"${workspaceFolder}/../fx-core/build/**/*.js",
26+
"${workspaceFolder}/../api/build/**/*.js"
27+
],
28+
"console": "integratedTerminal"
29+
},
30+
{
31+
"type": "pwa-node",
32+
"request": "launch",
33+
"name": "Launch init command (non-interactive)",
34+
"skipFiles": ["<node_internals>/**"],
35+
"program": "${workspaceFolder}/cli.js",
36+
"args": ["init"],
37+
"env": {
38+
"TEAMSFX_GENERATE_CONFIG_FILES": "true",
39+
"TEMPLATE_VERSION": "local"
40+
},
1441
"outFiles": [
1542
"${workspaceFolder}/lib/**/*.js",
1643
"${workspaceFolder}/../fx-core/build/**/*.js",

packages/cli/src/commands/models/create.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function adjustOptions(options: CLICommandOption[]) {
3131
for (const option of options) {
3232
if (option.type === "string" && option.name === CliQuestionName.Capability) {
3333
// use dynamic options for capability question
34-
option.choices = listAllTemplates().map((o) => o.name);
34+
option.choices = listAllTemplates().flatMap((o) => (o.alias ? [o.alias, o.name] : [o.name]));
3535
break;
3636
}
3737
}
@@ -46,7 +46,7 @@ export function getCreateCommand(): CLICommand {
4646
options: [...adjustOptions(CreateProjectOptions)],
4747
examples: [
4848
{
49-
command: `${process.env.TEAMSFX_CLI_BIN_NAME} new -c copilot-gpt-basic -n myagent -i false`,
49+
command: `${process.env.TEAMSFX_CLI_BIN_NAME} new -c declarative-agent -n myagent -i false`,
5050
description: "Create a new declarative agent",
5151
},
5252
{
@@ -72,11 +72,12 @@ export function getCreateCommand(): CLICommand {
7272
// for non-interactive mode, we need to preset project-type from capability to make sure the question model works
7373
const capability = inputs.capabilities as string;
7474
inputs["template-name"] = capability;
75-
if (inputs["programming-language"] === undefined) {
76-
// preset programming language if not specified
77-
const templates = listAllTemplates();
78-
const matched = templates.find((t) => t.name === capability);
79-
if (matched) {
75+
const templates = listAllTemplates();
76+
const matched = templates.find((t) => t.name === capability || t.alias === capability);
77+
if (matched) {
78+
inputs["template-name"] = matched.name;
79+
if (inputs["programming-language"] === undefined) {
80+
// preset programming language if not specified
8081
inputs["programming-language"] = matched.language as any;
8182
}
8283
}

packages/cli/src/commands/models/listTemplates.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33
import { CLICommand, ok, Platform } from "@microsoft/teamsfx-api";
4-
import {
5-
featureFlagManager,
6-
FeatureFlags,
7-
getAllTemplatesOnPlatform,
8-
} from "@microsoft/teamsfx-core";
4+
import * as teamsfxCore from "@microsoft/teamsfx-core";
5+
import { Template } from "@microsoft/teamsfx-core/build/component/generator/templates/metadata/interface";
96
import chalk from "chalk";
107
import Table from "cli-table3";
118
import { logger } from "../../commonlib/logger";
@@ -15,26 +12,32 @@ import { ListFormatOption } from "../common";
1512

1613
interface TemplateGroup {
1714
name: string;
15+
alias?: string;
1816
displayName: string;
1917
description: string;
2018
language: string;
2119
}
2220

2321
export function listAllTemplates(): TemplateGroup[] {
24-
let templates = getAllTemplatesOnPlatform(Platform.VSCode);
25-
if (featureFlagManager.getBooleanValue(FeatureFlags.CLIDotNet)) {
26-
templates = getAllTemplatesOnPlatform(Platform.VS);
22+
let templates = teamsfxCore.getAllTemplatesOnPlatform(Platform.VSCode);
23+
if (teamsfxCore.featureFlagManager.getBooleanValue(teamsfxCore.FeatureFlags.CLIDotNet)) {
24+
templates = teamsfxCore.getAllTemplatesOnPlatform(Platform.VS);
2725
}
2826

27+
return groupTemplatesByName(templates as Template[]);
28+
}
29+
30+
export function groupTemplatesByName(templates: Template[]): TemplateGroup[] {
2931
// Group by template name, ignoring programming language
3032
const groupedTemplates = new Map<string, TemplateGroup>();
3133

3234
templates.forEach((template) => {
3335
if (!groupedTemplates.has(template.name)) {
34-
const templateWithDisplay = template as typeof template & { displayName?: string };
36+
const templateWithDisplay = template as Template;
3537
groupedTemplates.set(template.name, {
3638
name: template.name,
37-
displayName: templateWithDisplay.displayName || template.name,
39+
alias: template.alias,
40+
displayName: templateWithDisplay.displayName || template.alias || template.name,
3841
description: template.description,
3942
language: template.language,
4043
});
@@ -71,8 +74,9 @@ function jsonToTable(templates: TemplateGroup[]): string {
7174
let maxNameLength = 0;
7275
let maxDescriptionLength = 0;
7376
templates.forEach((template) => {
74-
if (template.name.length > maxIdLength) {
75-
maxIdLength = template.name.length;
77+
const id = template.alias || template.name;
78+
if (id.length > maxIdLength) {
79+
maxIdLength = id.length;
7680
}
7781
if (template.displayName.length > maxNameLength) {
7882
maxNameLength = template.displayName.length;
@@ -105,7 +109,8 @@ function jsonToTable(templates: TemplateGroup[]): string {
105109
});
106110

107111
templates.forEach((template) => {
108-
table.push([template.name, template.displayName, template.description]);
112+
const id = template.alias || template.name;
113+
table.push([id, template.displayName, template.description]);
109114
});
110115

111116
return table.toString();

packages/cli/tests/unit/commands.tests.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CLIContext, SystemError, err, ok, signedIn, signedOut } from "@microsoft/teamsfx-api";
22
import {
3+
CliQuestionName,
34
CollaborationConstants,
45
CollaborationStateResult,
56
FuncToolChecker,
@@ -58,6 +59,7 @@ import { addCapabilityCommand } from "../../src/commands/models/addCapability";
5859
import { addPluginCommand } from "../../src/commands/models/addPlugin";
5960
import { entraAppUpdateCommand } from "../../src/commands/models/entraAppUpdate";
6061
import { envResetCommand } from "../../src/commands/models/envReset";
62+
import * as listTemplatesModule from "../../src/commands/models/listTemplates";
6163
import { regeneratePluginCommand } from "../../src/commands/models/regeneratePlugin";
6264
import { setCommand } from "../../src/commands/models/set";
6365
import { setSensitivityLabelCommand } from "../../src/commands/models/setSensitivityLabel";
@@ -142,6 +144,90 @@ describe("CLI commands", () => {
142144
const res = await getCreateCommand().handler!(ctx);
143145
assert.isTrue(res.isErr());
144146
});
147+
148+
it("uses template alias and preset language in non-interactive mode", async () => {
149+
sandbox.stub(activate, "getFxCore").returns(new FxCore({} as any));
150+
const createProjectStub = sandbox
151+
.stub(FxCore.prototype, "createProject")
152+
.resolves(ok({ projectPath: "..." }));
153+
sandbox.stub(featureFlagManager, "getBooleanValue").returns(false);
154+
sandbox.stub(listTemplatesModule, "listAllTemplates").returns([
155+
{
156+
name: "api-plugin",
157+
alias: "api-plugin-from-scratch",
158+
displayName: "API Plugin",
159+
description: "desc",
160+
language: "typescript",
161+
},
162+
] as any);
163+
164+
const ctx: CLIContext = {
165+
command: { ...getCreateCommand(), fullName: "new" },
166+
optionValues: {
167+
capabilities: "api-plugin-from-scratch",
168+
nonInteractive: true,
169+
},
170+
globalOptionValues: {},
171+
argumentValues: [],
172+
telemetryProperties: {},
173+
};
174+
175+
const res = await getCreateCommand().handler!(ctx);
176+
177+
assert.isTrue(res.isOk());
178+
assert.isTrue(createProjectStub.calledOnce);
179+
const inputs = createProjectStub.firstCall.args[0] as any;
180+
assert.equal(inputs["template-name"], "api-plugin");
181+
assert.equal(inputs["programming-language"], "typescript");
182+
});
183+
184+
it("keeps capability as template-name when template is not found", async () => {
185+
sandbox.stub(activate, "getFxCore").returns(new FxCore({} as any));
186+
const createProjectStub = sandbox
187+
.stub(FxCore.prototype, "createProject")
188+
.resolves(ok({ projectPath: "..." }));
189+
sandbox.stub(featureFlagManager, "getBooleanValue").returns(false);
190+
sandbox.stub(listTemplatesModule, "listAllTemplates").returns([] as any);
191+
192+
const ctx: CLIContext = {
193+
command: { ...getCreateCommand(), fullName: "new" },
194+
optionValues: {
195+
capabilities: "unknown-template",
196+
nonInteractive: true,
197+
"programming-language": "javascript",
198+
},
199+
globalOptionValues: {},
200+
argumentValues: [],
201+
telemetryProperties: {},
202+
};
203+
204+
const res = await getCreateCommand().handler!(ctx);
205+
206+
assert.isTrue(res.isOk());
207+
const inputs = createProjectStub.firstCall.args[0] as any;
208+
assert.equal(inputs["template-name"], "unknown-template");
209+
assert.equal(inputs["programming-language"], "javascript");
210+
});
211+
212+
it("includes alias and name in capability choices", async () => {
213+
sandbox.stub(listTemplatesModule, "listAllTemplates").returns([
214+
{
215+
name: "api-plugin",
216+
alias: "api-plugin-from-scratch",
217+
displayName: "API Plugin",
218+
description: "desc",
219+
language: "typescript",
220+
},
221+
] as any);
222+
223+
const command = getCreateCommand();
224+
const capabilityOption = command.options?.find((o) => o.name === CliQuestionName.Capability);
225+
226+
assert.deepEqual((capabilityOption as any)?.choices, [
227+
"api-plugin-from-scratch",
228+
"api-plugin",
229+
]);
230+
});
145231
});
146232

147233
describe("createSampleCommand", async () => {
@@ -1590,6 +1676,32 @@ describe("CLI read-only commands", () => {
15901676
const res = await listTemplatesCommand.handler!(ctx);
15911677
assert.isTrue(res.isOk());
15921678
});
1679+
1680+
it("groupTemplatesByName groups by name and falls back display name", async () => {
1681+
const templates = listTemplatesModule.groupTemplatesByName([
1682+
{
1683+
name: "dup-template",
1684+
alias: "dup-alias",
1685+
description: "desc 1",
1686+
language: "typescript",
1687+
},
1688+
{
1689+
name: "dup-template",
1690+
alias: "dup-alias-2",
1691+
description: "desc 2",
1692+
language: "javascript",
1693+
},
1694+
{
1695+
name: "no-alias-template",
1696+
description: "desc 3",
1697+
language: "typescript",
1698+
},
1699+
] as any);
1700+
1701+
assert.equal(templates.length, 2);
1702+
assert.equal(templates[0].displayName, "dup-alias");
1703+
assert.equal(templates[1].displayName, "no-alias-template");
1704+
});
15931705
});
15941706
describe("listSamplesCommand", async () => {
15951707
it("json", async () => {

packages/cli/tests/unit/engine.tests.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import {
33
CLICommandOption,
44
CLIContext,
55
CLIFoundCommand,
6-
LogLevel,
7-
SystemError,
86
err,
7+
LogLevel,
98
ok,
9+
SystemError,
1010
} from "@microsoft/teamsfx-api";
1111
import {
12-
featureFlagManager,
1312
FxCore,
1413
InputValidationError,
1514
MissingEnvironmentVariablesError,
@@ -18,6 +17,7 @@ import {
1817
} from "@microsoft/teamsfx-core";
1918
import { assert } from "chai";
2019
import "mocha";
20+
import mockedEnv from "mocked-env";
2121
import * as sinon from "sinon";
2222
import * as activate from "../../src/activate";
2323
import { getFxCore, resetFxCore } from "../../src/activate";
@@ -42,9 +42,6 @@ import {
4242
import * as main from "../../src/index";
4343
import CliTelemetry from "../../src/telemetry/cliTelemetry";
4444
import { getVersion } from "../../src/utils";
45-
import mockedEnv, { RestoreFn } from "mocked-env";
46-
import { shareCommand } from "../../src/commands/models/share";
47-
import { setSensitivityLabelCommand } from "../../src/commands/models/setSensitivityLabel";
4845

4946
describe("CLI Engine", () => {
5047
const sandbox = sinon.createSandbox();
@@ -417,7 +414,7 @@ describe("CLI Engine", () => {
417414
it("should validation failed for capability", async () => {
418415
sandbox
419416
.stub(process, "argv")
420-
.value(["node", "cli", "new", "-c", "tab", "-n", "myapp", "-i", "false"]);
417+
.value(["node", "cli", "new", "-c", "da", "-n", "myapp", "-i", "false"]);
421418
let error: any = {};
422419
sandbox.stub(engine, "processResult").callsFake(async (context, fxError) => {
423420
error = fxError;

packages/fx-core/src/component/generator/templates/metadata/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
export interface Template {
55
id: string;
66
name: string;
7+
alias?: string;
8+
displayName?: string;
79
language: "typescript" | "javascript" | "csharp" | "python" | "none" | "common";
810
description: string;
911
link?: string;

packages/fx-core/src/component/m365/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export enum Hub {
1010
export const outlookCopilotAppId = "d870f6cd-4aa5-4d42-9626-ab690c041429";
1111
export const outlookBaseUrl = "https://outlook.office.com";
1212
export const officeBaseUrl = "https://www.office.com";
13-
export const advancedDASettingUrl = "https://aka.ms/atk-actions/teamsapp-extendToM365";
13+
export const advancedDASettingUrl = "https://aka.ms/atk-da-share";
1414
export const M365HelpLink = "https://aka.ms/teamsfx-actions/teamsapp-extendToM365";

0 commit comments

Comments
 (0)