Files
C4IT-F4SD-M42WebApi/F4SDM42WebApi/M42ExtensionScaffolder/Solutions/Matrix42.Extensions.Scaffolder.Core/ExtensionScaffolder.cs
2026-06-26 11:31:03 +02:00

232 lines
7.5 KiB
C#

using Matrix42.Extensions.Scaffolder.Core.ProjectGenerators;
using System;
using System.Collections.Generic;
using System.IO;
namespace Matrix42.Extensions.Scaffolder.Core;
public sealed class ExtensionScaffolder : IExtensionScaffolder
{
private readonly DefaultProjectGenerator _projectGenerator = new();
public IReadOnlyList<string> Generate(ExtensionScaffoldingOptions options)
{
if (options is null)
throw new ArgumentNullException(nameof(options));
if (string.IsNullOrWhiteSpace(options.ExtensionPath)) throw new ArgumentException("ExtensionPath must be set.", nameof(options));
if (string.IsNullOrWhiteSpace(options.ExtensionNamespace)) throw new ArgumentException("ExtensionNamespace must be set.", nameof(options));
if (options.Projects == null) throw new ArgumentException("Projects must be set.", nameof(options));
var result = new List<string>();
PrepareRootStructure(options);
PrepareBasePackage(options);
var solutionDir = PrepareSolutionFolder(options);
PrepareRootArtifacts(options);
PrepareSolutionFiles(options, solutionDir);
foreach (var def in options.Projects)
{
var csprojPath = _projectGenerator.GenerateProject(options, def);
result.Add(csprojPath);
}
GenerateSolutionFile(options, solutionDir, result);
return result;
}
private void PrepareRootStructure(ExtensionScaffoldingOptions options)
{
Directory.CreateDirectory(options.ExtensionPath);
}
private void PrepareBasePackage(ExtensionScaffoldingOptions options)
{
var basePackageDir = Path.Combine(options.ExtensionPath, "BasePackage");
Directory.CreateDirectory(basePackageDir);
var packageJsonPath = Path.Combine(basePackageDir, "package.json");
if (!File.Exists(packageJsonPath))
{
var json = GetDefaultPackageJson(options);
File.WriteAllText(packageJsonPath, json);
}
}
/// <summary>
/// Creates /Solutions/{ns} and returns that path (solution-level folder).
/// </summary>
private string PrepareSolutionFolder(ExtensionScaffoldingOptions options)
{
var solutionDir = Path.Combine(options.ExtensionPath, "Solutions", options.ExtensionNamespace);
Directory.CreateDirectory(solutionDir);
return solutionDir;
}
private void PrepareRootArtifacts(ExtensionScaffoldingOptions options)
{
/*var azurePath = Path.Combine(options.ExtensionPath, "azure-pipelines-ci.yml");
if (!File.Exists(azurePath))
{
File.WriteAllText(azurePath, Templates.AzurePipelinesYml);
}*/
var azurePath = Path.Combine(options.ExtensionPath, "azure-pipelines-ci.yaml");
if (!File.Exists(azurePath))
{
var repoName = new DirectoryInfo(options.ExtensionPath).Name;
var content = Templates.GetAzurePiplineCi();
var replacedContent = content.Replace("{repoName}", repoName).Replace("{ns}", options.ExtensionNamespace);
File.WriteAllText(azurePath, replacedContent);
}
var gitignorePath = Path.Combine(options.ExtensionPath, ".gitignore");
if (!File.Exists(gitignorePath))
{
File.WriteAllText(gitignorePath, Templates.Gitignore);
}
/*var veracodePath = Path.Combine(options.ExtensionPath, "PrepareVeracodeScan.cmd");
if (!File.Exists(veracodePath))
{
var content = Templates.PrepareVeracodeScanCmd.Replace("{ns}", options.ExtensionNamespace);
File.WriteAllText(veracodePath, content);
}*/
var targetsPath = Path.Combine(options.ExtensionPath, "Extension.Assemblies.targets");
if (!File.Exists(targetsPath))
{
var content = Templates.GetMatrix42AssembliesTargets();
File.WriteAllText(targetsPath, content);
}
}
/// <summary>
/// Solution-level files that should live next to the .sln (e.g. BuildEvent.cmd).
/// </summary>
private void PrepareSolutionFiles(ExtensionScaffoldingOptions options, string solutionDir)
{
var buildEventPath = Path.Combine(solutionDir, "BuildEvent.cmd");
if (!File.Exists(buildEventPath))
{
var content = Templates.BuildEventCmdTemplate.Replace("{ns}", options.ExtensionNamespace);
File.WriteAllText(buildEventPath, content);
}
// If later you want to actually create the .sln here (for console scenario),
// this is the right place as well.
}
private static string GetDefaultPackageJson(ExtensionScaffoldingOptions options) =>
$@"{{
""Id"": ""{options.ExtensionId}"",
""Vendor"": ""Not Specified"",
""Name"": ""{options.ExtensionNamespace}"",
""Version"": ""1.0"",
""Prerequisites"": {{
""MinimalRequiredProductVersion"": ""26.1.0"",
""DependentPackages"": []
}},
""SetupDirectives"": {{
""MaintenanceMode"": false,
""RecycleWebApplication"": false,
""RestartM42WindowsServices"": false
}},
""Sandboxed"": true
}}";
/// <summary>
/// Creates {ns}.sln under solutionDir and adds all csproj files to it.
/// </summary>
private void GenerateSolutionFile(
ExtensionScaffoldingOptions options,
string solutionDir,
IReadOnlyList<string> projectPaths)
{
if (projectPaths == null || projectPaths.Count == 0)
return;
var solutionName = options.ExtensionNamespace;
var slnPath = Path.Combine(solutionDir, solutionName + ".sln");
// Basic VS solution header
var lines = new List<string>();
lines.Add("Microsoft Visual Studio Solution File, Format Version 12.00");
lines.Add("# Visual Studio Version 17");
lines.Add("VisualStudioVersion = 17.0.31912.275");
lines.Add("MinimumVisualStudioVersion = 10.0.40219.1");
// C# project type GUID
const string csProjTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
// Collect project GUIDs
var projectEntries = new List<Tuple<string, string, string>>();
// (projGuid, projName, relativePath)
foreach (var projPath in projectPaths)
{
var projName = Path.GetFileNameWithoutExtension(projPath);
var relativePath = GetRelativePath(solutionDir, projPath);
var projGuid = "{" + Guid.NewGuid().ToString().ToUpperInvariant() + "}";
projectEntries.Add(Tuple.Create(projGuid, projName, relativePath));
lines.Add(string.Format(
"Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"",
csProjTypeGuid,
projName,
relativePath,
projGuid));
lines.Add("EndProject");
}
// Global section (configs + project configs)
lines.Add("Global");
lines.Add("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
lines.Add("\t\tDebug|Any CPU = Debug|Any CPU");
lines.Add("\t\tRelease|Any CPU = Release|Any CPU");
lines.Add("\tEndGlobalSection");
lines.Add("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
foreach (var entry in projectEntries)
{
var guid = entry.Item1;
lines.Add(string.Format("\t\t{0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", guid));
lines.Add(string.Format("\t\t{0}.Debug|Any CPU.Build.0 = Debug|Any CPU", guid));
lines.Add(string.Format("\t\t{0}.Release|Any CPU.ActiveCfg = Release|Any CPU", guid));
lines.Add(string.Format("\t\t{0}.Release|Any CPU.Build.0 = Release|Any CPU", guid));
}
lines.Add("\tEndGlobalSection");
lines.Add("\tGlobalSection(SolutionProperties) = preSolution");
lines.Add("\t\tHideSolutionNode = FALSE");
lines.Add("\tEndGlobalSection");
lines.Add("EndGlobal");
File.WriteAllLines(slnPath, lines);
}
/// <summary>
/// Simple helper to get relative path from one folder to a file.
/// </summary>
private static string GetRelativePath(string basePath, string fullPath)
{
var baseUri = new Uri(AppendDirectorySeparatorChar(basePath));
var fullUri = new Uri(fullPath);
var relative = baseUri.MakeRelativeUri(fullUri).ToString();
return relative.Replace('/', Path.DirectorySeparatorChar);
}
private static string AppendDirectorySeparatorChar(string path)
{
return !path.EndsWith(Path.DirectorySeparatorChar.ToString()) ? path + Path.DirectorySeparatorChar : path;
}
}