Feature/dotnet folder structure v2 (#19)

* dotnet folder structure

* Generator code added

* Generator code added
This commit is contained in:
Dilmurod Makhamadaliev 2022-10-19 13:55:28 -04:00 коммит произвёл GitHub
Родитель 3128b52a3c
Коммит b0c9babc8f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 423 добавлений и 246 удалений

72
BenchPress.sln Normal file
Просмотреть файл

@ -0,0 +1,72 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "engine", "engine", "{29011A2E-34D3-468C-AA6F-C5AD5597970D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.TestEngine", "engine\BenchPress.TestEngine\BenchPress.TestEngine.csproj", "{19C5E14A-FCA3-4335-A25C-D6DA88BE3099}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.TestEngine.Tests", "engine\BenchPress.TestEngine.Tests\BenchPress.TestEngine.Tests.csproj", "{AD2CE189-AB17-4B0E-A01A-DE8B14280C6C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "framework", "framework", "{37AC63C5-5039-4217-8170-80C7AF5831B3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{8223C874-C45A-4ABE-A340-893ED768661C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.Module", "framework\dotnet\BenchPress.Module\BenchPress.Module.csproj", "{D9BEFD0F-AC9B-4EA8-AC27-54189778729B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.TestFramework", "framework\dotnet\BenchPress.TestFramework\BenchPress.TestFramework.csproj", "{B7C203B4-865B-4D63-B3D3-8A90990AC13A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.TestFramework.Tests", "framework\dotnet\BenchPress.TestFramework.Tests\BenchPress.TestFramework.Tests.csproj", "{B1BC5E2E-2694-4E49-908A-F9CBD387DD89}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{BEBFA54D-228E-49F8-87F0-27D50FF25743}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{4596A437-B726-43F7-AAD2-89BE5ECAECFB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.Sample", "samples\dotnet\BenchPress.Sample\BenchPress.Sample.csproj", "{89C81EDE-90E0-47CD-93DB-2E5024244D88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{19C5E14A-FCA3-4335-A25C-D6DA88BE3099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19C5E14A-FCA3-4335-A25C-D6DA88BE3099}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19C5E14A-FCA3-4335-A25C-D6DA88BE3099}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19C5E14A-FCA3-4335-A25C-D6DA88BE3099}.Release|Any CPU.Build.0 = Release|Any CPU
{AD2CE189-AB17-4B0E-A01A-DE8B14280C6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD2CE189-AB17-4B0E-A01A-DE8B14280C6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD2CE189-AB17-4B0E-A01A-DE8B14280C6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD2CE189-AB17-4B0E-A01A-DE8B14280C6C}.Release|Any CPU.Build.0 = Release|Any CPU
{D9BEFD0F-AC9B-4EA8-AC27-54189778729B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9BEFD0F-AC9B-4EA8-AC27-54189778729B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9BEFD0F-AC9B-4EA8-AC27-54189778729B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9BEFD0F-AC9B-4EA8-AC27-54189778729B}.Release|Any CPU.Build.0 = Release|Any CPU
{B7C203B4-865B-4D63-B3D3-8A90990AC13A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7C203B4-865B-4D63-B3D3-8A90990AC13A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7C203B4-865B-4D63-B3D3-8A90990AC13A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7C203B4-865B-4D63-B3D3-8A90990AC13A}.Release|Any CPU.Build.0 = Release|Any CPU
{B1BC5E2E-2694-4E49-908A-F9CBD387DD89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1BC5E2E-2694-4E49-908A-F9CBD387DD89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1BC5E2E-2694-4E49-908A-F9CBD387DD89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1BC5E2E-2694-4E49-908A-F9CBD387DD89}.Release|Any CPU.Build.0 = Release|Any CPU
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{19C5E14A-FCA3-4335-A25C-D6DA88BE3099} = {29011A2E-34D3-468C-AA6F-C5AD5597970D}
{AD2CE189-AB17-4B0E-A01A-DE8B14280C6C} = {29011A2E-34D3-468C-AA6F-C5AD5597970D}
{8223C874-C45A-4ABE-A340-893ED768661C} = {37AC63C5-5039-4217-8170-80C7AF5831B3}
{D9BEFD0F-AC9B-4EA8-AC27-54189778729B} = {8223C874-C45A-4ABE-A340-893ED768661C}
{B7C203B4-865B-4D63-B3D3-8A90990AC13A} = {8223C874-C45A-4ABE-A340-893ED768661C}
{B1BC5E2E-2694-4E49-908A-F9CBD387DD89} = {8223C874-C45A-4ABE-A340-893ED768661C}
{4596A437-B726-43F7-AAD2-89BE5ECAECFB} = {BEBFA54D-228E-49F8-87F0-27D50FF25743}
{89C81EDE-90E0-47CD-93DB-2E5024244D88} = {4596A437-B726-43F7-AAD2-89BE5ECAECFB}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -1,143 +1,143 @@
using System.Text.Json.Nodes;
namespace Generators;
public class AzureDeploymentImporter
{
public static IEnumerable<TestMetadata> Import(FileInfo inputFile, string outputFolderPath)
{
return Import(inputFile.FullName, outputFolderPath);
}
public static IEnumerable<TestMetadata> Import(string inputFileFullPath, string outputFolderPath)
{
var jsonFileContent = "";
if (inputFileFullPath.EndsWith(".bicep"))
{
var tempFileFullPath = Path.GetTempFileName();
var buildArgs = new string[]
{
"build",
inputFileFullPath,
"--outfile",
tempFileFullPath
};
Bicep.Cli.Program.Main(buildArgs).Wait();
jsonFileContent = File.ReadAllText(tempFileFullPath);
var generateParamsArgs = new string[]
{
"generate-params",
inputFileFullPath,
"--outfile",
outputFolderPath + "\\generated"
};
Bicep.Cli.Program.Main(generateParamsArgs).Wait();
File.Delete(tempFileFullPath);
}
else if (inputFileFullPath.EndsWith(".json"))
{
jsonFileContent = File.ReadAllText(inputFileFullPath);
}
else
{
throw new FileFormatException();
}
var parsed = JsonNode.Parse(jsonFileContent)?.AsObject();
if (parsed == null)
{
throw new Exception("Failed to parse json file");
}
var list = new List<TestMetadata>();
foreach (var resource in (JsonArray)parsed["resources"]!)
{
if (resource == null)
{
throw new Exception("Failed to parse json file");
}
var resourceType = resource["type"]?.ToString().Trim();
var resourceName = resource["name"]?.ToString().Trim();
if (resourceName == null || resourceType == null)
{
throw new Exception("Failed to parse json file");
}
if (resourceName.StartsWith("[") && resourceName.EndsWith("]"))
{
var parts = new[] { "parameters", "variables" };
foreach (var part in parts)
{
var temp = resourceName;
var parsedValue = temp!
.Replace("[", "")
.Replace("]", "")
.Replace(part, "")
.Replace("(", "")
.Replace(")", "")
.Replace("'", "")
.Trim();
if (!string.IsNullOrWhiteSpace(parsedValue))
{
var defaultValueNode = parsed[part]?[parsedValue];
if (defaultValueNode is JsonObject)
{
temp = defaultValueNode["defaultValue"]!.ToString();
}
else
{
temp = parsed[part]?[parsedValue]?.ToString();
}
}
if (!string.IsNullOrWhiteSpace(temp))
{
resourceName = temp;
break;
}
}
}
if (resourceName == null)
{
throw new Exception("Failed to parse json file");
}
var extraProperties = new Dictionary<string, object>();
var location = resource["location"]?.ToString()?.Trim();
// todo: do actual stuff
if (location == null)
{
location = "FAKE-LOCATION";
}
extraProperties.Add("location", location);
extraProperties.Add("resourceGroup", "FAKE-RESOURCE-GROUP");
try
{
list.Add(new TestMetadata(resourceType, resourceName, extraProperties));
}
catch (UnknownResourceTypeException)
{
// ignore
}
}
return list;
}
}
using System.Text.Json.Nodes;
namespace Generators;
public class AzureDeploymentImporter
{
public static IEnumerable<TestMetadata> Import(FileInfo inputFile, string outputFolderPath)
{
return Import(inputFile.FullName, outputFolderPath);
}
public static IEnumerable<TestMetadata> Import(string inputFileFullPath, string outputFolderPath)
{
var jsonFileContent = "";
if (inputFileFullPath.EndsWith(".bicep"))
{
var tempFileFullPath = Path.GetTempFileName();
var buildArgs = new string[]
{
"build",
inputFileFullPath,
"--outfile",
tempFileFullPath
};
Bicep.Cli.Program.Main(buildArgs).Wait();
jsonFileContent = File.ReadAllText(tempFileFullPath);
var generateParamsArgs = new string[]
{
"generate-params",
inputFileFullPath,
"--outfile",
outputFolderPath + "\\generated"
};
Bicep.Cli.Program.Main(generateParamsArgs).Wait();
File.Delete(tempFileFullPath);
}
else if (inputFileFullPath.EndsWith(".json"))
{
jsonFileContent = File.ReadAllText(inputFileFullPath);
}
else
{
throw new FileFormatException();
}
var parsed = JsonNode.Parse(jsonFileContent)?.AsObject();
if (parsed == null)
{
throw new Exception("Failed to parse json file");
}
var list = new List<TestMetadata>();
foreach (var resource in (JsonArray)parsed["resources"]!)
{
if (resource == null)
{
throw new Exception("Failed to parse json file");
}
var resourceType = resource["type"]?.ToString().Trim();
var resourceName = resource["name"]?.ToString().Trim();
if (resourceName == null || resourceType == null)
{
throw new Exception("Failed to parse json file");
}
if (resourceName.StartsWith("[") && resourceName.EndsWith("]"))
{
var parts = new[] { "parameters", "variables" };
foreach (var part in parts)
{
var temp = resourceName;
var parsedValue = temp!
.Replace("[", "")
.Replace("]", "")
.Replace(part, "")
.Replace("(", "")
.Replace(")", "")
.Replace("'", "")
.Trim();
if (!string.IsNullOrWhiteSpace(parsedValue))
{
var defaultValueNode = parsed[part]?[parsedValue];
if (defaultValueNode is JsonObject)
{
temp = defaultValueNode["defaultValue"]!.ToString();
}
else
{
temp = parsed[part]?[parsedValue]?.ToString();
}
}
if (!string.IsNullOrWhiteSpace(temp))
{
resourceName = temp;
break;
}
}
}
if (resourceName == null)
{
throw new Exception("Failed to parse json file");
}
var extraProperties = new Dictionary<string, object>();
var location = resource["location"]?.ToString()?.Trim();
// todo: do actual stuff
if (location == null)
{
location = "FAKE-LOCATION";
}
extraProperties.Add("location", location);
extraProperties.Add("resourceGroup", "FAKE-RESOURCE-GROUP");
try
{
list.Add(new TestMetadata(resourceType, resourceName, extraProperties));
}
catch (UnknownResourceTypeException)
{
// ignore
}
}
return list;
}
}

Просмотреть файл

@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Stubble.Core" Version="1.9.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<ProjectReference Include="..\bicep\src\Bicep.Cli\Bicep.Cli.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Stubble.Core" Version="1.9.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<ProjectReference Include="..\bicep\src\Bicep.Cli\Bicep.Cli.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,83 +1,83 @@
using Generators;
using Generators.LanguageProviders;
using Generators.ResourceTypes;
using System.CommandLine;
using System.Linq;
var importFileOption = new Option<FileInfo?>(name: "--import", description: "The bicep file to import and scaffold tests for");
var outputFolderOption = new Option<DirectoryInfo?>(name: "--output", description: "Path that output will be saved");
var languageProviderOption = new Option<LanguageProviderOptions>(name: "--provider", description: "Language provider that will be used to generate test files");
var rootCommand = new RootCommand("Test Generator for Bicep and ARM Templates");
rootCommand.AddOption(importFileOption);
rootCommand.AddOption(languageProviderOption);
rootCommand.SetHandler((fileInfo, outputFolder, languageProvider) =>
{
if (fileInfo is null) return;
if (languageProvider == LanguageProviderOptions.Undefined) return;
var testFilePath = outputFolder?.FullName ?? Path.GetFullPath("output");
if (!Directory.Exists(testFilePath))
{
Directory.CreateDirectory(testFilePath);
}
ILanguageProvider provider = languageProvider switch
{
LanguageProviderOptions.Powershell => new PowershellLanguageProvider(),
_ => throw new NotImplementedException(),
};
var generator = new TestGenerator(provider);
var metadataList = AzureDeploymentImporter.Import(fileInfo, testFilePath);
var testList = new List<TestDefinition>();
var testGroups = new List<IEnumerable<TestDefinition>>();
foreach (var metadata in metadataList)
{
testList.Add(new TestDefinition(metadata, TestType.ResourceExists));
testList.Add(new TestDefinition(metadata, TestType.Location));
}
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes())
.Where(type => typeof(ResourceType).IsAssignableFrom(type) && !type.IsAbstract)
.ToList()
.ForEach(type =>
{
testGroups.Add(testList.Where(t => t.Metadata.ResourceType.GetType() == type));
});
testGroups.Add(testList.Where(t => t.Metadata.ResourceType.GetType() == typeof(ResourceGroup)));
foreach (var group in testGroups)
{
if (!group.Any()) continue;
var testsOutput = generator.Generate(group, provider.GetTemplateFileName());
var testFileName = group.First().Metadata.ResourceType.Prefix + ".Tests.ps1";
var testFileFullName = Path.Join(testFilePath, testFileName);
File.WriteAllText(testFileFullName, testsOutput);
}
},
importFileOption, outputFolderOption, languageProviderOption
);
await rootCommand.InvokeAsync(args);
public enum LanguageProviderOptions
{
Undefined,
Powershell,
NodeJs,
}
using Generators;
using Generators.LanguageProviders;
using Generators.ResourceTypes;
using System.CommandLine;
using System.Linq;
var importFileOption = new Option<FileInfo?>(name: "--import", description: "The bicep file to import and scaffold tests for");
var outputFolderOption = new Option<DirectoryInfo?>(name: "--output", description: "Path that output will be saved");
var languageProviderOption = new Option<LanguageProviderOptions>(name: "--provider", description: "Language provider that will be used to generate test files");
var rootCommand = new RootCommand("Test Generator for Bicep and ARM Templates");
rootCommand.AddOption(importFileOption);
rootCommand.AddOption(languageProviderOption);
rootCommand.SetHandler((fileInfo, outputFolder, languageProvider) =>
{
if (fileInfo is null) return;
if (languageProvider == LanguageProviderOptions.Undefined) return;
var testFilePath = outputFolder?.FullName ?? Path.GetFullPath("output");
if (!Directory.Exists(testFilePath))
{
Directory.CreateDirectory(testFilePath);
}
ILanguageProvider provider = languageProvider switch
{
LanguageProviderOptions.Powershell => new PowershellLanguageProvider(),
_ => throw new NotImplementedException(),
};
var generator = new TestGenerator(provider);
var metadataList = AzureDeploymentImporter.Import(fileInfo, testFilePath);
var testList = new List<TestDefinition>();
var testGroups = new List<IEnumerable<TestDefinition>>();
foreach (var metadata in metadataList)
{
testList.Add(new TestDefinition(metadata, TestType.ResourceExists));
testList.Add(new TestDefinition(metadata, TestType.Location));
}
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes())
.Where(type => typeof(ResourceType).IsAssignableFrom(type) && !type.IsAbstract)
.ToList()
.ForEach(type =>
{
testGroups.Add(testList.Where(t => t.Metadata.ResourceType.GetType() == type));
});
testGroups.Add(testList.Where(t => t.Metadata.ResourceType.GetType() == typeof(ResourceGroup)));
foreach (var group in testGroups)
{
if (!group.Any()) continue;
var testsOutput = generator.Generate(group, provider.GetTemplateFileName());
var testFileName = group.First().Metadata.ResourceType.Prefix + ".Tests.ps1";
var testFileFullName = Path.Join(testFilePath, testFileName);
File.WriteAllText(testFileFullName, testsOutput);
}
},
importFileOption, outputFolderOption, languageProviderOption
);
await rootCommand.InvokeAsync(args);
public enum LanguageProviderOptions
{
Undefined,
Powershell,
NodeJs,
}

@ -1 +0,0 @@
Subproject commit adfd76ff0593dc66cf409a72155d6eda142e3167

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,10 @@
namespace BenchPress.TestEngine.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

Просмотреть файл

@ -0,0 +1 @@
global using Xunit;

Просмотреть файл

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

Просмотреть файл

@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,10 @@
namespace BenchPress.TestFramework.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

Просмотреть файл

@ -0,0 +1 @@
global using Xunit;

Просмотреть файл

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

Просмотреть файл

@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Просмотреть файл

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

Просмотреть файл

@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");