Skip to content

Commit 2afd863

Browse files
committed
Add command stubs for init, new, start, pack with project detection
Register init, new, start, and pack commands with their CLI options. Commands display workload install guidance until workloads are available. Includes ProjectDetector for runtime detection from project files. 47 tests passing.
1 parent 2eae67d commit 2afd863

File tree

12 files changed

+890
-0
lines changed

12 files changed

+890
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.CommandLine;
5+
using Azure.Functions.Cli.Console;
6+
7+
namespace Azure.Functions.Cli.Commands;
8+
9+
/// <summary>
10+
/// Initializes a new Azure Functions project. The full implementation requires
11+
/// a language workload to be installed — this defines the command skeleton and options.
12+
/// </summary>
13+
public class InitCommand : BaseCommand
14+
{
15+
public static readonly Option<string?> WorkerRuntimeOption = new("--worker-runtime", "-w")
16+
{
17+
Description = "The worker runtime for the project"
18+
};
19+
20+
public static readonly Option<string?> NameOption = new("--name", "-n")
21+
{
22+
Description = "The name of the function app project"
23+
};
24+
25+
public static readonly Option<string?> LanguageOption = new("--language", "-l")
26+
{
27+
Description = "The programming language (e.g., C#, F#, JavaScript, TypeScript, Python)"
28+
};
29+
30+
public static readonly Option<bool> ForceOption = new("--force")
31+
{
32+
Description = "Force initialization even if the folder is not empty"
33+
};
34+
35+
private readonly IInteractionService _interaction;
36+
37+
public InitCommand(IInteractionService interaction)
38+
: base("init", "Initialize a new Azure Functions project.")
39+
{
40+
_interaction = interaction;
41+
42+
AddPathArgument();
43+
Options.Add(WorkerRuntimeOption);
44+
Options.Add(NameOption);
45+
Options.Add(LanguageOption);
46+
Options.Add(ForceOption);
47+
}
48+
49+
protected override Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
50+
{
51+
ApplyPath(parseResult, createIfNotExists: true);
52+
53+
_interaction.WriteError("No language workloads installed.");
54+
_interaction.WriteBlankLine();
55+
_interaction.WriteMarkupLine(
56+
"[grey]Install a workload to initialize a project:[/]");
57+
_interaction.WriteBlankLine();
58+
_interaction.WriteMarkupLine(" [white]func workload install dotnet[/] [grey]C#, F#[/]");
59+
_interaction.WriteMarkupLine(" [white]func workload install node[/] [grey]JavaScript, TypeScript[/]");
60+
_interaction.WriteMarkupLine(" [white]func workload install python[/] [grey]Python[/]");
61+
_interaction.WriteMarkupLine(" [white]func workload install java[/] [grey]Java[/]");
62+
_interaction.WriteMarkupLine(" [white]func workload install powershell[/] [grey]PowerShell[/]");
63+
_interaction.WriteBlankLine();
64+
_interaction.WriteMarkupLine("[grey]Run[/] [white]func workload search[/] [grey]to discover available workloads.[/]");
65+
66+
return Task.FromResult(1);
67+
}
68+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.CommandLine;
5+
using Azure.Functions.Cli.Console;
6+
7+
namespace Azure.Functions.Cli.Commands;
8+
9+
/// <summary>
10+
/// Creates a new function from a template. The full implementation requires
11+
/// a language workload to be installed — this defines the command skeleton and options.
12+
/// </summary>
13+
public class NewCommand : BaseCommand
14+
{
15+
public static readonly Option<string?> NameOption = new("--name", "-n")
16+
{
17+
Description = "The name of the function"
18+
};
19+
20+
public static readonly Option<string?> TemplateOption = new("--template", "-t")
21+
{
22+
Description = "The function template name"
23+
};
24+
25+
public static readonly Option<bool> ForceOption = new("--force")
26+
{
27+
Description = "Overwrite existing files"
28+
};
29+
30+
private readonly IInteractionService _interaction;
31+
32+
public NewCommand(IInteractionService interaction)
33+
: base("new", "Create a new function from a template.")
34+
{
35+
_interaction = interaction;
36+
37+
AddPathArgument();
38+
Options.Add(NameOption);
39+
Options.Add(TemplateOption);
40+
Options.Add(ForceOption);
41+
}
42+
43+
protected override Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
44+
{
45+
ApplyPath(parseResult, createIfNotExists: true);
46+
47+
_interaction.WriteError("No language workloads installed.");
48+
_interaction.WriteBlankLine();
49+
_interaction.WriteMarkupLine(
50+
"[grey]Install a workload to create functions from templates:[/]");
51+
_interaction.WriteBlankLine();
52+
_interaction.WriteMarkupLine(" [white]func workload install dotnet[/] [grey]C#, F#[/]");
53+
_interaction.WriteMarkupLine(" [white]func workload install node[/] [grey]JavaScript, TypeScript[/]");
54+
_interaction.WriteMarkupLine(" [white]func workload install python[/] [grey]Python[/]");
55+
_interaction.WriteMarkupLine(" [white]func workload install java[/] [grey]Java[/]");
56+
_interaction.WriteMarkupLine(" [white]func workload install powershell[/] [grey]PowerShell[/]");
57+
_interaction.WriteBlankLine();
58+
_interaction.WriteMarkupLine("[grey]Run[/] [white]func workload search[/] [grey]to discover available workloads.[/]");
59+
60+
return Task.FromResult(1);
61+
}
62+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.CommandLine;
5+
using Azure.Functions.Cli.Console;
6+
7+
namespace Azure.Functions.Cli.Commands;
8+
9+
/// <summary>
10+
/// Packages an Azure Functions project into a zip ready for deployment.
11+
/// The full implementation requires a language workload — this defines
12+
/// the command skeleton and options.
13+
/// </summary>
14+
public class PackCommand : BaseCommand
15+
{
16+
public static readonly Option<string?> OutputOption = new("--output", "-o")
17+
{
18+
Description = "The directory to place the output zip file in"
19+
};
20+
21+
public static readonly Option<bool> NoBuildOption = new("--no-build")
22+
{
23+
Description = "Skip building the project before packaging"
24+
};
25+
26+
private readonly IInteractionService _interaction;
27+
28+
public PackCommand(IInteractionService interaction)
29+
: base("pack", "Package the function app into a zip ready for deployment.")
30+
{
31+
_interaction = interaction;
32+
33+
AddPathArgument();
34+
Options.Add(OutputOption);
35+
Options.Add(NoBuildOption);
36+
}
37+
38+
protected override Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
39+
{
40+
ApplyPath(parseResult);
41+
42+
var projectPath = Directory.GetCurrentDirectory();
43+
44+
// Verify this is a Functions project
45+
if (!File.Exists(Path.Combine(projectPath, "host.json")))
46+
{
47+
_interaction.WriteError("No Azure Functions project found. Run func init first.");
48+
return Task.FromResult(1);
49+
}
50+
51+
// Detect the runtime
52+
var detectedRuntime = ProjectDetector.DetectRuntime(projectPath);
53+
if (detectedRuntime is null)
54+
{
55+
_interaction.WriteError("Could not detect the worker runtime for this project.");
56+
_interaction.WriteMarkupLine("[grey]Ensure the project contains the expected project files (e.g., .csproj, package.json).[/]");
57+
return Task.FromResult(1);
58+
}
59+
60+
_interaction.WriteError($"No pack provider for runtime '{detectedRuntime}'.");
61+
_interaction.WriteMarkupLine(
62+
$"[grey]Install the workload:[/] [white]func workload install {detectedRuntime}[/]");
63+
return Task.FromResult(1);
64+
}
65+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace Azure.Functions.Cli.Commands;
5+
6+
/// <summary>
7+
/// Shared utility for detecting the worker runtime and language of a Functions project.
8+
/// </summary>
9+
internal static class ProjectDetector
10+
{
11+
/// <summary>
12+
/// Detects the worker runtime and language from the project files in the given directory.
13+
/// </summary>
14+
public static (string? Runtime, string? Language) DetectRuntimeAndLanguage(string directory)
15+
{
16+
// Check for .csproj → dotnet C#
17+
if (Directory.EnumerateFiles(directory, "*.csproj").Any())
18+
{
19+
return ("dotnet", "C#");
20+
}
21+
22+
// Check for .fsproj → dotnet F#
23+
if (Directory.EnumerateFiles(directory, "*.fsproj").Any())
24+
{
25+
return ("dotnet", "F#");
26+
}
27+
28+
// Check for package.json → node
29+
if (File.Exists(Path.Combine(directory, "package.json")))
30+
{
31+
return ("node", null);
32+
}
33+
34+
// Check for requirements.txt or pyproject.toml → python
35+
if (File.Exists(Path.Combine(directory, "requirements.txt"))
36+
|| File.Exists(Path.Combine(directory, "pyproject.toml")))
37+
{
38+
return ("python", null);
39+
}
40+
41+
// Check for pom.xml or build.gradle → java
42+
if (File.Exists(Path.Combine(directory, "pom.xml"))
43+
|| File.Exists(Path.Combine(directory, "build.gradle")))
44+
{
45+
return ("java", null);
46+
}
47+
48+
// Check for profile.ps1 → powershell
49+
if (File.Exists(Path.Combine(directory, "profile.ps1")))
50+
{
51+
return ("powershell", null);
52+
}
53+
54+
return (null, null);
55+
}
56+
57+
/// <summary>
58+
/// Detects just the worker runtime from the project files in the given directory.
59+
/// </summary>
60+
public static string? DetectRuntime(string directory)
61+
=> DetectRuntimeAndLanguage(directory).Runtime;
62+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.CommandLine;
5+
using Azure.Functions.Cli.Console;
6+
7+
namespace Azure.Functions.Cli.Commands;
8+
9+
/// <summary>
10+
/// Launches the Azure Functions host runtime. Supports 'func start' and
11+
/// Placeholder for 'func start' (not yet implemented).
12+
/// </summary>
13+
public class StartCommand : BaseCommand
14+
{
15+
public static readonly Option<int?> PortOption = new("--port", "-p")
16+
{
17+
Description = "The port to listen on (default: 7071)"
18+
};
19+
20+
public static readonly Option<string?> CorsOption = new("--cors")
21+
{
22+
Description = "A comma-separated list of CORS origins"
23+
};
24+
25+
public static readonly Option<bool> CorsCredentialsOption = new("--cors-credentials")
26+
{
27+
Description = "Allow cross-origin authenticated requests"
28+
};
29+
30+
public static readonly Option<string[]?> FunctionsOption = new("--functions")
31+
{
32+
Description = "A space-separated list of functions to load",
33+
Arity = ArgumentArity.ZeroOrMore
34+
};
35+
36+
public static readonly Option<bool> NoBuildOption = new("--no-build")
37+
{
38+
Description = "Do not build the project before running"
39+
};
40+
41+
public static readonly Option<bool> EnableAuthOption = new("--enable-auth")
42+
{
43+
Description = "Enable full authentication handling"
44+
};
45+
46+
public static readonly Option<string?> HostVersionOption = new("--host-version")
47+
{
48+
Description = "The host runtime version to use (e.g., 4.1049.0)"
49+
};
50+
51+
private readonly IInteractionService _interaction;
52+
53+
public StartCommand(IInteractionService interaction)
54+
: base("start", "Launch the Azure Functions host runtime.")
55+
{
56+
_interaction = interaction;
57+
58+
AddPathArgument();
59+
Options.Add(PortOption);
60+
Options.Add(CorsOption);
61+
Options.Add(CorsCredentialsOption);
62+
Options.Add(FunctionsOption);
63+
Options.Add(NoBuildOption);
64+
Options.Add(EnableAuthOption);
65+
Options.Add(HostVersionOption);
66+
}
67+
68+
protected override Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
69+
{
70+
ApplyPath(parseResult);
71+
_interaction.WriteWarning("The 'start' command is not yet implemented in this version.");
72+
_interaction.WriteMarkupLine("[grey]This is a preview build of Azure Functions CLI vNext.[/]");
73+
return Task.FromResult(1);
74+
}
75+
}

src/Func.Cli/Parser.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ public static FuncRootCommand CreateCommand(IInteractionService interaction)
2525
// Create built-in commands
2626
var helpCommand = new HelpCommand(interaction, rootCommand);
2727
var versionCommand = new VersionCommand(interaction);
28+
var initCommand = new InitCommand(interaction);
29+
var newCommand = new NewCommand(interaction);
30+
var packCommand = new PackCommand(interaction);
31+
var startCommand = new StartCommand(interaction);
2832

2933
// Register built-in commands
3034
rootCommand.Subcommands.Add(versionCommand);
3135
rootCommand.Subcommands.Add(helpCommand);
36+
rootCommand.Subcommands.Add(initCommand);
37+
rootCommand.Subcommands.Add(newCommand);
38+
rootCommand.Subcommands.Add(packCommand);
39+
rootCommand.Subcommands.Add(startCommand);
3240

3341
// Replace built-in help rendering with Spectre on all commands
3442
ReplaceHelpAction(rootCommand, helpCommand);

0 commit comments

Comments
 (0)