Skip to content

Add --allow-prerelease and --no-sdk-version options to dotnet new globaljson template#54133

Open
marcpopMSFT with Copilot wants to merge 14 commits into
mainfrom
copilot/add-allow-prerelease-option
Open

Add --allow-prerelease and --no-sdk-version options to dotnet new globaljson template#54133
marcpopMSFT with Copilot wants to merge 14 commits into
mainfrom
copilot/add-allow-prerelease-option

Conversation

Copilot AI commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

dotnet new globaljson had no way to set allowPrerelease in the generated global.json, requiring users to manually look up the field name and add it by hand. Additionally, the template always included an SDK version, preventing users from creating a minimal global.json with only allowPrerelease set.

Template changes

  • Added AllowPrerelease choice parameter ("true" / "false") to template.json with defaultValue: "" (omitted from output when unspecified — fully backward compatible) and defaultIfOptionWithoutValue: "true"
  • Added NoSdkVersion bool parameter to template.json with defaultValue: false — when set, the SDK version is omitted from the generated global.json
  • Mapped to --allow-prerelease and --no-sdk-version CLI options in dotnetcli.host.json
  • Restructured template conditionals to handle JSON trailing-comma validity across all combinations of the three optional fields (rollForward, allowPrerelease, version), using string-comparison conditions (AllowPrerelease!="")
  • Updated all 14 localization files with English placeholder strings for both new parameters

Template engine fix

Fixed a boolean conversion consistency bug in Microsoft.TemplateEngine.Core: the And, Or, and Xor operators in Scope.cs used a direct (bool)x cast that throws InvalidCastException when a parameter value is stored as a string (e.g. "false"). These operators now use Convert.ToBoolean(x ?? "False"), consistent with how the Not operator already handled this case. Empty strings are also handled gracefully (treated as false) to prevent errors when choice parameters with defaultValue: "" are evaluated in boolean context.

Usage

# Suppress prerelease SDKs with a specific version
dotnet new globaljson --allow-prerelease false

# Suppress prerelease SDKs without specifying a version
dotnet new globaljson --allow-prerelease false --no-sdk-version

Output for --allow-prerelease false:

{
  "sdk": {
    "allowPrerelease": false,
    "version": "9.0.100"
  }
}

Output for --allow-prerelease false --no-sdk-version:

{
  "sdk": {
    "allowPrerelease": false
  }
}

Note: --allow-prerelease requires an explicit true or false value. Specifying the flag without a value will not set allowPrerelease in the output.

Tests

  • Added 4 new AllCommonItemsCreate test cases covering --allow-prerelease false, --allow-prerelease true, combined with --sdk-version, --no-sdk-version alone, and --allow-prerelease false --no-sdk-version
  • Added corresponding approval snapshot files
  • Updated globaljson help text snapshot to include both new options
  • Added 2 unit tests to Cpp2EvaluatorTests verifying that && and || operators correctly handle string-typed boolean variable values

@Mrxx99

Mrxx99 commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Why does file generated with
dotnet new globaljson --allow-prerelease false
include the SDK version? My use case was to just specify allow prerelease false without SDK version because I wanted to be on the latest SDK that is not prerelease. I know I can just remove the SDK version after creation, but the intention for the issue #32500 (that I created) was just avoiding prereleas

@marcpopMSFT

Copy link
Copy Markdown
Member

Hmm, I missed in the original request that you didn't even want an sdk value. The current behavior includes a default value for that. We could add a --no-sdk flag but then that would have to have error checking with --sdk-version. I'm not sure we'd want --sdk-version without a value to do that as that's most likely a typo (as I expect this to be the less common scenario). We could allow --sdk-version false and somehow make that not provide a version but that's a bit odd as version expects a version.

@baronfel any thoughts on a direction to go to have allow-prerelease with no version?

@baronfel

baronfel commented May 4, 2026

Copy link
Copy Markdown
Member

A --no-sdk-version could be a bit more explicit here, and it would make the conditions a bit easier to write since we have a constrained condition language in the templates.

@marcpopMSFT

Copy link
Copy Markdown
Member

@copilot can we add a --no-sdk-version option that won't include the SDK version as there is a scenario where a customer might want all-prerelease of false but not specify a version.

Copilot AI commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot can we add a --no-sdk-version option that won't include the SDK version as there is a scenario where a customer might want all-prerelease of false but not specify a version.

Added --no-sdk-version in commit f08d4fa. When specified, the SDK version is omitted from the generated global.json, enabling the primary use case:

dotnet new globaljson --allow-prerelease false --no-sdk-version

Output:

{
  "sdk": {
    "allowPrerelease": false
  }
}

The template conditional logic handles all combinations of --roll-forward, --allow-prerelease, and --no-sdk-version while maintaining valid JSON (no trailing commas).

Copilot AI changed the title Add --allow-prerelease option to dotnet new globaljson template Add --allow-prerelease and --no-sdk-version options to dotnet new globaljson template May 5, 2026
@marcpopMSFT

Copy link
Copy Markdown
Member

@copilot when i'm testing this, I end up with an error creating the global.json and it ends halfway through.

/workspaces/test $ dotnet new globaljson --allow-prerelease false
Template "global.json file" could not be created.
Failed to create template.
Details: Error while processing file /content/GlobalJson/global.json
Unable to cast object of type 'System.String' to type 'System.Boolean'.

Copilot AI commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot when i'm testing this, I end up with an error creating the global.json and it ends halfway through.

...

Fixed in commit 4d1ae57. The root cause was that the template engine's And operator uses a direct C# (bool)x cast, which fails when a bool-typed parameter value is stored as the string "false". The fix changes NoSdkVersion from datatype: "bool" to datatype: "choice" (consistent with the other optional parameters AllowPrerelease, RollForward, and TestRunner), and updates all conditions to use string equality comparisons (NoSdkVersion=="" / NoSdkVersion!="") which are safe regardless of the underlying storage type.

@baronfel

baronfel commented May 6, 2026

Copy link
Copy Markdown
Member

@copilot when i'm testing this, I end up with an error creating the global.json and it ends halfway through.

...

Fixed in commit 4d1ae57. The root cause was that the template engine's And operator uses a direct C# (bool)x cast, which fails when a bool-typed parameter value is stored as the string "false". The fix changes NoSdkVersion from datatype: "bool" to datatype: "choice" (consistent with the other optional parameters AllowPrerelease, RollForward, and TestRunner), and updates all conditions to use string equality comparisons (NoSdkVersion=="" / NoSdkVersion!="") which are safe regardless of the underlying storage type.

@copilot why can't a bool be used instead here? wasn't the original problem defining the default value as "false" (string) instead of false (bool) in the template.config.json?

Copilot AI commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot when i'm testing this, I end up with an error creating the global.json and it ends halfway through.

...
...

You raise a valid point. Technically, a bool parameter should work: JExtensions.ToString reads defaultValue and falls through to element.ToJsonString() for non-strings, so both "false" (JSON string) and false (JSON boolean) produce the same "false" string — which ParameterConverter.TryConvertLiteralToDatatype then correctly parses to false (bool) via bool.TryParse. The && operator's x => (bool)x cast works fine on a boxed bool.

The key behavioral difference is in the condition evaluator's Scope.cs: Operator.Not uses Convert.ToBoolean(x) (safe for strings), while Operator.And/Operator.Or use the direct cast (bool)x (fails for strings). The choice approach sidesteps this entirely by always using string equality comparisons (NoSdkVersion=="") that never involve (bool)x.

I changed to choice as a conservative fix consistent with the other optional parameters (AllowPrerelease, RollForward, TestRunner), but if you'd prefer bool with the string-comparison conditions replaced by negated bare references, that should also work.

Copilot AI requested a review from baronfel May 6, 2026 14:49
@baronfel

baronfel commented May 6, 2026

Copy link
Copy Markdown
Member

@copilot I'd prefer to fix the boolean conversion consistency issue - the 'natural' way to define data types should work consistently.

Copilot AI commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot I'd prefer to fix the boolean conversion consistency issue - the 'natural' way to define data types should work consistently.

Fixed in commit 223f5a8. The root cause was in Scope.cs — the And, Or, and Xor operators used (bool)x which fails when a variable value is stored as a string (e.g. "false"), while the Not operator already used Convert.ToBoolean(x ?? "False") which handles both.

Changes:

  • Scope.cs: And/Or/Xor now use Convert.ToBoolean(x ?? "False"), consistent with Not
  • template.json: NoSdkVersion is back to datatype: "bool" with defaultValue: false
  • global.json: conditions use natural boolean syntax (NoSdkVersion/!NoSdkVersion)
  • Added 2 unit tests to Cpp2EvaluatorTests covering string-bool operands in &&/||

Copilot AI review requested due to automatic review settings May 13, 2026 17:55
Copilot AI and others added 9 commits May 13, 2026 12:55
Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/3a08d95c-8aa4-40b3-827d-65843a267b72

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/bb7321e2-df28-4dc1-8781-6a48f9437b42

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
…tual exclusion)

Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/ffec2f0f-275e-417e-9840-41550b9f33e3

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
… evaluators

Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/ffec2f0f-275e-417e-9840-41550b9f33e3

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/ffec2f0f-275e-417e-9840-41550b9f33e3

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
…lting to true

Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/62e860c0-a590-4876-9f13-a0782f3bd215

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
@baronfel
baronfel force-pushed the copilot/add-allow-prerelease-option branch from 0505e54 to f2f727e Compare May 13, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds two new template options to dotnet new globaljson--allow-prerelease (to set the allowPrerelease field) and --no-sdk-version (to omit the SDK version from the generated file). Also patches the in-repo Microsoft.TemplateEngine.Core boolean evaluators to handle string-typed bool values without throwing InvalidCastException, and adds integration approval snapshots and unit tests for the new behavior.

Changes:

  • New AllowPrerelease and NoSdkVersion template parameters wired to CLI options, with conditional logic in global.json to keep JSON well-formed across all combinations.
  • Template engine fix: Scope.cs And/Or/Xor and the shared/Cpp condition finalizers now coerce string and empty-string operands to bool instead of casting directly.
  • New integration approval snapshots and 4 new Cpp2EvaluatorTests unit tests.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
template_feed/.../GlobalJson/.template.config/template.json Adds AllowPrerelease, AllowPrereleaseLower, NoSdkVersion symbols (declared as bool — see review).
template_feed/.../GlobalJson/global.json Restructures conditionals to emit rollForward, allowPrerelease, version with valid trailing commas.
template_feed/.../GlobalJson/.template.config/dotnetcli.host.json Maps new symbols to --allow-prerelease / --no-sdk-version.
template_feed/.../localize/templatestrings.*.json (×14) Adds English placeholder strings for new symbols (including `choices/true
src/TemplateEngine/.../Cpp/Scope.cs Adds ToBool helper used by Not/And/Or/Xor to handle string and empty operands.
src/TemplateEngine/.../Cpp/CppStyleEvaluatorDefinition.cs Treats empty-string final result as false.
src/TemplateEngine/.../Shared/SharedEvaluatorDefinition.cs Treats empty-string evaluation result as false.
test/.../Cpp2EvaluatorTests.cs Adds 4 tests for string-bool &&/`
test/dotnet-new.IntegrationTests/CommonTemplatesTests.cs Adds 6 new [InlineData] cases and broadens version-scrub guard for --no-sdk-version.
test/.../Approvals/DotnetNewHelpTests.CanShowHelpForTemplate_globaljson.verified.txt Help snapshot updated with new options (renders --allow-prerelease as choice — see review).
test/.../Approvals/AllCommonItemsCreate.--allow-prerelease.verified/** New approval files for the 5 new test combinations.

Comment on lines +71 to +87
"AllowPrerelease": {
"type": "parameter",
"description": "Whether prerelease SDK versions should be considered when selecting the SDK version to use.",
"displayName": "Allow prerelease",
"defaultValue": false,
"defaultIfOptionWithoutValue": true,
"datatype": "bool"
},
"AllowPrereleaseLower": {
"type": "generated",
"generator": "casing",
"parameters": {
"source": "AllowPrerelease",
"toLower": "true"
},
"replaces": "ALLOW_PRERELEASE_VALUE"
},
Comment on lines +1 to +4
{
"sdk": {
}
}
Comment on lines +8 to +15
"symbols/AllowPrerelease/description": "Whether prerelease SDK versions should be considered when selecting the SDK version to use.",
"symbols/AllowPrerelease/displayName": "Allow prerelease",
"symbols/AllowPrerelease/choices/true/description": "Consider prerelease SDK versions when selecting the .NET SDK version to use.",
"symbols/AllowPrerelease/choices/true/displayName": "Allow prerelease",
"symbols/AllowPrerelease/choices/false/description": "Don't consider prerelease SDK versions when selecting the .NET SDK version to use.",
"symbols/AllowPrerelease/choices/false/displayName": "Disallow prerelease",
"symbols/NoSdkVersion/description": "Exclude the SDK version from the generated global.json.",
"symbols/NoSdkVersion/displayName": "No SDK version",
Comment on lines +107 to +117
private static bool ToBool(object? x)
{
return x switch
{
bool b => b,
null => false,
string s when string.IsNullOrEmpty(s) => false, // unspecified choice parameter (defaultValue: "") → false
string s when bool.TryParse(s, out bool parsed) => parsed,
_ => Convert.ToBoolean(x)
};
}
@marcpopMSFT

Copy link
Copy Markdown
Member

@baronfel the current implementation doesn't set the global.json property if all-prerelease is false. I can't switch the check back to != as now it had a default value which means it sets that property even if not specified.

It needs to only set a default if the flag is passed in (with no true/false value included) and otherwise be null. Also, I'm not sure if there is a way to require all-prerelease if you're specifying --no-sdk-version as otherwise, you end up with a non-useable globaljson.

@marcpopMSFT

Copy link
Copy Markdown
Member

@baronfel looks like we lost the cleaner history but my prpoosal is to go back to the state I tested before where everything worked except for an empty --allow-prerelease. So not specifying it worked and setting it to true or false worked.

I wonder if we should reset and have copilot start from scratch.

@marcpopMSFT

Copy link
Copy Markdown
Member

@copilot can you switch back to the solution you had when I made this comment? #54133 (comment) both us and you were unable to solve the behavior and our choice is to either keep investing when we aren't sure we can fix this or accept that state where the feature overall works except you have to specify true/false. I'm ok with that prior state where we ignore the flag unless you specify the boolean value.

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copilot AI requested a review from a team as a code owner July 13, 2026 23:24
Copilot AI and others added 2 commits July 13, 2026 23:25
- Reverts f2f727e: restores AllowPrerelease as choice type (defaultValue: "",
  defaultIfOptionWithoutValue: "true") with string-comparison conditions
- Removes AllowPrereleaseLower casing generator (not needed with choice type)
- Removes failing test case for --allow-prerelease without value
- Removes corresponding approval snapshot files

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@copilot can you switch back to the solution you had when I made this comment? #54133 (comment) both us and you were unable to solve the behavior and ou...

Reverted to the state before f2f727e in the latest commit. The AllowPrerelease parameter is back to choice type with defaultValue: "" and string-comparison conditions (AllowPrerelease!=""), and the failing test case for --allow-prerelease (no value) has been removed along with its snapshot files. All scenarios with explicit values (--allow-prerelease true, --allow-prerelease false, --no-sdk-version, combined usage) continue to work and have test coverage.

@marcpopMSFT

Copy link
Copy Markdown
Member

@copilot resolve the merge conflicts in this pull request

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copilot AI requested a review from a team as a code owner July 22, 2026 23:41
…release-option

# Conflicts:
#	test/TemplateEngine/Microsoft.TemplateEngine.Core.UnitTests/Cpp2EvaluatorTests.cs
#	test/dotnet-new.IntegrationTests/CommonTemplatesTests.cs

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Resolved in the merge commit. There were two conflicts:

  1. Cpp2EvaluatorTests.cs: Our new test methods used xUnit [Fact]/Assert.Null/Assert.True. Main had migrated the file to MSTest. Resolved by converting our tests to [TestMethod] with Assert.IsNull/Assert.IsTrue/Assert.IsFalse.

  2. CommonTemplatesTests.cs: Main migrated from [Theory]/[InlineData] to [TestMethod]/[DataRow] and added ClassCleanup/_fixture. Resolved by keeping main's MSTest format while adding back our new --allow-prerelease and --no-sdk-version [DataRow] entries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dotnet new globaljson should have allowPrerelease option

5 participants