-
Notifications
You must be signed in to change notification settings - Fork 149
Replace JsonCli tool with direct nupkg parsing and native dotnet new
#4947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,36 +5,172 @@ | |
|
|
||
| import { type IActionContext } from '@microsoft/vscode-azext-utils'; | ||
| import { composeArgs, withArg, withNamedArg, withQuotedArg, type CommandLineArgs } from '@microsoft/vscode-processutils'; | ||
| import * as fs from 'fs'; | ||
| import * as os from 'os'; | ||
| import * as path from 'path'; | ||
| import { coerce as semVerCoerce, type SemVer } from 'semver'; | ||
| import { type FuncVersion } from '../../FuncVersion'; | ||
| import { ext } from "../../extensionVariables"; | ||
| import { localize } from '../../localize'; | ||
| import { cpUtils } from "../../utils/cpUtils"; | ||
| import { findShortNameByIdentity, parseTemplatesFromNupkg } from './parseNupkgTemplates'; | ||
|
|
||
| export async function executeDotnetTemplateCommand(context: IActionContext, version: FuncVersion, projTemplateKey: string, workingDirectory: string | undefined, operation: 'list' | 'create', additionalArgs?: CommandLineArgs): Promise<string> { | ||
| const jsonDllPath: string = ext.context.asAbsolutePath(path.join('resources', 'dotnetJsonCli', 'Microsoft.TemplateEngine.JsonCli.dll')); | ||
|
|
||
| const args = composeArgs( | ||
| withNamedArg('--roll-forward', 'Major'), | ||
| withQuotedArg(jsonDllPath), | ||
| withNamedArg('--templateDir', getDotnetTemplateDir(context, version, projTemplateKey), { shouldQuote: true }), | ||
| withNamedArg('--operation', operation), | ||
| withArg(...(additionalArgs ?? [])), | ||
| )(); | ||
| return await cpUtils.executeCommand( | ||
| undefined, | ||
| workingDirectory, | ||
| 'dotnet', | ||
| args); | ||
| const itemNupkgFileName = 'item.nupkg'; | ||
| const projectNupkgFileName = 'project.nupkg'; | ||
|
|
||
| export enum DotnetTemplateOperation { | ||
| List = 'list', | ||
| Create = 'create', | ||
| } | ||
|
|
||
| /** | ||
| * Lists templates by parsing nupkg files directly (no longer uses the JsonCli DLL). | ||
| */ | ||
| export async function executeDotnetTemplateCommand(context: IActionContext, version: FuncVersion, projTemplateKey: string, workingDirectory: string | undefined, operation: DotnetTemplateOperation.List, additionalArgs?: CommandLineArgs): Promise<string>; | ||
| /** | ||
| * @deprecated For 'create' operations, use {@link executeDotnetTemplateCreate} instead. | ||
| */ | ||
| export async function executeDotnetTemplateCommand(context: IActionContext, version: FuncVersion, projTemplateKey: string, workingDirectory: string | undefined, operation: DotnetTemplateOperation, additionalArgs?: CommandLineArgs): Promise<string>; | ||
| export async function executeDotnetTemplateCommand(context: IActionContext, version: FuncVersion, projTemplateKey: string, workingDirectory: string | undefined, operation: DotnetTemplateOperation, additionalArgs?: CommandLineArgs): Promise<string> { | ||
| const templateDir = getDotnetTemplateDir(context, version, projTemplateKey); | ||
|
|
||
| if (operation === DotnetTemplateOperation.List) { | ||
| return await listDotnetTemplates(templateDir); | ||
| } else { | ||
| // Fallback for any remaining callers that haven't migrated to executeDotnetTemplateCreate | ||
| const { identity, templateArgs } = parseJsonCliStyleArgs(additionalArgs ?? []); | ||
| await executeDotnetTemplateCreate(context, version, projTemplateKey, workingDirectory, identity, templateArgs); | ||
| return ''; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Lists all templates from the item.nupkg and project.nupkg in the template directory | ||
| * by parsing the `.template.config/template.json` files directly from the nupkg archives. | ||
| */ | ||
| async function listDotnetTemplates(templateDir: string): Promise<string> { | ||
| const itemNupkg = path.join(templateDir, itemNupkgFileName); | ||
| const projectNupkg = path.join(templateDir, projectNupkgFileName); | ||
|
|
||
| const templates: object[] = []; | ||
|
|
||
| for (const nupkgPath of [itemNupkg, projectNupkg]) { | ||
| try { | ||
| await fs.promises.access(nupkgPath); | ||
| templates.push(...await parseTemplatesFromNupkg(nupkgPath)); | ||
| } catch { | ||
| // nupkg doesn't exist, skip | ||
| } | ||
| } | ||
|
|
||
| return JSON.stringify(templates); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a function or project from a .NET template using native `dotnet new` commands. | ||
| * Uses an isolated DOTNET_CLI_HOME to avoid polluting the user's global template installation. | ||
| */ | ||
| export async function executeDotnetTemplateCreate( | ||
| context: IActionContext, | ||
| version: FuncVersion, | ||
| projTemplateKey: string, | ||
| workingDirectory: string | undefined, | ||
| identity: string, | ||
| templateArgs: Record<string, string>, | ||
| ): Promise<void> { | ||
| const templateDir = getDotnetTemplateDir(context, version, projTemplateKey); | ||
| const itemNupkg = path.join(templateDir, itemNupkgFileName); | ||
| const projectNupkg = path.join(templateDir, projectNupkgFileName); | ||
|
|
||
| // Collect existing nupkg paths | ||
| const nupkgPaths: string[] = []; | ||
| for (const p of [itemNupkg, projectNupkg]) { | ||
| try { | ||
| await fs.promises.access(p); | ||
| nupkgPaths.push(p); | ||
| } catch { | ||
| // doesn't exist, skip | ||
| } | ||
| } | ||
|
|
||
| // Find the shortName for the given template identity | ||
| const shortName = await findShortNameByIdentity(nupkgPaths, identity); | ||
|
|
||
| // Use an isolated DOTNET_CLI_HOME so template installation doesn't affect the user's global state | ||
| // This is how the JSON CLI tool operateed | ||
|
nturinski marked this conversation as resolved.
Outdated
|
||
| const tempCliHome = path.join(os.tmpdir(), `azfunc-dotnet-home-${Date.now()}-${Math.random().toString(36).substring(2)}`); | ||
|
bwateratmsft marked this conversation as resolved.
Outdated
|
||
| const prevDotnetCliHome = process.env.DOTNET_CLI_HOME; | ||
|
|
||
| try { | ||
| process.env.DOTNET_CLI_HOME = tempCliHome; | ||
|
|
||
|
Comment on lines
+81
to
+88
|
||
| // Install template packages | ||
| for (const nupkgPath of nupkgPaths) { | ||
| await cpUtils.executeCommand( | ||
| undefined, | ||
| undefined, | ||
| 'dotnet', | ||
| composeArgs(withArg('new', 'install'), withQuotedArg(nupkgPath))(), | ||
| ); | ||
| } | ||
|
|
||
| // Build dotnet new args: dotnet new <shortName> --<param> <value> ... | ||
| const createArgs = composeArgs( | ||
| withArg('new', shortName), | ||
| ...Object.entries(templateArgs) | ||
| .filter(([, value]) => value !== undefined && value !== '') | ||
| .map(([key, value]) => withNamedArg(`--${key}`, value, { shouldQuote: true })), | ||
| )(); | ||
|
|
||
| await cpUtils.executeCommand( | ||
| undefined, | ||
| workingDirectory, | ||
| 'dotnet', | ||
| createArgs, | ||
| ); | ||
| } finally { | ||
| // Restore DOTNET_CLI_HOME | ||
| if (prevDotnetCliHome !== undefined) { | ||
| process.env.DOTNET_CLI_HOME = prevDotnetCliHome; | ||
| } else { | ||
| delete process.env.DOTNET_CLI_HOME; | ||
| } | ||
|
|
||
| // Clean up isolated home directory | ||
| await fs.promises.rm(tempCliHome, { recursive: true, force: true }).catch(() => { /* best-effort cleanup */ }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Parses JSON Cli-style arguments (--identity, --arg:name, etc.) into structured data. | ||
| * Used only as a compatibility shim for callers that have not yet migrated to executeDotnetTemplateCreate. | ||
| */ | ||
| function parseJsonCliStyleArgs(args: CommandLineArgs): { identity: string; templateArgs: Record<string, string> } { | ||
| const flatArgs: string[] = (Array.isArray(args) ? args : [args]).map(String); | ||
| let identity = ''; | ||
| const templateArgs: Record<string, string> = {}; | ||
|
|
||
| for (let i = 0; i < flatArgs.length; i++) { | ||
| const arg = flatArgs[i]; | ||
| if (arg === '--identity' && i + 1 < flatArgs.length) { | ||
| identity = flatArgs[i + 1].replace(/^"|"$/g, ''); | ||
| i++; | ||
| } else if (arg.startsWith('--arg:') && i + 1 < flatArgs.length) { | ||
| const paramName = arg.replace('--arg:', ''); | ||
| templateArgs[paramName] = flatArgs[i + 1].replace(/^"|"$/g, ''); | ||
| i++; | ||
| } | ||
| } | ||
|
|
||
| return { identity, templateArgs }; | ||
| } | ||
|
|
||
| export function getDotnetItemTemplatePath(context: IActionContext, version: FuncVersion, projTemplateKey: string): string { | ||
| return path.join(getDotnetTemplateDir(context, version, projTemplateKey), 'item.nupkg'); | ||
| return path.join(getDotnetTemplateDir(context, version, projTemplateKey), itemNupkgFileName); | ||
| } | ||
|
|
||
| export function getDotnetProjectTemplatePath(context: IActionContext, version: FuncVersion, projTemplateKey: string): string { | ||
| return path.join(getDotnetTemplateDir(context, version, projTemplateKey), 'project.nupkg'); | ||
| return path.join(getDotnetTemplateDir(context, version, projTemplateKey), projectNupkgFileName); | ||
| } | ||
|
|
||
| export function getDotnetTemplateDir(context: IActionContext, version: FuncVersion, projTemplateKey: string): string { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New behavior (nupkg template parsing +
dotnet new install/dotnet new) is not directly covered by tests. Consider adding unit/integration tests that validate: (1) parsing of.template.config/template.jsonfrom the shipped backup nupkgs, including choice/boolean symbols and multi-shortName cases; and (2)executeDotnetTemplateCreateruns with an isolated template home and succeeds in generating output in a temp directory.