From ef75c417c189bed59623cad6e4f28911185211c0 Mon Sep 17 00:00:00 2001 From: Matthias Koch Date: Mon, 1 Apr 2024 21:23:56 +0200 Subject: [PATCH] wip: build schema using NJsonSchema --- .nuke/build.schema.json | 348 ++++++++---------- source/Nuke.Build.Shared/CompletionUtility.cs | 50 ++- ...tGetCompletionItemsFromSchema.verified.txt | 14 + ...CompletionItemsParameterBuild.verified.txt | 38 ++ ...GetCompletionItemsTargetBuild.verified.txt | 35 ++ .../Nuke.Build.Tests/CompletionUtilityTest.cs | 48 ++- .../Nuke.Build.Tests/Nuke.Build.Tests.csproj | 4 - ...mParameterAttributeAttribute.verified.json | 90 +++++ ...ilityTest.TestGetBuildSchema.verified.json | 88 ++--- ...ilityTest.TestParameterBuild.verified.json | 14 +- source/Nuke.Build.Tests/SchemaUtilityTest.cs | 280 ++++---------- source/Nuke.Build.Tests/parameters.json | 6 +- .../HandleShellCompletionAttribute.cs | 18 +- source/Nuke.Build/Nuke.Build.csproj | 1 + source/Nuke.Build/Utilities/SchemaUtility.cs | 205 +++++++---- 15 files changed, 693 insertions(+), 546 deletions(-) create mode 100644 source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsFromSchema.verified.txt create mode 100644 source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsParameterBuild.verified.txt create mode 100644 source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsTargetBuild.verified.txt create mode 100644 source/Nuke.Build.Tests/SchemaUtilityTest.TestCustomParameterAttributeAttribute.verified.json diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index b42ce3ca0..956ab15ae 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -1,76 +1,174 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/build", - "title": "Build Schema", + "properties": { + "AutoStash": { + "type": "boolean" + }, + "CodecovToken": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "Configuration": { + "type": "string", + "enum": [ + "Debug", + "Release" + ] + }, + "DiscordWebhook": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "FeedzNuGetApiKey": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "GitHubReleaseGitHubToken": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "IgnoreFailedSources": { + "type": "boolean", + "description": "Ignore unreachable sources during Restore" + }, + "Major": { + "type": "boolean" + }, + "MastodonAccessToken": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "PublicNuGetApiKey": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "SignPathApiToken": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "SignPathOrganizationId": { + "type": "string" + }, + "SignPathPolicySlug": { + "type": "string" + }, + "SignPathProjectSlug": { + "type": "string" + }, + "SlackWebhook": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "TestDegreeOfParallelism": { + "type": "integer", + "format": "int32" + }, + "TwitterAccessToken": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "TwitterAccessTokenSecret": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "TwitterConsumerKey": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "TwitterConsumerSecret": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "UseHttps": { + "type": "boolean" + } + }, "definitions": { - "build": { - "type": "object", + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "Announce", + "AnnounceDiscord", + "AnnounceMastodon", + "AnnounceSlack", + "AnnounceTwitter", + "Changelog", + "CheckoutExternalRepositories", + "Clean", + "Compile", + "CreateGitHubRelease", + "DeletePackages", + "DownloadLicenses", + "GenerateGlobalSolution", + "GeneratePublicApi", + "GenerateTools", + "Hotfix", + "Install", + "InstallFonts", + "Milestone", + "Pack", + "Publish", + "References", + "Release", + "ReleaseImage", + "ReportCoverage", + "ReportDuplicates", + "ReportIssues", + "Restore", + "RunTargetInDockerImageTest", + "SignPackages", + "Test", + "UpdateContributors", + "UpdateStargazers" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "NukeBuild": { "properties": { - "AutoStash": { - "type": "boolean" - }, - "CodecovToken": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "Configuration": { - "type": "string", - "enum": [ - "Debug", - "Release" - ] - }, "Continue": { "type": "boolean", "description": "Indicates to continue a previously failed build attempt" }, - "DiscordWebhook": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "FeedzNuGetApiKey": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "GitHubReleaseGitHubToken": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, "Help": { "type": "boolean", "description": "Shows the help text for this build assembly" }, "Host": { - "type": "string", "description": "Host for execution. Default is 'automatic'", - "enum": [ - "AppVeyor", - "AzurePipelines", - "Bamboo", - "Bitbucket", - "Bitrise", - "GitHubActions", - "GitLab", - "Jenkins", - "Rider", - "SpaceAutomation", - "TeamCity", - "Terminal", - "TravisCI", - "VisualStudio", - "VSCode" - ] - }, - "IgnoreFailedSources": { - "type": "boolean", - "description": "Ignore unreachable sources during Restore" - }, - "Major": { - "type": "boolean" - }, - "MastodonAccessToken": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" + "$ref": "#/definitions/Host" }, "NoLogo": { "type": "boolean", @@ -91,152 +189,30 @@ "type": "string" } }, - "PublicNuGetApiKey": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, "Root": { "type": "string", "description": "Root directory during build execution" }, - "SignPathApiToken": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "SignPathOrganizationId": { - "type": "string" - }, - "SignPathPolicySlug": { - "type": "string" - }, - "SignPathProjectSlug": { - "type": "string" - }, "Skip": { "type": "array", "description": "List of targets to be skipped. Empty list skips all dependencies", "items": { - "type": "string", - "enum": [ - "Announce", - "AnnounceDiscord", - "AnnounceMastodon", - "AnnounceSlack", - "AnnounceTwitter", - "Changelog", - "CheckoutExternalRepositories", - "Clean", - "Compile", - "CreateGitHubRelease", - "DeletePackages", - "DownloadLicenses", - "GenerateGlobalSolution", - "GeneratePublicApi", - "GenerateTools", - "Hotfix", - "Install", - "InstallFonts", - "Milestone", - "Pack", - "Publish", - "References", - "Release", - "ReleaseImage", - "ReportCoverage", - "ReportDuplicates", - "ReportIssues", - "Restore", - "RunTargetInDockerImageTest", - "SignPackages", - "Test", - "UpdateContributors", - "UpdateStargazers" - ] + "$ref": "#/definitions/ExecutableTarget" } }, - "SlackWebhook": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "Solution": { - "type": "string", - "description": "Path to a solution file that is automatically loaded" - }, "Target": { "type": "array", "description": "List of targets to be invoked. Default is '{default_target}'", "items": { - "type": "string", - "enum": [ - "Announce", - "AnnounceDiscord", - "AnnounceMastodon", - "AnnounceSlack", - "AnnounceTwitter", - "Changelog", - "CheckoutExternalRepositories", - "Clean", - "Compile", - "CreateGitHubRelease", - "DeletePackages", - "DownloadLicenses", - "GenerateGlobalSolution", - "GeneratePublicApi", - "GenerateTools", - "Hotfix", - "Install", - "InstallFonts", - "Milestone", - "Pack", - "Publish", - "References", - "Release", - "ReleaseImage", - "ReportCoverage", - "ReportDuplicates", - "ReportIssues", - "Restore", - "RunTargetInDockerImageTest", - "SignPackages", - "Test", - "UpdateContributors", - "UpdateStargazers" - ] + "$ref": "#/definitions/ExecutableTarget" } }, - "TestDegreeOfParallelism": { - "type": "integer" - }, - "TwitterAccessToken": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "TwitterAccessTokenSecret": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "TwitterConsumerKey": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "TwitterConsumerSecret": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "UseHttps": { - "type": "boolean" - }, "Verbosity": { - "type": "string", "description": "Logging verbosity during build execution. Default is 'Normal'", - "enum": [ - "Minimal", - "Normal", - "Quiet", - "Verbose" - ] + "$ref": "#/definitions/Verbosity" } } } - } + }, + "$ref": "#/definitions/NukeBuild" } diff --git a/source/Nuke.Build.Shared/CompletionUtility.cs b/source/Nuke.Build.Shared/CompletionUtility.cs index 327eb816b..edaaaca26 100644 --- a/source/Nuke.Build.Shared/CompletionUtility.cs +++ b/source/Nuke.Build.Shared/CompletionUtility.cs @@ -22,17 +22,47 @@ public static IReadOnlyDictionary GetItemsFromSchema(AbsoluteP public static IReadOnlyDictionary GetItemsFromSchema(JsonDocument schema, IEnumerable profileNames) { - string[] GetEnumValues(JsonElement property) - => property.TryGetProperty("enum", out var enumProperty) - ? enumProperty.EnumerateArray().Select(x => x.GetString()).ToArray() - : property.TryGetProperty("items", out var itemsProperty) - ? GetEnumValues(itemsProperty) - : null; - - var properties = schema.RootElement.GetProperty("definitions").GetProperty("build").GetProperty("properties") - .EnumerateObject().OfType(); - return properties.ToDictionary(x => x.Name, x => GetEnumValues(x.Value)) + var definitions = schema.RootElement.GetProperty("definitions").EnumerateObject().ToDictionary(x => x.Name, x => x); + + var parameterProperties = schema.RootElement.GetProperty("definitions").TryGetProperty("NukeBuild", out var nukebuildProperty) + ? nukebuildProperty.GetProperty("properties").EnumerateObject() + .Concat(schema.RootElement.TryGetProperty("properties", out var properties) ? properties.EnumerateObject() : []) + : definitions["build"].Value.GetProperty("properties").EnumerateObject(); + + return parameterProperties + .Select(x => (x.Name, Values: GetValues(x))) + .Where(x => x.Values != null) + .ToDictionary(x => x.Name, x => x.Values) .SetKeyValue(Constants.LoadedLocalProfilesParameterName, profileNames.ToArray()).AsReadOnly(); + + string[] GetValues(JsonProperty property) + { + if (property.Value.TryGetProperty("type", out var typeProperty)) + { + var types = typeProperty.ValueKind != JsonValueKind.Array + ? [typeProperty.GetString()] + : typeProperty.EnumerateArray().Select(x => x.GetString()).ToArray(); + if (types.ContainsAnyOrdinalIgnoreCase(["string", "boolean", "integer"])) + { + return property.Value.TryGetProperty("enum", out var enumProperty) + ? enumProperty.EnumerateArray().Select(x => x.GetString()).ToArray() + : []; + } + + if (types.Single() == "array") + return GetValues(property.Value.EnumerateObject().Single(x => x.Name == "items")); + + if (types.Single() == "object") + return null; + } + else if (property.Value.TryGetProperty("$ref", out var refProperty)) + { + var definition = definitions.GetValueOrDefault(refProperty.GetString().NotNull().Split('/').LastOrDefault()); + return GetValues(definition); + } + + throw new NotSupportedException(); + } } // ReSharper disable once CognitiveComplexity diff --git a/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsFromSchema.verified.txt b/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsFromSchema.verified.txt new file mode 100644 index 000000000..9387e8cae --- /dev/null +++ b/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsFromSchema.verified.txt @@ -0,0 +1,14 @@ +{ + Configuration: [ + Debug, + Release + ], + NoLogo: [], + Profile: [ + dev + ], + Target: [ + Restore, + Compile + ] +} \ No newline at end of file diff --git a/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsParameterBuild.verified.txt b/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsParameterBuild.verified.txt new file mode 100644 index 000000000..cfdb64000 --- /dev/null +++ b/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsParameterBuild.verified.txt @@ -0,0 +1,38 @@ +{ + BooleanParam: [], + ComponentInheritedParam: [], + Continue: [], + CustomEnumerationArrayParam: [ + Debug, + Release + ], + CustomEnumerationParam: [ + Debug, + Release + ], + Help: [], + Host: [ + Rider, + Terminal, + VisualStudio, + VSCode + ], + IntegerArrayParam: [], + NoLogo: [], + NullableBooleanParam: [], + Partition: [], + Plan: [], + Profile: [], + RegularParam: [], + Root: [], + SecretParam: [], + Skip: [], + StringArrayParam: [], + Target: [], + Verbosity: [ + Verbose, + Normal, + Minimal, + Quiet + ] +} \ No newline at end of file diff --git a/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsTargetBuild.verified.txt b/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsTargetBuild.verified.txt new file mode 100644 index 000000000..84f42e6f5 --- /dev/null +++ b/source/Nuke.Build.Tests/CompletionUtilityTest.TestGetCompletionItemsTargetBuild.verified.txt @@ -0,0 +1,35 @@ +{ + Continue: [], + Help: [], + Host: [ + Rider, + Terminal, + VisualStudio, + VSCode + ], + NoLogo: [], + Partition: [], + Plan: [], + Profile: [ + dev + ], + Root: [], + Skip: [ + ExplicitTarget, + ImplementedTarget, + InheritedTarget, + RegularTarget + ], + Target: [ + ExplicitTarget, + ImplementedTarget, + InheritedTarget, + RegularTarget + ], + Verbosity: [ + Verbose, + Normal, + Minimal, + Quiet + ] +} \ No newline at end of file diff --git a/source/Nuke.Build.Tests/CompletionUtilityTest.cs b/source/Nuke.Build.Tests/CompletionUtilityTest.cs index 440aaaa42..8888ec3bd 100644 --- a/source/Nuke.Build.Tests/CompletionUtilityTest.cs +++ b/source/Nuke.Build.Tests/CompletionUtilityTest.cs @@ -6,16 +6,47 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; using FluentAssertions; +using Nuke.Common.IO; using Nuke.Common.Utilities; +using VerifyTests; +using VerifyXunit; using Xunit; namespace Nuke.Common.Tests; +[UsesVerify] public class CompletionUtilityTest { + private VerifySettings _verifySettings; + + public CompletionUtilityTest() + { + _verifySettings = new VerifySettings(); + _verifySettings.DontIgnoreEmptyCollections(); + } + + [Fact] + public async Task TestGetCompletionItemsTargetBuild() + { + var file = (AbsolutePath)"/Users/matt/code/nuke/source/Nuke.Build.Tests/SchemaUtilityTest.TestTargetBuild.verified.json"; + var schema = JsonDocument.Parse(file.ReadAllText()); + var items = CompletionUtility.GetItemsFromSchema(schema, new[] { "dev" }); + await Verifier.Verify(items, _verifySettings); + } + [Fact] - public void TestGetCompletionItemsFromSchema() + public async Task TestGetCompletionItemsParameterBuild() + { + var file = (AbsolutePath)"/Users/matt/code/nuke/source/Nuke.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json"; + var schema = JsonDocument.Parse(file.ReadAllText()); + var items = CompletionUtility.GetItemsFromSchema(schema, []); + await Verifier.Verify(items, _verifySettings); + } + + [Fact] + public async Task TestGetCompletionItemsFromSchema() { var schema = JsonDocument.Parse(""" { @@ -62,14 +93,7 @@ public void TestGetCompletionItemsFromSchema() """); var profileNames = new[] { "dev" }; var items = CompletionUtility.GetItemsFromSchema(schema, profileNames); - items.Should().BeEquivalentTo( - new Dictionary - { - ["NoLogo"] = null, - ["Configuration"] = new[] { "Debug", "Release" }, - ["Target"] = new[] { "Restore", "Compile" }, - [Constants.LoadedLocalProfilesParameterName] = profileNames - }); + await Verifier.Verify(items, _verifySettings); } [Theory] @@ -94,9 +118,9 @@ public void TestGetRelevantCompletionItems(string words, string[] expectedItems) var completionItems = new Dictionary { - { Constants.InvokedTargetsParameterName, new[] { "Compile", "GitHubPublish" } }, - { "ApiKey", null }, - { "NuGetSource", null } + { Constants.InvokedTargetsParameterName, ["Compile", "GitHubPublish"] }, + { "ApiKey", [] }, + { "NuGetSource", [] } }; CompletionUtility.GetRelevantItems(words, completionItems) .Should() diff --git a/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj b/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj index a049ac954..4ba2b3ec9 100644 --- a/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj +++ b/source/Nuke.Build.Tests/Nuke.Build.Tests.csproj @@ -8,8 +8,4 @@ - - - - diff --git a/source/Nuke.Build.Tests/SchemaUtilityTest.TestCustomParameterAttributeAttribute.verified.json b/source/Nuke.Build.Tests/SchemaUtilityTest.TestCustomParameterAttributeAttribute.verified.json new file mode 100644 index 000000000..61883c8cf --- /dev/null +++ b/source/Nuke.Build.Tests/SchemaUtilityTest.TestCustomParameterAttributeAttribute.verified.json @@ -0,0 +1,90 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "properties": { + "ComplexTypeParamWithAttribute": { + "type": "string" + } + }, + "definitions": { + "Host": { + "type": "string", + "enum": [ + "Rider", + "Terminal", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string" + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "NukeBuild": { + "properties": { + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "description": "Host for execution. Default is 'automatic'", + "$ref": "#/definitions/Host" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Verbosity": { + "description": "Logging verbosity during build execution. Default is 'Normal'", + "$ref": "#/definitions/Verbosity" + } + } + } + }, + "$ref": "#/definitions/NukeBuild" +} \ No newline at end of file diff --git a/source/Nuke.Build.Tests/SchemaUtilityTest.TestGetBuildSchema.verified.json b/source/Nuke.Build.Tests/SchemaUtilityTest.TestGetBuildSchema.verified.json index 50469f6cf..31e7c1171 100644 --- a/source/Nuke.Build.Tests/SchemaUtilityTest.TestGetBuildSchema.verified.json +++ b/source/Nuke.Build.Tests/SchemaUtilityTest.TestGetBuildSchema.verified.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/build", "title": "Build Schema", @@ -6,10 +6,16 @@ "build": { "type": "object", "properties": { - "BoolParam": { + "BooleanParam": { "type": "boolean" }, - "ComplexParam": { + "ComplexTypeArrayParam": { + "type": "array", + "items": { + "type": "string" + } + }, + "ComplexTypeParam": { "type": "string" }, "ComponentInheritedParam": { @@ -19,30 +25,44 @@ "type": "boolean", "description": "Indicates to continue a previously failed build attempt" }, + "CustomEnumerationArrayParam": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Debug", + "Release" + ] + } + }, + "CustomEnumerationParam": { + "type": "string", + "enum": [ + "Debug", + "Release" + ] + }, "Help": { "type": "boolean", "description": "Shows the help text for this build assembly" }, "Host": { "type": "string", - "description": "Host for execution. Default is 'automatic'", - "enum": [ - "Rider", - "Terminal", - "VisualStudio", - "VSCode" - ] + "description": "Host for execution. Default is 'automatic'" + }, + "IntegerArrayParam": { + "type": "array", + "items": { + "type": "integer" + } }, "NoLogo": { "type": "boolean", "description": "Disables displaying the NUKE logo" }, - "NullableBoolParam": { + "NullableBooleanParam": { "type": "boolean" }, - "NullableIntegerParam": { - "type": "integer" - }, "Partition": { "type": "string", "description": "Partition to use on CI" @@ -58,11 +78,14 @@ "type": "string" } }, + "RegularParam": { + "type": "string" + }, "Root": { "type": "string", "description": "Root directory during build execution" }, - "Secret": { + "SecretParam": { "type": "string", "default": "Secrets must be entered via 'nuke :secrets [profile]'" }, @@ -70,41 +93,20 @@ "type": "array", "description": "List of targets to be skipped. Empty list skips all dependencies", "items": { - "type": "string", - "enum": [ - "ExplicitTarget", - "ImplementedTarget", - "InheritedTarget", - "RegularTarget" - ] + "type": "string" } }, - "StringParam": { - "type": "string" - }, - "Target": { + "StringArrayParam": { "type": "array", - "description": "List of targets to be invoked. Default is '{default_target}'", "items": { - "type": "string", - "enum": [ - "ExplicitTarget", - "ImplementedTarget", - "InheritedTarget", - "RegularTarget" - ] + "type": "string" } }, - "Verbosities": { + "Target": { "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", "items": { - "type": "string", - "enum": [ - "Minimal", - "Normal", - "Quiet", - "Verbose" - ] + "type": "string" } }, "Verbosity": { @@ -120,4 +122,4 @@ } } } -} +} \ No newline at end of file diff --git a/source/Nuke.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json b/source/Nuke.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json index 746dd6dcc..884c5b348 100644 --- a/source/Nuke.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json +++ b/source/Nuke.Build.Tests/SchemaUtilityTest.TestParameterBuild.verified.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "http://json-schema.org/draft-04/schema#", "properties": { "BooleanParam": { @@ -7,11 +7,11 @@ "ComplexTypeArrayParam": { "type": "array", "items": { - "$ref": "#/definitions/ComplexType" + "$ref": "#/definitions/OfSchemaUtilityTestF9E5B3352D3C7B2A09EBB8075A0ECA7D376B43F377AA6CCDDF0E21014566A9E2E__ComplexType" } }, "ComplexTypeParam": { - "$ref": "#/definitions/ComplexType" + "$ref": "#/definitions/OfSchemaUtilityTestF9E5B3352D3C7B2A09EBB8075A0ECA7D376B43F377AA6CCDDF0E21014566A9E2E__ComplexType" }, "ComponentInheritedParam": { "type": "string" @@ -61,7 +61,7 @@ } }, "definitions": { - "ComplexType": { + "OfSchemaUtilityTestF9E5B3352D3C7B2A09EBB8075A0ECA7D376B43F377AA6CCDDF0E21014566A9E2E__ComplexType": { "type": "object", "properties": { "String": { @@ -89,13 +89,13 @@ "type": "null" }, { - "$ref": "#/definitions/ComplexSubType" + "$ref": "#/definitions/OfSchemaUtilityTestF9E5B3352D3C7B2A09EBB8075A0ECA7D376B43F377AA6CCDDF0E21014566A9E2E__ComplexSubType" } ] } } }, - "ComplexSubType": { + "OfSchemaUtilityTestF9E5B3352D3C7B2A09EBB8075A0ECA7D376B43F377AA6CCDDF0E21014566A9E2E__ComplexSubType": { "type": "object", "properties": { "Boolean": { @@ -187,4 +187,4 @@ } }, "$ref": "#/definitions/NukeBuild" -} +} \ No newline at end of file diff --git a/source/Nuke.Build.Tests/SchemaUtilityTest.cs b/source/Nuke.Build.Tests/SchemaUtilityTest.cs index 7b75a4c68..6c2af9979 100644 --- a/source/Nuke.Build.Tests/SchemaUtilityTest.cs +++ b/source/Nuke.Build.Tests/SchemaUtilityTest.cs @@ -3,25 +3,13 @@ // https://github.com/nuke-build/nuke/blob/master/LICENSE using System; -using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reflection; -using System.Text.Encodings.Web; -using System.Text.Json; using System.Threading.Tasks; -using Namotion.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using NJsonSchema; -using NJsonSchema.Generation; -using NuGet.Packaging; using Nuke.Common.Execution; using Nuke.Common.IO; using Nuke.Common.Tooling; using Nuke.Common.Utilities; -using Nuke.Common.ValueInjection; using VerifyXunit; using Xunit; @@ -30,228 +18,112 @@ namespace Nuke.Common.Tests; [UsesVerify] public class SchemaUtilityTest { - public class Resolver : DefaultContractResolver - { - protected override List GetSerializableMembers(Type objectType) - { - return objectType == typeof(ExecutableTarget) || objectType == typeof(Host) - ? new List() - : base.GetSerializableMembers(objectType); - } - } - - private class BuildSchemaGenerator : JsonSchemaGenerator - { - public static JsonSchema Generate(T build) - where T : INukeBuild - { - return new BuildSchemaGenerator( - build, - new JsonSchemaGeneratorSettings - { - FlattenInheritanceHierarchy = true, - SerializerSettings = - new JsonSerializerSettings - { - ContractResolver = new Resolver(), - Converters = new JsonConverter[] { new StringEnumConverter() } - } - }).Generate(); - } - - private readonly INukeBuild _build; - - private BuildSchemaGenerator(INukeBuild build, JsonSchemaGeneratorSettings settings) - : base(settings) - { - _build = build; - } - - public JsonSchema Generate() - { - var userSchema = new JsonSchema(); - var baseSchema = new JsonSchema(); - var schemaResolver = new JsonSchemaResolver(userSchema, Settings); - - var parameterMembers = ValueInjectionUtility.GetParameterMembers(_build.GetType(), includeUnlisted: true); - foreach (var parameterMember in parameterMembers) - { - var schema = parameterMember.DeclaringType == typeof(NukeBuild) ? baseSchema : userSchema; - var name = ParameterService.GetParameterMemberName(parameterMember); - var property = CreateProperty(parameterMember, schemaResolver); - schema.Properties[name] = property; - } - - // ValueInjectionUtility.GetParameterMembers(_build.GetType(), includeUnlisted: true) - // // .Where(x => x.Name.EqualsAnyOrdinalIgnoreCase( - // // nameof(NukeBuild.SkippedTargets), - // // nameof(NukeBuild.InvokedTargets), - // // nameof(NukeBuild.Verbosity) - // // )) - // .ToDictionary(ParameterService.GetParameterMemberName, x => CreateProperty(x, schemaResolver)) - // .ForEach(x => - // { - // baseSchema.Properties[x.Key] = x.Value; - // }); - - userSchema.Reference = baseSchema; - userSchema.Definitions["NukeBuild"] = baseSchema; - - JsonSchema UpdatePropertySchema(string name, IEnumerable values) - { - var schema = userSchema.Definitions[name]; - schema.Type = JsonObjectType.String; - schema.AllowAdditionalProperties = true; - schema.Enumeration.AddRange(values); - return schema; - } - - // TODO: why can't this use value sets? - var targetNames = ExecutableTargetFactory.GetTargetProperties(_build.GetType()).Select(x => x.GetDisplayShortName()).OrderBy(x => x); - var executableTargetSchema = UpdatePropertySchema("ExecutableTarget", targetNames); - baseSchema.Properties["Target"].Item = baseSchema.Properties["Skip"].Item = new JsonSchema { Reference = executableTargetSchema }; - - var hostNames = Host.AvailableTypes.Select(x => x.Name).OrderBy(x => x); - var hostSchema = UpdatePropertySchema("Host", hostNames); - baseSchema.Properties["Host"].Reference = hostSchema; - - foreach (var definition in userSchema.Definitions.Values) - { - definition.EnumerationNames.Clear(); - definition.AllowAdditionalProperties = true; - } - - return userSchema; - } - - private JsonSchemaProperty CreateProperty(MemberInfo parameterMember, JsonSchemaResolver schemaResolver) - { - var property = GenerateWithReference( - parameterMember.ToContextualAccessor().AccessorType, - schemaResolver); - - property.Description = ParameterService.GetParameterDescription(parameterMember); - property.Default = parameterMember.HasCustomAttribute() - ? "Secrets must be entered via 'nuke :secrets [profile]'" - : null; - - var values = ParameterService.GetParameterValueSet(parameterMember, _build) - ?.Select(x => (object)x.Text); - if (values != null && !parameterMember.GetMemberType().IsEnum) - { - property.Type = !parameterMember.GetMemberType().IsCollectionLike() - ? JsonObjectType.String - : JsonObjectType.Array; - var propertySchema = property.Reference ?? property; - if (property.Type == JsonObjectType.String) - propertySchema.Enumeration.AddRange(values); - else - propertySchema.Item.Enumeration.AddRange(values); - } - - if (Nullable.GetUnderlyingType(parameterMember.GetMemberType()) != null) - property.Type |= JsonObjectType.Null; - - return property; - } - } - [Fact] public Task TestEmptyBuild() { - var jsonSchema = BuildSchemaGenerator.Generate(new EmptyBuild()); - return Verifier.Verify(jsonSchema.ToJson(), "json"); + var jsonSchema = SchemaUtility.GetJsonString(new EmptyBuild()); + return Verifier.Verify(jsonSchema, "json"); } [Fact] public Task TestTargetBuild() { - var jsonSchema = BuildSchemaGenerator.Generate(new TargetBuild()); - return Verifier.Verify(jsonSchema.ToJson(), "json"); + var jsonSchema = SchemaUtility.GetJsonString(new TargetBuild()); + return Verifier.Verify(jsonSchema, "json"); } [Fact] public Task TestParameterBuild() { - var jsonSchema = BuildSchemaGenerator.Generate(new ParameterBuild()); - return Verifier.Verify(jsonSchema.ToJson(), "json"); + var jsonSchema = SchemaUtility.GetJsonString(new ParameterBuild()); + return Verifier.Verify(jsonSchema, "json"); } [Fact] - public Task TestGetBuildSchema() + public Task TestCustomParameterAttributeAttribute() { - var schema = SchemaUtility.GetBuildSchema(new ParameterBuild()); - var options = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - var json = System.Text.Json.JsonSerializer.Serialize(schema, options); - return Verifier.Verify(json, "json"); + var jsonSchema = SchemaUtility.GetJsonString(new CustomParameterAttributeBuild()); + return Verifier.Verify(jsonSchema, "json"); } +} - // ReSharper disable All - private class EmptyBuild : NukeBuild - { - } +// ReSharper disable All +#pragma warning disable CS0414 // Field is assigned but its value is never used +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +file class EmptyBuild : NukeBuild +{ +} - private class TargetBuild : NukeBuild, ITargetComponent - { - Target RegularTarget => _ => _; - public Target ImplementedTarget => _ => _; - Target ITargetComponent.ExplicitTarget => _ => _; - } +file class TargetBuild : NukeBuild, ITargetComponent +{ + Target RegularTarget => _ => _; + public Target ImplementedTarget => _ => _; + Target ITargetComponent.ExplicitTarget => _ => _; +} - private interface ITargetComponent : INukeBuild - { - Target InheritedTarget => _ => _; - Target ImplementedTarget => _ => _; - Target ExplicitTarget => _ => _; - } +file interface ITargetComponent : INukeBuild +{ + Target InheritedTarget => _ => _; + Target ImplementedTarget => _ => _; + Target ExplicitTarget => _ => _; +} - private class ParameterBuild : NukeBuild, IParameterComponent - { - [Parameter] readonly string RegularParam; - [Parameter] [Secret] readonly string SecretParam; +file class ParameterBuild : NukeBuild, IParameterComponent +{ + [Parameter] readonly string RegularParam; + [Parameter] [Secret] readonly string SecretParam; - [Parameter] readonly bool BooleanParam; - [Parameter] readonly bool? NullableBooleanParam; + [Parameter] readonly bool BooleanParam; + [Parameter] readonly bool? NullableBooleanParam; - [Parameter] readonly string[] StringArrayParam; - [Parameter] readonly int[] IntegerArrayParam; + [Parameter] readonly string[] StringArrayParam; + [Parameter] readonly int[] IntegerArrayParam; - [Parameter] readonly CustomEnumeration CustomEnumerationParam; - [Parameter] readonly CustomEnumeration[] CustomEnumerationArrayParam; + [Parameter] readonly CustomEnumeration CustomEnumerationParam; + [Parameter] readonly CustomEnumeration[] CustomEnumerationArrayParam; - [Parameter] readonly ComplexType ComplexTypeParam; - [Parameter] readonly ComplexType[] ComplexTypeArrayParam; - } + [Parameter] readonly ComplexType ComplexTypeParam; + [Parameter] readonly ComplexType[] ComplexTypeArrayParam; +} - [ParameterPrefix("Component")] - private interface IParameterComponent : INukeBuild - { - [Parameter] string InheritedParam => TryGetValue(() => InheritedParam); - } +[ParameterPrefix("Component")] +file interface IParameterComponent : INukeBuild +{ + [Parameter] string InheritedParam => TryGetValue(() => InheritedParam); +} - private class ComplexType - { - public string String; - public int Number; - public AbsolutePath[] Paths; - public ComplexSubType SubObject; - } +file class ComplexType +{ + public string String; + public int Number; + public AbsolutePath[] Paths; + public ComplexSubType SubObject; +} - private class ComplexSubType - { - public bool? Boolean; - } +file class ComplexSubType +{ + public bool? Boolean; +} - [TypeConverter(typeof(TypeConverter))] - public class CustomEnumeration : Enumeration - { - public static CustomEnumeration Debug = new() { Value = nameof(Debug) }; - public static CustomEnumeration Release = new() { Value = nameof(Release) }; +[TypeConverter(typeof(TypeConverter))] +file class CustomEnumeration : Enumeration +{ + public static CustomEnumeration Debug = new() { Value = nameof(Debug) }; + public static CustomEnumeration Release = new() { Value = nameof(Release) }; - public static implicit operator string(CustomEnumeration configuration) - { - return configuration.Value; - } + public static implicit operator string(CustomEnumeration configuration) + { + return configuration.Value; } - // ReSharper restore All } + +file class CustomParameterAttributeBuild : NukeBuild +{ + [CustomParameter] readonly ComplexType ComplexTypeParamWithAttribute; +} + +file class CustomParameterAttribute : ParameterAttribute; + +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value +#pragma warning restore CS0414 // Field is assigned but its value is never used +// ReSharper restore All diff --git a/source/Nuke.Build.Tests/parameters.json b/source/Nuke.Build.Tests/parameters.json index 91352c364..0b2ee0afb 100644 --- a/source/Nuke.Build.Tests/parameters.json +++ b/source/Nuke.Build.Tests/parameters.json @@ -1,7 +1,5 @@ { - "$schema": "./SchemaUtilityTest.TestParameterBuild.received.json", + "$schema": "./SchemaUtilityTest.TestTargetBuild.verified.json", "Verbosity": "Minimal", - "Target": ["ExplicitTarget"], - "NullableBooleanParam": false, - "CustomEnumerationArrayParam": ["Debug", "Release"] + "Target": ["ExplicitTarget", "InheritedTarget"] } diff --git a/source/Nuke.Build/Execution/Extensions/HandleShellCompletionAttribute.cs b/source/Nuke.Build/Execution/Extensions/HandleShellCompletionAttribute.cs index 152d031fe..0baf73f2f 100644 --- a/source/Nuke.Build/Execution/Extensions/HandleShellCompletionAttribute.cs +++ b/source/Nuke.Build/Execution/Extensions/HandleShellCompletionAttribute.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Nuke.Common.CI; +using Nuke.Common.IO; using Nuke.Common.Utilities; using static Nuke.Common.Constants; @@ -31,12 +32,23 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge } else if (Build.BuildProjectFile != null) { - SchemaUtility.WriteBuildSchemaFile(Build); - SchemaUtility.WriteDefaultParametersFile(Build); + var buildSchema = SchemaUtility.GetJsonString(Build); + var buildSchemaFile = GetBuildSchemaFile(Build.RootDirectory); + buildSchemaFile.WriteAllText(buildSchema); + + var parametersFile = GetDefaultParametersFile(Build.RootDirectory); + if (!parametersFile.Exists()) + { + parametersFile.WriteAllText($$""" + { + "$schema": "./{{BuildSchemaFileName}}" + } + """); + } } else if (ParameterService.GetPositionalArgument(0) == ":complete") { - var schema = SchemaUtility.GetBuildSchema(Build); + var schema = SchemaUtility.GetJsonDocument(Build); var profileNames = GetProfileNames(Build.RootDirectory); var completionItems = CompletionUtility.GetItemsFromSchema(schema, profileNames); diff --git a/source/Nuke.Build/Nuke.Build.csproj b/source/Nuke.Build/Nuke.Build.csproj index e5cc4b115..6b80ca653 100644 --- a/source/Nuke.Build/Nuke.Build.csproj +++ b/source/Nuke.Build/Nuke.Build.csproj @@ -19,6 +19,7 @@ + diff --git a/source/Nuke.Build/Utilities/SchemaUtility.cs b/source/Nuke.Build/Utilities/SchemaUtility.cs index 4d61f96d8..b5a7acbbf 100644 --- a/source/Nuke.Build/Utilities/SchemaUtility.cs +++ b/source/Nuke.Build/Utilities/SchemaUtility.cs @@ -5,9 +5,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Encodings.Web; +using System.Reflection; using System.Text.Json; -using Nuke.Common.IO; +using Namotion.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using NJsonSchema; +using NJsonSchema.Generation; +using NuGet.Packaging; using Nuke.Common.Utilities; using Nuke.Common.ValueInjection; using static Nuke.Common.Constants; @@ -16,93 +22,146 @@ namespace Nuke.Common.Execution; public class SchemaUtility { - public static void WriteBuildSchemaFile(INukeBuild build) + private class SchemaGenerator : JsonSchemaGenerator { - var buildSchemaFile = GetBuildSchemaFile(build.RootDirectory); - var buildSchema = GetBuildSchema(build); - var options = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - var json = JsonSerializer.Serialize(buildSchema, options); - buildSchemaFile.WriteAllText(json); - } + private class Resolver : DefaultContractResolver + { + protected override List GetSerializableMembers(Type objectType) + { + return objectType == typeof(ExecutableTarget) || objectType == typeof(Host) + ? new List() + : base.GetSerializableMembers(objectType); + } + } - // ReSharper disable once CognitiveComplexity - public static JsonDocument GetBuildSchema(INukeBuild build) - { - var parameters = ValueInjectionUtility - .GetParameterMembers(build.GetType(), includeUnlisted: true) - // .Where(x => x.DeclaringType != typeof(NukeBuild)) - .Select(x => - new + public static JsonSchema Generate(T build) where T : INukeBuild + { + return new SchemaGenerator( + build, + new JsonSchemaGeneratorSettings { - Name = ParameterService.GetParameterMemberName(x), - Description = ParameterService.GetParameterDescription(x), - MemberType = x.GetMemberType(), - ScalarType = x.GetMemberType().GetScalarType(), - EnumValues = ParameterService.GetParameterValueSet(x, build)?.Select(x => x.Text), - IsRequired = x.HasCustomAttribute(), - IsSecret = x.HasCustomAttribute() - }).ToList(); - - string GetJsonType(Type type) - => type.IsCollectionLike() - ? "array" - : type.GetScalarType() == typeof(int) - ? "integer" - : type.GetScalarType() == typeof(bool) - ? "boolean" - : "string"; - - var properties = new Dictionary(); - foreach (var parameter in parameters) + FlattenInheritanceHierarchy = true, + SerializerSettings = + new JsonSerializerSettings + { + ContractResolver = new Resolver(), + Converters = new JsonConverter[] { new StringEnumConverter() } + } + }).Generate(); + } + + private readonly INukeBuild _build; + + private SchemaGenerator(INukeBuild build, JsonSchemaGeneratorSettings settings) + : base(settings) + { + _build = build; + } + + private JsonSchema Generate() { - var property = new Dictionary(); - property["type"] = GetJsonType(parameter.MemberType); + var baseSchema = new JsonSchema(); + var userSchema = new JsonSchema(); + var schemaResolver = new JsonSchemaResolver(userSchema, Settings); + + var parameterMembers = ValueInjectionUtility.GetParameterMembers(_build.GetType(), includeUnlisted: true); + foreach (var parameterMember in parameterMembers) + { + var schema = parameterMember.DeclaringType == typeof(NukeBuild) ? baseSchema : userSchema; + var name = ParameterService.GetParameterMemberName(parameterMember); + var property = CreateProperty(parameterMember, schemaResolver); + schema.Properties[name] = property; + } + + // ValueInjectionUtility.GetParameterMembers(_build.GetType(), includeUnlisted: true) + // // .Where(x => x.Name.EqualsAnyOrdinalIgnoreCase( + // // nameof(NukeBuild.SkippedTargets), + // // nameof(NukeBuild.InvokedTargets), + // // nameof(NukeBuild.Verbosity) + // // )) + // .ToDictionary(ParameterService.GetParameterMemberName, x => CreateProperty(x, schemaResolver)) + // .ForEach(x => + // { + // baseSchema.Properties[x.Key] = x.Value; + // }); - if (parameter.Description != null) - property["description"] = parameter.Description; + userSchema.Reference = baseSchema; + userSchema.Definitions[nameof(NukeBuild)] = baseSchema; - if (parameter.IsSecret) - property["default"] = "Secrets must be entered via 'nuke :secrets [profile]'"; + // TODO: why can't this use value sets? + var targetNames = ExecutableTargetFactory.GetTargetProperties(_build.GetType()).Select(x => x.GetDisplayShortName()).OrderBy(x => x); + var executableTargetSchema = UpdatePropertySchema(nameof(ExecutableTarget), targetNames); + baseSchema.Properties[InvokedTargetsParameterName].Item = + baseSchema.Properties[SkippedTargetsParameterName].Item = new JsonSchema { Reference = executableTargetSchema }; - if (parameter.EnumValues != null && !parameter.MemberType.IsCollectionLike()) - property["enum"] = parameter.EnumValues; + var hostNames = Host.AvailableTypes.Select(x => x.Name).OrderBy(x => x); + var hostSchema = UpdatePropertySchema(nameof(NukeBuild.Host), hostNames); + baseSchema.Properties[nameof(NukeBuild.Host)].Reference = hostSchema; - if (parameter.MemberType.IsCollectionLike()) + RemoveXEnumValues(); + + return userSchema; + + JsonSchema UpdatePropertySchema(string name, IEnumerable values) { - var items = new Dictionary(); - items["type"] = GetJsonType(parameter.ScalarType); - if (parameter.EnumValues != null) - items["enum"] = parameter.EnumValues; - property["items"] = items; + var schema = userSchema.Definitions[name]; + schema.Type = JsonObjectType.String; + schema.AllowAdditionalProperties = true; + schema.Enumeration.AddRange(values); + return schema; } - properties[parameter.Name] = property; + void RemoveXEnumValues() + { + foreach (var definition in userSchema.Definitions.Values) + { + definition.EnumerationNames.Clear(); + definition.AllowAdditionalProperties = true; + } + } } - var build2 = new Dictionary { ["type"] = "object", ["properties"] = properties }; - var definitions = new Dictionary { ["build"] = build2 }; - var jsonDictionary = new Dictionary - { - ["$schema"] = "http://json-schema.org/draft-04/schema#", - ["$ref"] = "#/definitions/build", - ["title"] = "Build Schema", - ["definitions"] = definitions - }; - return JsonDocument.Parse(JsonSerializer.Serialize(jsonDictionary)); + private JsonSchemaProperty CreateProperty(MemberInfo parameterMember, JsonSchemaResolver schemaResolver) + { + var property = parameterMember.GetCustomAttribute().NotNull().GetType() == typeof(ParameterAttribute) + ? GenerateWithReference( + parameterMember.ToContextualAccessor().AccessorType, + schemaResolver) + : new JsonSchemaProperty { Type = JsonObjectType.String }; + + property.Description = ParameterService.GetParameterDescription(parameterMember); + property.Default = parameterMember.HasCustomAttribute() + ? "Secrets must be entered via 'nuke :secrets [profile]'" + : null; + + var values = ParameterService.GetParameterValueSet(parameterMember, _build) + ?.Select(x => (object)x.Text); + if (values != null && !parameterMember.GetMemberType().IsEnum) + { + property.Type = !parameterMember.GetMemberType().IsCollectionLike() + ? JsonObjectType.String + : JsonObjectType.Array; + var propertySchema = property.Reference ?? property; + if (property.Type == JsonObjectType.String) + propertySchema.Enumeration.AddRange(values); + else + propertySchema.Item.Enumeration.AddRange(values); + } + + if (Nullable.GetUnderlyingType(parameterMember.GetMemberType()) != null) + property.Type |= JsonObjectType.Null; + + return property; + } } - public static void WriteDefaultParametersFile(INukeBuild build) + public static string GetJsonString(INukeBuild build) { - var parametersFile = GetDefaultParametersFile(build.RootDirectory); - if (parametersFile.Exists()) - return; + return SchemaGenerator.Generate(build).ToJson(); + } - parametersFile.WriteAllLines( - new[] - { - "{", - $" \"$schema\": \"./{BuildSchemaFileName}\"", - "}" - }); + public static JsonDocument GetJsonDocument(INukeBuild build) + { + return JsonDocument.Parse(GetJsonString(build)); } }