2026-04-21 20:59:52 +08:00
|
|
|
using System.Globalization;
|
|
|
|
|
|
|
|
|
|
namespace LanMountainDesktop.Launcher;
|
|
|
|
|
|
|
|
|
|
internal sealed class CommandContext
|
|
|
|
|
{
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
private const string LaunchSourceOptionName = "launch-source";
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private static readonly string[] GuiCommands =
|
|
|
|
|
[
|
|
|
|
|
"launch",
|
|
|
|
|
"apply-update",
|
|
|
|
|
"preview-splash",
|
|
|
|
|
"preview-error",
|
|
|
|
|
"preview-update",
|
|
|
|
|
"preview-oobe",
|
|
|
|
|
"preview-debug"
|
|
|
|
|
];
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
public string Command { get; }
|
|
|
|
|
|
|
|
|
|
public string SubCommand { get; }
|
|
|
|
|
|
|
|
|
|
public IReadOnlyDictionary<string, string> Options { get; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 原始命令行参数,用于转发给主程序
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyList<string> RawArgs { get; }
|
|
|
|
|
|
|
|
|
|
public bool IsLegacyPluginInstall =>
|
|
|
|
|
Options.ContainsKey("source") &&
|
|
|
|
|
Options.ContainsKey("plugins-dir") &&
|
|
|
|
|
Options.ContainsKey("result");
|
|
|
|
|
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
public string LaunchSource => NormalizeLaunchSource(GetOption(LaunchSourceOptionName)) ?? InferLaunchSource();
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// 是否处于调试模式(从 Rider/VS 等 IDE 启动)
|
|
|
|
|
/// 仅当明确指定 --debug 参数或调试器附加时才启用
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsDebugMode =>
|
|
|
|
|
Options.ContainsKey("debug") ||
|
|
|
|
|
System.Diagnostics.Debugger.IsAttached;
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
public bool IsPreviewCommand =>
|
|
|
|
|
Command.StartsWith("preview-", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
public bool IsGuiCommand =>
|
|
|
|
|
GuiCommands.Contains(Command, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
public bool IsMaintenanceCommand =>
|
|
|
|
|
string.Equals(LaunchSource, "apply-update", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
string.Equals(LaunchSource, "plugin-install", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
string.Equals(Command, "update", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
string.Equals(Command, "plugin", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
public string? ExplicitAppRoot => GetOption("app-root");
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
private CommandContext(string command, string subCommand, Dictionary<string, string> options, string[] rawArgs)
|
|
|
|
|
{
|
|
|
|
|
Command = command;
|
|
|
|
|
SubCommand = subCommand;
|
|
|
|
|
Options = options;
|
|
|
|
|
RawArgs = rawArgs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static CommandContext FromArgs(string[] args)
|
|
|
|
|
{
|
|
|
|
|
var options = ParseOptions(args);
|
|
|
|
|
var command = args.Length > 0 && !args[0].StartsWith("--", StringComparison.Ordinal)
|
|
|
|
|
? args[0]
|
|
|
|
|
: "launch";
|
|
|
|
|
var subCommand = args.Length > 1 && !args[1].StartsWith("--", StringComparison.Ordinal)
|
|
|
|
|
? args[1]
|
|
|
|
|
: string.Empty;
|
|
|
|
|
|
|
|
|
|
return new CommandContext(command, subCommand, options, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string? GetOption(string key)
|
|
|
|
|
{
|
|
|
|
|
return Options.TryGetValue(key, out var value) ? value : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int GetIntOption(string key, int fallback)
|
|
|
|
|
{
|
|
|
|
|
var raw = GetOption(key);
|
|
|
|
|
return int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)
|
|
|
|
|
? value
|
|
|
|
|
: fallback;
|
|
|
|
|
}
|
|
|
|
|
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
private string InferLaunchSource()
|
|
|
|
|
{
|
|
|
|
|
if (IsPreviewCommand)
|
|
|
|
|
{
|
|
|
|
|
return "debug-preview";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.Equals(Command, "apply-update", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return "apply-update";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsLegacyPluginInstall || string.Equals(Command, "plugin", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return "plugin-install";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "normal";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string? NormalizeLaunchSource(string? raw)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(raw))
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return raw.Trim().ToLowerInvariant() switch
|
|
|
|
|
{
|
|
|
|
|
"normal" => "normal",
|
|
|
|
|
"postinstall" => "postinstall",
|
|
|
|
|
"apply-update" => "apply-update",
|
|
|
|
|
"plugin-install" => "plugin-install",
|
|
|
|
|
"debug-preview" => "debug-preview",
|
|
|
|
|
_ => null
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
private static Dictionary<string, string> ParseOptions(string[] args)
|
|
|
|
|
{
|
|
|
|
|
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
for (var i = 0; i < args.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var current = args[i];
|
|
|
|
|
if (!current.StartsWith("--", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var key = current[2..];
|
|
|
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i + 1 < args.Length && !args[i + 1].StartsWith("--", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
values[key] = args[++i];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
values[key] = "true";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return values;
|
|
|
|
|
}
|
|
|
|
|
}
|