Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions eng/ci/templates/jobs/test-e2e-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ jobs:
python_linux_x64:
languageWorker: 'Python'
runtime: 'linux-x64'
custom_linux_x64:
languageWorker: 'Custom'
runtime: 'linux-x64'

steps:
- pwsh: ./eng/scripts/start-emulators.ps1
Expand Down
3 changes: 3 additions & 0 deletions eng/ci/templates/jobs/test-e2e-osx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ jobs:
python_osx_x64:
languageWorker: 'Python'
runtime: 'osx-x64'
custom_osx_x64:
languageWorker: 'Custom'
runtime: 'osx-x64'

steps:
- pwsh: ./eng/scripts/start-emulators.ps1
Expand Down
3 changes: 3 additions & 0 deletions eng/ci/templates/jobs/test-e2e-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ jobs:
python_win_x64:
languageWorker: 'Python'
runtime: 'win-x64'
custom_win_x64:
languageWorker: 'Custom'
runtime: 'win-x64'

steps:
- pwsh: ./eng/scripts/start-emulators.ps1 -NoWait
Expand Down
1 change: 1 addition & 0 deletions eng/ci/templates/steps/run-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ steps:
Write-Host "##vso[task.setvariable variable=DURABLE_FUNCTION_PATH]$(Build.SourcesDirectory)/test/Azure.Functions.Cli.Tests/Resources/DurableTestFolder"
Write-Host "##vso[task.setvariable variable=INPROC_RUN_SETTINGS]$(Build.SourcesDirectory)/test/Cli/Func.E2ETests/.runsettings/start_tests/ci_pipeline/dotnet_inproc.runsettings"
Write-Host "##vso[task.setvariable variable=TEST_PROJECT_PATH]$(Build.SourcesDirectory)/test/TestFunctionApps"
Write-Host "##vso[task.setvariable variable=FUNCTIONS_PYTHON_DOCKER_IMAGE]mcr.microsoft.com/azure-functions/python:4-python3.11-buildenv"
displayName: 'Set environment variables for E2E tests'


Expand Down
5 changes: 2 additions & 3 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Azure Functions CLI 4.2.2
# Azure Functions CLI 4.2.3

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think new feature = minor

#### Host Version

- Host Version: 4.1041.200
- In-Proc Host Version: 4.41.100 (4.841.100, 4.641.100)

#### Changes

- Fix .NET template install bug (#4612)
- Add `func pack` functionality to other languages (#4600)
2 changes: 2 additions & 0 deletions src/Cli/func/ActionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ internal sealed class ActionAttribute : Attribute
public string HelpText { get; set; } = "placeholder";

public bool ShowInHelp { get; set; } = true;

public string ParentCommandName { get; set; } = string.Empty;
}
}
2 changes: 2 additions & 0 deletions src/Cli/func/ActionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ internal class ActionType
public IEnumerable<Context> SubContexts { get; set; }

public IEnumerable<string> Names { get; set; }

public IEnumerable<string> ParentCommandName { get; set; }
}
}
85 changes: 76 additions & 9 deletions src/Cli/func/Actions/HelpAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public HelpAction(IEnumerable<TypeAttributePair> actions, Func<Type, IAction> cr
Type = type,
Contexts = attributes.Select(a => a.Context),
SubContexts = attributes.Select(a => a.SubContext),
Names = attributes.Select(a => a.Name)
Names = attributes.Select(a => a.Name),
ParentCommandName = attributes.Select(a => a.ParentCommandName)
};
});
}
Expand Down Expand Up @@ -187,7 +188,8 @@ private void DisplayGeneralHelp()
.WriteLine("Usage: func [context] <action> [-/--options]")
.WriteLine();
DisplayContextsHelp(contexts);
var actions = _actionTypes.Where(a => a.Contexts.Contains(Context.None));
var actions = _actionTypes
.Where(a => a.Contexts.Contains(Context.None));
DisplayActionsHelp(actions);
}

Expand All @@ -211,15 +213,80 @@ private void DisplayActionsHelp(IEnumerable<ActionType> actions)
if (actions.Any())
{
ColoredConsole.WriteLine(TitleColor("Actions: "));

// Group actions by parent command
var parentCommands = actions
.Where(a => a.ParentCommandName.All(p => string.IsNullOrEmpty(p))) // Actions with no parent
.ToList();

var subCommands = actions
.Where(a => a.ParentCommandName.Any(p => !string.IsNullOrEmpty(p))) // Actions with a parent
.ToList();

var longestName = actions.Select(a => a.Names).SelectMany(n => n).Max(n => n.Length);
longestName += 2; // for coloring chars
foreach (var action in actions)

// Display parent commands first
foreach (var parentAction in parentCommands)
{
ColoredConsole.WriteLine(GetActionHelp(action, longestName));
DisplaySwitches(action);
// Display parent command
ColoredConsole.WriteLine(GetActionHelp(parentAction, longestName));
DisplaySwitches(parentAction);

// Find and display child commands for this parent
var parentName = parentAction.Names.First();
var childCommands = subCommands
.Where(s => s.ParentCommandName.Any(p => p.Equals(parentName, StringComparison.OrdinalIgnoreCase)))
.ToList();

if (childCommands.Any())
{
ColoredConsole.WriteLine(); // Add spacing before subcommands

foreach (var childCommand in childCommands)
{
DisplaySubCommandHelp(childCommand);
}
}

ColoredConsole.WriteLine();
}
}
}

ColoredConsole.WriteLine();
private void DisplaySubCommandHelp(ActionType subCommand)
{
// Ensure subCommand is valid
if (subCommand == null)
{
return;
}

// Extract the runtime name from the full command name
// E.g., "pack dotnet" -> "Dotnet"
var fullCommandName = subCommand.Names?.FirstOrDefault();

string runtimeName = null;
if (!string.IsNullOrWhiteSpace(fullCommandName))
{
var parts = fullCommandName.Split(' ', StringSplitOptions.RemoveEmptyEntries);
runtimeName = parts.Length > 1 && !string.IsNullOrEmpty(parts[1])
? char.ToUpper(parts[1][0]) + parts[1].Substring(1).ToLower()
: fullCommandName;
}

// Fall back to a safe default if we couldn't determine a runtime name
runtimeName ??= subCommand.Type?.Name ?? "subcommand";

var description = subCommand.Type?.GetCustomAttributes<ActionAttribute>()?.FirstOrDefault()?.HelpText;

// Display indented subcommand header
ColoredConsole.WriteLine($" {runtimeName.DarkCyan()} {description}");

// Display subcommand switches with extra indentation
if (subCommand.Type != null)
{
DisplaySwitches(subCommand);
}
}

Expand Down Expand Up @@ -261,7 +328,7 @@ private void DisplayPositionalArguments(IEnumerable<CliArgument> arguments)
longestName += 4; // 4 for coloring and <> characters
foreach (var argument in arguments)
{
var helpLine = string.Format($" {{0, {-longestName}}} {{1}}", $"<{argument.Name}>".DarkGray(), argument.Description);
var helpLine = string.Format($"{" "}{{0, {-longestName}}} {{1}}", $"<{argument.Name}>".DarkGray(), argument.Description);
if (helpLine.Length < SafeConsole.BufferWidth)
{
ColoredConsole.WriteLine(helpLine);
Expand All @@ -277,7 +344,7 @@ private void DisplayPositionalArguments(IEnumerable<CliArgument> arguments)
}
}

private static void DisplayOptions(IEnumerable<ICommandLineOption> options)
private static void DisplayOptions(IEnumerable<ICommandLineOption> options, bool addExtraIndent = false)
{
var longestName = options.Max(o =>
{
Expand Down Expand Up @@ -311,7 +378,7 @@ private static void DisplayOptions(IEnumerable<ICommandLineOption> options)
stringBuilder.Append($" [-{option.ShortName}]");
}

var helpSwitch = string.Format($" {{0, {-longestName}}} ", stringBuilder.ToString().DarkGray());
var helpSwitch = string.Format($"{(addExtraIndent ? " " : " ")}{{0, {-longestName}}} ", stringBuilder.ToString().DarkGray());
var helpSwitchLength = helpSwitch.Length - 2; // helpSwitch contains 2 formatting characters.
var helpText = option.Description;
if (string.IsNullOrWhiteSpace(helpText))
Expand Down
123 changes: 0 additions & 123 deletions src/Cli/func/Actions/LocalActions/PackAction.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Azure.Functions.Cli.Interfaces;
using Fclp;

namespace Azure.Functions.Cli.Actions.LocalActions.PackAction
{
[Action(Name = "pack custom", ParentCommandName = "pack", ShowInHelp = false, HelpText = "Arguments specific to custom worker runtime apps when running func pack")]
internal class CustomPackSubcommandAction : PackSubcommandAction
{
public override ICommandLineParserResult ParseArgs(string[] args)
{
return base.ParseArgs(args);
}

public async Task RunAsync(PackOptions packOptions)
{
await ExecuteAsync(packOptions);
}

protected override Task<string> GetPackingRootAsync(string functionAppRoot, PackOptions options)
{
// Custom worker packs from the function app root without extra steps
return Task.FromResult(functionAppRoot);
}

public override Task RunAsync()
{
// Keep this since this subcommand is not meant to be run directly.
return Task.CompletedTask;
}
}
}
Loading
Loading