Skip to content

Commit 23b033e

Browse files
authored
Enable Explicit toggling for sources (i.e. Enable/Disable) (microsoft#5904)
<!-- To check a checkbox place an "x" between the brackets. e.g: [x] --> - [x] I have signed the [Contributor License Agreement](https://cla.opensource.microsoft.com/microsoft/winget-pkgs). - [x] I have updated the [Release Notes](../doc/ReleaseNotes.md). - [x] This pull request is related to an issue. ----- Closes microsoft#5797 This issue was related to enabling/disabling a source without removing it. Functionally in WinGet this is already captured with the "Explicit" property, where a source is not included unless it is explicitly referenced. This is effectively equivalent to a "disabled" source. In examining this problem we determined the best way to resolve this is to allow editing of a source's properties, specifically the "Explicit" property, without having to remove and re-add a source, which is problematic with default sources. This PR implements that solution to allow editing of a Source's properties, starting with the Explicit property. This works on User defined sources and Default sources. In terms of policy, Edit follows Remove; if you can remove a source you can edit that source. If you cannot remove the source, then you cannot edit the source either. This PR Introduces a new experimental feature, "**sourceEdit**" which has this new editing behavior. The user experience is not final so we are keeping it behind sourceEdit for now for testing and refinement. **New functionality: source edit subcommand** This allows a source to be edited without removing/readding. This currently only supports the "Explicit" property of a source, and includes User sources and Default sources (such as the winget-font source). Aliases for this command: `set` and `config` Example1: `winget source edit -n winget --explicit true` This will make the winget source explicit. Example2: `winget source edit -n winget-font -e false` This will make the winget-font source no longer explicit. This interface is probably going to change later, which is one of the reasons why we are using the experimental feature. Key implementation detail is the addition of an "IsOverride" property to the SourceDetailsInternal which functions similar to IsTombstone, only instead of deleting the source it overrides some of the source's properties, currently the "Explicit" property. This is needed for the Default sources. For user sources the update is straightforward. <img width="504" height="110" alt="image" src="https://github.com/user-attachments/assets/d4d16dd2-61d7-460b-ba3e-310fbdb499e9" /> **Tested** * Added CLI tests for the default source override validation * Added E2E tests for the command validation * Manual verification of the command functionality. ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/5904)
1 parent 21b685c commit 23b033e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+925
-4
lines changed

doc/ReleaseNotes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
## New in v1.28
22

3+
# Experimental Feature: 'sourceEdit'
4+
New feature that adds an 'edit' subcommand to the 'source' command. This can be used to set an explicit source to be implicit and vice-versa. For example, with this feature you can make the 'winget-font' source an implicit source instead of explicit source.
5+
6+
To enable this feature, add the 'sourceEdit' experimental feature to your settings.
7+
```
8+
"experimentalFeatures": {
9+
"sourceEdit": true
10+
},
11+
```
12+
To use the feature, try `winget source edit winget-font` to set the Explicit state to the default.
13+
314
<!-- Nothing yet! -->
415

516
## Bug Fixes

doc/Settings.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,13 @@ This feature enables support for fonts via `winget settings`. The `winget font l
393393
"fonts": true
394394
},
395395
```
396+
397+
### sourceEdit
398+
399+
This feature enables support for additional source command improvements via `winget settings`. The `winget source edit` command will become available with this feature.
400+
401+
```json
402+
"experimentalFeatures": {
403+
"sourceEdit": true
404+
},
405+
```

src/AppInstallerCLICore/Argument.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ namespace AppInstaller::CLI
121121
return { type, "explicit"_liv };
122122
case Execution::Args::Type::SourceTrustLevel:
123123
return { type, "trust-level"_liv };
124+
case Execution::Args::Type::SourceEditExplicit:
125+
return { type, "explicit"_liv, 'e' };
124126

125127
//Hash Command
126128
case Execution::Args::Type::HashFile:
@@ -410,6 +412,8 @@ namespace AppInstaller::CLI
410412
return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional };
411413
case Args::Type::SourceExplicit:
412414
return Argument{ type, Resource::String::SourceExplicitArgumentDescription, ArgumentType::Flag };
415+
case Args::Type::SourceEditExplicit:
416+
return Argument{ type, Resource::String::SourceEditExplicitArgumentDescription, ArgumentType::Positional };
413417
case Args::Type::SourceTrustLevel:
414418
return Argument{ type, Resource::String::SourceTrustLevelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help };
415419
case Args::Type::ValidateManifest:

src/AppInstallerCLICore/Commands/SourceCommand.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace AppInstaller::CLI
2121
std::make_unique<SourceListCommand>(FullName()),
2222
std::make_unique<SourceUpdateCommand>(FullName()),
2323
std::make_unique<SourceRemoveCommand>(FullName()),
24+
std::make_unique<SourceEditCommand>(FullName()),
2425
std::make_unique<SourceResetCommand>(FullName()),
2526
std::make_unique<SourceExportCommand>(FullName()),
2627
});
@@ -312,4 +313,55 @@ namespace AppInstaller::CLI
312313
Workflow::GetSourceListWithFilter <<
313314
Workflow::ExportSourceList;
314315
}
316+
317+
// Source Edit Command
318+
319+
std::vector<Argument> SourceEditCommand::GetArguments() const
320+
{
321+
return {
322+
Argument::ForType(Args::Type::SourceName).SetRequired(true),
323+
Argument::ForType(Args::Type::SourceEditExplicit),
324+
};
325+
}
326+
327+
Resource::LocString SourceEditCommand::ShortDescription() const
328+
{
329+
return { Resource::String::SourceEditCommandShortDescription };
330+
}
331+
332+
Resource::LocString SourceEditCommand::LongDescription() const
333+
{
334+
return { Resource::String::SourceEditCommandLongDescription };
335+
}
336+
337+
Utility::LocIndView SourceEditCommand::HelpLink() const
338+
{
339+
return s_SourceCommand_HelpLink;
340+
}
341+
342+
void SourceEditCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const
343+
{
344+
if (execArgs.Contains(Execution::Args::Type::SourceEditExplicit))
345+
{
346+
std::string_view explicitArg = execArgs.GetArg(Execution::Args::Type::SourceEditExplicit);
347+
auto convertedArg = Utility::TryConvertStringToBool(explicitArg);
348+
if (!convertedArg.has_value())
349+
{
350+
auto validOptions = Utility::Join(", "_liv, std::vector<Utility::LocIndString>{
351+
"true"_lis,
352+
"false"_lis,
353+
});
354+
throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::SourceEditExplicit).Name(), validOptions));
355+
}
356+
}
357+
}
358+
359+
void SourceEditCommand::ExecuteInternal(Context& context) const
360+
{
361+
context <<
362+
Workflow::EnsureFeatureEnabled(Settings::ExperimentalFeature::Feature::SourceEdit) <<
363+
Workflow::EnsureRunningAsAdmin <<
364+
Workflow::GetSourceListWithFilter <<
365+
Workflow::EditSources;
366+
}
315367
}

src/AppInstallerCLICore/Commands/SourceCommand.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,20 @@ namespace AppInstaller::CLI
121121
protected:
122122
void ExecuteInternal(Execution::Context& context) const override;
123123
};
124+
125+
struct SourceEditCommand final : public Command
126+
{
127+
SourceEditCommand(std::string_view parent) : Command("edit", { "config", "set" }, parent, Settings::ExperimentalFeature::Feature::SourceEdit) {}
128+
129+
std::vector<Argument> GetArguments() const override;
130+
131+
Resource::LocString ShortDescription() const override;
132+
Resource::LocString LongDescription() const override;
133+
134+
Utility::LocIndView HelpLink() const override;
135+
136+
protected:
137+
void ValidateArgumentsInternal(Execution::Args& execArgs) const override;
138+
void ExecuteInternal(Execution::Context& context) const override;
139+
};
124140
}

src/AppInstallerCLICore/ExecutionArgs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ namespace AppInstaller::CLI::Execution
6565
ForceSourceReset,
6666
SourceExplicit,
6767
SourceTrustLevel,
68+
SourceEditExplicit,
6869

6970
//Hash Command
7071
HashFile,

src/AppInstallerCLICore/Resources.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,13 @@ namespace AppInstaller::CLI::Resource
687687
WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription);
688688
WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription);
689689
WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription);
690+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandLongDescription);
691+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandShortDescription);
692+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditExplicitArgumentDescription);
693+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNewValue);
694+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNoChanges);
695+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOldValue);
696+
WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOne);
690697
WINGET_DEFINE_RESOURCE_STRINGID(SourceExplicitArgumentDescription);
691698
WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandLongDescription);
692699
WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandShortDescription);

src/AppInstallerCLICore/Workflows/SourceFlow.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,40 @@ namespace AppInstaller::CLI::Workflow
272272
}
273273
}
274274

275+
void EditSources(Execution::Context& context)
276+
{
277+
// We are assuming there is only one match, as SourceName is a required parameter.
278+
const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>();
279+
280+
for (const auto& sd : sources)
281+
{
282+
// Get the current source with this name.
283+
Repository::Source targetSource{ sd.Name };
284+
auto oldExplicitValue = sd.Explicit;
285+
286+
std::optional<bool> isExplicit;
287+
if (context.Args.Contains(Execution::Args::Type::SourceEditExplicit))
288+
{
289+
isExplicit = Utility::TryConvertStringToBool(context.Args.GetArg(Execution::Args::Type::SourceEditExplicit));
290+
}
291+
292+
Repository::SourceEdit edits{ isExplicit };
293+
if (!targetSource.RequiresChanges(edits))
294+
{
295+
context.Reporter.Info() << Resource::String::SourceEditNoChanges(Utility::LocIndView{ sd.Name }) << std::endl;
296+
continue;
297+
}
298+
299+
context.Reporter.Info() << Resource::String::SourceEditOne(Utility::LocIndView{ sd.Name }) << std::endl;
300+
targetSource.Edit(edits);
301+
302+
// Output updated source information. Since only Explicit is editable, we will only list that field. The name of the source being edited is listed prior to the edits.
303+
Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceEditOldValue, Resource::String::SourceEditNewValue });
304+
table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(oldExplicitValue) }, std::string{ Utility::ConvertBoolToString(isExplicit.value()) } });
305+
table.Complete();
306+
}
307+
}
308+
275309
void QueryUserForSourceReset(Execution::Context& context)
276310
{
277311
if (!context.Args.Contains(Execution::Args::Type::ForceSourceReset))

src/AppInstallerCLICore/Workflows/SourceFlow.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,10 @@ namespace AppInstaller::CLI::Workflow
8383
// Inputs: None
8484
// Outputs: None
8585
void ForceInstalledCacheUpdate(Execution::Context& context);
86+
87+
// Edits a source in SourceList.
88+
// Required Args: SourceName
89+
// Inputs: SourceList
90+
// Outputs: None
91+
void EditSources(Execution::Context& context);
8692
}

src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ public void DisableWinGetPolicy()
110110
Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message);
111111
Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult);
112112

113+
groupPolicyException = Assert.Catch<GroupPolicyException>(() => { EditPackageCatalogOptions packageManagerSettings = this.TestFactory.CreateEditPackageCatalogOptions(); });
114+
Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message);
115+
Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult);
116+
113117
// PackageManagerSettings is not implemented in context OutOfProcDev
114118
if (this.TestFactory.Context == ClsidContext.InProc)
115119
{

0 commit comments

Comments
 (0)