Skip to content

Commit ca39473

Browse files
committed
wip garbage copilot mvp
1 parent 65ca631 commit ca39473

14 files changed

+450
-18
lines changed

package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@
149149
"title": "%azureFunctions.browseWebsite%",
150150
"category": "Azure Functions"
151151
},
152+
{
153+
"command": "azureFunctions.createProjectFromFunctionApp",
154+
"title": "%azureFunctions.createProjectFromFunctionApp%",
155+
"category": "Azure Functions"
156+
},
152157
{
153158
"command": "azureFunctions.configureDeploymentSource",
154159
"title": "%azureFunctions.configureDeploymentSource%",
@@ -211,6 +216,18 @@
211216
"category": "Azure Functions",
212217
"enablement": "!virtualWorkspace"
213218
},
219+
{
220+
"command": "azureFunctions.testExternalRuntime",
221+
"title": "Test External Runtime Configuration",
222+
"category": "Azure Functions (Test)",
223+
"enablement": "!virtualWorkspace"
224+
},
225+
{
226+
"command": "azureFunctions.testTypeScriptExternalRuntime",
227+
"title": "Test TypeScript External Runtime",
228+
"category": "Azure Functions (Test)",
229+
"enablement": "!virtualWorkspace"
230+
},
214231
{
215232
"command": "azureFunctions.createSlot",
216233
"title": "%azureFunctions.createSlot%",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"azureFunctions.assignManagedIdentity": "Assign Managed Identity to Function App...",
1414
"azureFunctions.unassignManagedIdentity": "Unassign Managed Identity from Function App...",
1515
"azureFunctions.browseWebsite": "Browse Website",
16+
"azureFunctions.createProjectFromFunctionApp": "Create Project from Function App...",
1617
"azureFunctions.configureDeploymentSource": "Configure Deployment Source...",
1718
"azureFunctions.connectToGitHub": "Connect to GitHub Repository...",
1819
"azureFunctions.copyFunctionUrl": "Copy Function Url",

src/commands/createNewProject/IProjectWizardContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export interface IProjectWizardContext extends IActionContext {
3131
targetFramework?: string | string[];
3232

3333
containerizedProject?: boolean;
34+
35+
// External runtime configuration from Azure APIs
36+
externalRuntimeConfig?: {
37+
runtimeName: string;
38+
runtimeVersion: string;
39+
};
3440
}
3541

3642
export type OpenBehavior = 'AddToWorkspace' | 'OpenInNewWindow' | 'OpenInCurrentWindow' | 'AlreadyOpen' | 'DontOpen';

src/commands/createNewProject/NewProjectLanguageStep.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { type QuickPickOptions } from 'vscode';
88
import { ProjectLanguage, nodeDefaultModelVersion, nodeLearnMoreLink, nodeModels, pythonDefaultModelVersion, pythonLearnMoreLink, pythonModels } from '../../constants';
99
import { localize } from '../../localize';
1010
import { TemplateSchemaVersion } from '../../templates/TemplateProviderBase';
11+
import { getRuntimeMapping } from '../../utils/externalRuntimeUtils';
1112
import { nonNullProp } from '../../utils/nonNull';
1213
import { openUrl } from '../../utils/openUrl';
1314
import { FunctionListStep } from '../createFunction/FunctionListStep';
@@ -38,6 +39,19 @@ export class NewProjectLanguageStep extends AzureWizardPromptStep<IProjectWizard
3839
}
3940

4041
public async prompt(context: IProjectWizardContext): Promise<void> {
42+
if (context.externalRuntimeConfig) {
43+
// Auto-set language based on external runtime config
44+
const mapping = getRuntimeMapping(context.externalRuntimeConfig.runtimeName, context.externalRuntimeConfig.runtimeVersion);
45+
if (mapping) {
46+
context.language = mapping.language as ProjectLanguage;
47+
context.languageModel = mapping.languageModel;
48+
if (mapping.targetFramework) {
49+
context.targetFramework = [mapping.targetFramework];
50+
}
51+
return;
52+
}
53+
}
54+
4155
// Only display 'supported' languages that can be debugged in VS Code
4256
let languagePicks: IAzureQuickPickItem<{ language: ProjectLanguage, model?: number } | undefined>[] = [
4357
{ label: ProjectLanguage.JavaScript, data: { language: ProjectLanguage.JavaScript } },

src/commands/createNewProject/createNewProject.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ export async function createNewProjectFromCommand(
4747
});
4848
}
4949

50-
export async function createNewProjectInternal(context: IActionContext, options: api.ICreateFunctionOptions): Promise<void> {
50+
export async function createNewProjectInternal(context: IActionContext, options: api.ICreateFunctionOptions & {
51+
externalRuntimeConfig?: {
52+
runtimeName: string;
53+
runtimeVersion: string;
54+
};
55+
}): Promise<void> {
5156
addLocalFuncTelemetry(context, undefined);
5257

5358
const language: ProjectLanguage | undefined = <ProjectLanguage>options.language || getGlobalSetting(projectLanguageSetting);
@@ -59,7 +64,8 @@ export async function createNewProjectInternal(context: IActionContext, options:
5964
{
6065
language,
6166
version: tryParseFuncVersion(version),
62-
projectTemplateKey
67+
projectTemplateKey,
68+
externalRuntimeConfig: options.externalRuntimeConfig
6369
},
6470
await createActivityContext()
6571
);

src/commands/createNewProject/dotnetSteps/DotnetRuntimeStep.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,26 @@ export class DotnetRuntimeStep extends AzureWizardPromptStep<IProjectWizardConte
2121
const filteredRuntimes = runtimes.filter(runtime => context.targetFramework?.includes(runtime.targetFramework));
2222
let workerRuntime: cliFeedUtils.IWorkerRuntime | undefined = undefined;
2323
if (filteredRuntimes.length > 1) {
24-
const placeHolder: string = localize('selectWorkerRuntime', 'Select a .NET runtime');
25-
workerRuntime = (await context.ui.showQuickPick(new DotnetRuntimeStep().getPicks(context, filteredRuntimes), { placeHolder })).data;
24+
if (context.externalRuntimeConfig) {
25+
// Separate filtered runtimes by isolated and non-isolated
26+
const isolatedRuntimes = filteredRuntimes.filter(r => r.capabilities.includes('isolated'));
27+
const inProcessRuntimes = filteredRuntimes.filter(r => !r.capabilities.includes('isolated'));
28+
29+
30+
// Pick runtime based on the expected linuxFxVersion
31+
const isIsolatedRequest = context.externalRuntimeConfig.runtimeName.toLowerCase().includes('isolated');
32+
if (isIsolatedRequest && isolatedRuntimes.length > 0) {
33+
workerRuntime = isolatedRuntimes[0];
34+
} else if (!isIsolatedRequest && inProcessRuntimes.length > 0) {
35+
workerRuntime = inProcessRuntimes[0];
36+
} else {
37+
// Fallback: prefer isolated if available, otherwise use first available
38+
workerRuntime = isolatedRuntimes[0] || inProcessRuntimes[0] || filteredRuntimes[0];
39+
}
40+
} else {
41+
const placeHolder: string = localize('selectWorkerRuntime', 'Select a .NET runtime');
42+
workerRuntime = (await context.ui.showQuickPick(new DotnetRuntimeStep().getPicks(context, filteredRuntimes), { placeHolder })).data;
43+
}
2644
} else if (filteredRuntimes.length === 1) {
2745
workerRuntime = filteredRuntimes[0];
2846
}
@@ -39,6 +57,26 @@ export class DotnetRuntimeStep extends AzureWizardPromptStep<IProjectWizardConte
3957
}
4058

4159
public async prompt(context: IProjectWizardContext): Promise<void> {
60+
if (context.externalRuntimeConfig) {
61+
// Auto-select runtime based on external config using linuxFxVersion format
62+
const runtimes = await getRuntimes(context);
63+
const runtimeName = context.externalRuntimeConfig.runtimeName.toLowerCase();
64+
const runtimeVersion = context.externalRuntimeConfig.runtimeVersion;
65+
66+
// Construct the expected linuxFxVersion string
67+
const expectedLinuxFxVersion = `${runtimeName}|${runtimeVersion}`.toLowerCase();
68+
69+
// Find runtime that matches the expected linuxFxVersion pattern
70+
const isIsolatedRequest = expectedLinuxFxVersion.includes('isolated');
71+
const result = runtimes.find(r => {
72+
const runtimeIsIsolated = r.displayInfo.displayName.toLowerCase().includes('isolated');
73+
return runtimeIsIsolated === isIsolatedRequest;
74+
}) || runtimes[0];
75+
76+
setWorkerRuntime(context, result);
77+
return;
78+
}
79+
4280
const placeHolder: string = localize('selectWorkerRuntime', 'Select a .NET runtime');
4381
let result: cliFeedUtils.IWorkerRuntime | undefined;
4482
while (true) {
@@ -54,7 +92,8 @@ export class DotnetRuntimeStep extends AzureWizardPromptStep<IProjectWizardConte
5492
}
5593

5694
public shouldPrompt(context: IProjectWizardContext): boolean {
57-
return !context.workerRuntime;
95+
// Skip prompting if external runtime configuration is provided or worker runtime already set
96+
return !context.workerRuntime && !context.externalRuntimeConfig;
5897
}
5998

6099
private async getPicks(context: IProjectWizardContext, runtimes?: cliFeedUtils.IWorkerRuntime[]): Promise<IAzureQuickPickItem<cliFeedUtils.IWorkerRuntime | undefined>[]> {

src/commands/createNewProject/javaSteps/JavaVersionStep.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class JavaVersionStep extends AzureWizardPromptStep<IJavaProjectWizardCon
5454
}
5555

5656
public shouldPrompt(context: IJavaProjectWizardContext): boolean {
57-
return !context.javaVersion;
57+
// Skip prompting if external runtime configuration is provided
58+
return !context.javaVersion && !context.externalRuntimeConfig;
5859
}
5960
}

src/commands/createNewProject/pythonSteps/IPythonVenvWizardContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ export interface IPythonVenvWizardContext extends IActionContext {
1414
useExistingVenv?: boolean;
1515
venvName?: string;
1616
suppressSkipVenv?: boolean;
17+
18+
// External runtime configuration from Azure APIs
19+
externalRuntimeConfig?: {
20+
runtimeName: string;
21+
runtimeVersion: string;
22+
};
1723
}

src/commands/createNewProject/pythonSteps/PythonAliasListStep.ts

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { AzureWizardPromptStep, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils";
7+
import { gt, satisfies } from "semver";
78
import { localize } from "../../../localize";
89
import { getGlobalSetting } from "../../../vsCodeConfig/settings";
910
import { EnterPythonAliasStep } from "./EnterPythonAliasStep";
@@ -14,6 +15,21 @@ export class PythonAliasListStep extends AzureWizardPromptStep<IPythonVenvWizard
1415
public hideStepCount: boolean = true;
1516

1617
public async prompt(context: IPythonVenvWizardContext): Promise<void> {
18+
19+
20+
if (context.externalRuntimeConfig) {
21+
const installedVersions = await getInstalledPythonVersions(context);
22+
const matchingVersion = findBestMatchingVersion(context.externalRuntimeConfig.runtimeVersion, installedVersions);
23+
24+
if (matchingVersion) {
25+
context.pythonAlias = matchingVersion.alias;
26+
context.telemetry.properties.pythonAliasBehavior = 'externalRuntimeConfigMatch';
27+
return;
28+
} else {
29+
return;
30+
}
31+
}
32+
1733
const placeHolder: string = localize('selectAlias', 'Select a Python interpreter to create a virtual environment');
1834
const result: string | boolean = (await context.ui.showQuickPick(getPicks(context), { placeHolder })).data;
1935
if (typeof result === 'string') {
@@ -26,6 +42,7 @@ export class PythonAliasListStep extends AzureWizardPromptStep<IPythonVenvWizard
2642
}
2743

2844
public shouldPrompt(context: IPythonVenvWizardContext): boolean {
45+
// Skip prompting if external runtime configuration is provided
2946
return !context.useExistingVenv && !context.pythonAlias;
3047
}
3148

@@ -55,7 +72,43 @@ async function getPicks(context: IPythonVenvWizardContext): Promise<IAzureQuickP
5572
}
5673

5774
const picks: IAzureQuickPickItem<string | boolean>[] = [];
58-
const versions: string[] = [];
75+
const pythonVersions = await getInstalledPythonVersions(context);
76+
pythonVersions.forEach(pv => picks.push({
77+
label: pv.alias,
78+
description: pv.version,
79+
data: pv.alias
80+
}));
81+
82+
picks.push({ label: localize('enterAlias', '$(keyboard) Manually enter Python interpreter or full path'), data: true });
83+
84+
if (!context.suppressSkipVenv) {
85+
picks.push({ label: localize('skipVenv', '$(circle-slash) Skip virtual environment'), data: false, suppressPersistence: true });
86+
}
87+
88+
return picks;
89+
}
90+
91+
interface InstalledPythonVersion {
92+
alias: string;
93+
version: string;
94+
}
95+
96+
// use this method to preselect python version if runtime version is provided externally
97+
98+
async function getInstalledPythonVersions(context: IPythonVenvWizardContext): Promise<InstalledPythonVersion[]> {
99+
const supportedVersions: string[] = await getSupportedPythonVersions(context, context.version);
100+
101+
const aliasesToTry: string[] = ['python', 'python3', 'py'];
102+
for (const version of supportedVersions) {
103+
aliasesToTry.push(`python${version}`, `py -${version}`);
104+
}
105+
106+
const globalPythonPathSetting: string | undefined = getGlobalSetting('pythonPath', 'python');
107+
if (globalPythonPathSetting) {
108+
aliasesToTry.unshift(globalPythonPathSetting);
109+
}
110+
111+
const versions: InstalledPythonVersion[] = [];
59112
for (const alias of aliasesToTry) {
60113
let version: string;
61114
try {
@@ -64,23 +117,27 @@ async function getPicks(context: IPythonVenvWizardContext): Promise<IAzureQuickP
64117
continue;
65118
}
66119

67-
if (isSupportedPythonVersion(supportedVersions, version) && !versions.some(v => v === version)) {
68-
picks.push({
69-
label: alias,
70-
description: version,
71-
data: alias
120+
if (isSupportedPythonVersion(supportedVersions, version) && !versions.some(v => v.version === version)) {
121+
versions.push({
122+
alias,
123+
version,
72124
});
73-
versions.push(version);
74125
}
75126
}
76127

77128
context.telemetry.properties.detectedPythonVersions = versions.join(',');
129+
return versions;
130+
}
78131

79-
picks.push({ label: localize('enterAlias', '$(keyboard) Manually enter Python interpreter or full path'), data: true });
132+
// use semver to find a matching python version
133+
function findMatchingVersion(requestedVersion: string, versions: InstalledPythonVersion[]): InstalledPythonVersion | undefined {
134+
return versions.find(v => satisfies(v.version, requestedVersion));
135+
}
80136

81-
if (!context.suppressSkipVenv) {
82-
picks.push({ label: localize('skipVenv', '$(circle-slash) Skip virtual environment'), data: false, suppressPersistence: true });
137+
function findBestMatchingVersion(requestedVersion: string, versions: InstalledPythonVersion[]): InstalledPythonVersion | undefined {
138+
let matchingVersion = findMatchingVersion(requestedVersion, versions);
139+
if (!matchingVersion) {
140+
matchingVersion = versions.reduce((prev, current) => (gt(current.version, prev.version) ? current : prev));
83141
}
84-
85-
return picks;
142+
return matchingVersion;
86143
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { type IActionContext } from '@microsoft/vscode-azext-utils';
7+
import { getCurrentWorkspacePath, validateRuntimeParams } from '../utils/externalRuntimeUtils';
8+
import { type FunctionAppStackValue } from './createFunctionApp/stacks/models/FunctionAppStackModel';
9+
import { createNewProjectInternal } from './createNewProject/createNewProject';
10+
11+
export type RuntimeName = FunctionAppStackValue | 'dotnet-isolated';
12+
13+
interface InitializeProjectForSlashAzureParams {
14+
id: string;
15+
runtimeName: RuntimeName;
16+
runtimeVersion: string;
17+
}
18+
19+
/**
20+
*
21+
* Needs to initialize a local project based on the given function app information.
22+
*
23+
* @param context
24+
* @param id id of the function app in Azure
25+
* @param runtimeName ex: 'node', 'python', 'dotnet', etc.
26+
* @param runtimeVersion ex: '18', '3.11', '8.0', etc.
27+
*/
28+
export async function initializeProjectForSlashAzure(
29+
context: IActionContext,
30+
params: InitializeProjectForSlashAzureParams
31+
): Promise<void> {
32+
33+
const { id, runtimeName, runtimeVersion } = params;
34+
35+
// Validate the runtime configuration
36+
validateRuntimeParams(runtimeName, runtimeVersion);
37+
38+
context.telemetry.properties.externalRuntimeName = runtimeName;
39+
context.telemetry.properties.externalRuntimeVersion = runtimeVersion;
40+
41+
// todo set id in settings as the default app to deploy to
42+
console.log(id);
43+
44+
// Initialize the project using the existing createNewProjectInternal with external runtime config
45+
await createNewProjectInternal(context, {
46+
folderPath: getCurrentWorkspacePath(),
47+
suppressOpenFolder: true, // Don't open folder since we're in current workspace
48+
externalRuntimeConfig: {
49+
runtimeName,
50+
runtimeVersion
51+
}
52+
});
53+
}

0 commit comments

Comments
 (0)