Skip to content
Merged
6 changes: 3 additions & 3 deletions .github/plugin/skills/winapp-cli/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ For full debugging scenarios and IDE setup, see the [Debugging Guide](https://gi

## Tips

- Use `--use-defaults` (alias: `--no-prompt`) in CI/CD pipelines and scripts to avoid interactive prompts
- Use `--use-defaults` (alias: `--no-prompt`) in CI/CD pipelines and scripts to avoid interactive prompts. Non-interactive environments (piped stdin, CI runners) are auto-detected and will use defaults automatically with a warning.
- If you only need `Package.appxmanifest` without SDK setup, use `winapp manifest generate` instead of `init`
- `winapp init` is idempotent for the config file — re-running it won't overwrite an existing `winapp.yaml` unless you use `--config-only`
- For Electron projects, prefer `npm install --save-dev @microsoft/winappcli` and use `npx winapp init` instead of the standalone CLI
Expand All @@ -152,14 +152,14 @@ For full debugging scenarios and IDE setup, see the [Debugging Guide](https://gi
| "winapp.yaml not found" | Running `restore`/`update` without config | Run `winapp init` first, or ensure you're in the right directory |
| "Directory not found" | Target directory doesn't exist | Create the directory first or check the path |
| SDK download fails | Network issue or firewall | Ensure internet access; check proxy settings |
| `init` prompts unexpectedly in CI | Missing `--use-defaults` flag | Add `--use-defaults` to skip all prompts |
| `init` prompts unexpectedly in CI | Missing `--use-defaults` flag | Add `--use-defaults` to skip all prompts (note: non-interactive shells are now auto-detected) |


## Command Reference

### `winapp init`

Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default (use --use-defaults to skip prompts). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing.
Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default; automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing.

#### Arguments
<!-- auto-generated from cli-schema.json -->
Expand Down
2 changes: 1 addition & 1 deletion docs/cli-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@
}
},
"init": {
"description": "Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default (use --use-defaults to skip prompts). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing.",
"description": "Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default; automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing.",
"hidden": false,
"arguments": {
"base-directory": {
Expand Down
4 changes: 2 additions & 2 deletions docs/fragments/skills/winapp-cli/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ For full debugging scenarios and IDE setup, see the [Debugging Guide](https://gi

## Tips

- Use `--use-defaults` (alias: `--no-prompt`) in CI/CD pipelines and scripts to avoid interactive prompts
- Use `--use-defaults` (alias: `--no-prompt`) in CI/CD pipelines and scripts to avoid interactive prompts. Non-interactive environments (piped stdin, CI runners) are auto-detected and will use defaults automatically with a warning.
- If you only need `Package.appxmanifest` without SDK setup, use `winapp manifest generate` instead of `init`
- `winapp init` is idempotent for the config file — re-running it won't overwrite an existing `winapp.yaml` unless you use `--config-only`
- For Electron projects, prefer `npm install --save-dev @microsoft/winappcli` and use `npx winapp init` instead of the standalone CLI
Expand All @@ -147,4 +147,4 @@ For full debugging scenarios and IDE setup, see the [Debugging Guide](https://gi
| "winapp.yaml not found" | Running `restore`/`update` without config | Run `winapp init` first, or ensure you're in the right directory |
| "Directory not found" | Target directory doesn't exist | Create the directory first or check the path |
| SDK download fails | Network issue or firewall | Ensure internet access; check proxy settings |
| `init` prompts unexpectedly in CI | Missing `--use-defaults` flag | Add `--use-defaults` to skip all prompts |
| `init` prompts unexpectedly in CI | Missing `--use-defaults` flag | Add `--use-defaults` to skip all prompts (note: non-interactive shells are now auto-detected) |
1 change: 1 addition & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The search skips commonly ignored directories (node_modules, bin, obj, .git, etc

- If a directory argument is provided (e.g., `winapp init .` or `winapp init path/to/project`), the search is skipped and `init` checks only that directory for a compatible project
- If `--use-defaults` is set without a directory argument, `init` searches for projects and errors with the list of detected projects — pass an explicit directory to use non-interactive mode (e.g., `winapp init . --use-defaults`)
- In non-interactive environments (piped stdin, CI, redirected input), `init` automatically uses `--use-defaults` behavior and emits a warning: `Non-interactive environment detected. Using default values.`
- If the current directory is a compatible project, `init` proceeds immediately
- If exactly one project is found elsewhere, you're prompted to confirm
- If multiple projects are found, you can select which one to initialize — the current directory is always available as a fallback option
Expand Down
1 change: 1 addition & 0 deletions src/winapp-CLI/WinApp.Cli.Tests/BaseCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract class BaseCommandTests(bool configPaths = true, LogLevel logLeve
public void SetupBase()
{
TestAnsiConsole = new TestConsole();
TestAnsiConsole.Profile.Capabilities.Interactive = true;

ConsoleStdOut = new OutputCapture(TestAnsiConsole.Profile.Out.Writer);
ConsoleStdErr = new OutputCapture(Console.Error);
Expand Down
46 changes: 46 additions & 0 deletions src/winapp-CLI/WinApp.Cli.Tests/InitCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,50 @@ public async Task InitCommand_NoArgs_NestedProject_ConfigPlacedInProjectDir()
Assert.IsFalse(File.Exists(rootConfig),
"winapp.yaml should NOT be in root when a nested project was selected");
}

[TestMethod]
public async Task InitCommand_NonInteractiveShell_UsesDefaultsWithoutPrompting()
{
// Arrange — simulate a non-interactive environment (piped stdin, CI)
TestAnsiConsole.Profile.Capabilities.Interactive = false;

var initCommand = GetRequiredService<InitCommand>();
// No --use-defaults, no explicit directory — would normally prompt interactively
var args = new[] { "--config-only" };

// Do NOT push any keys — if it tries to prompt, it will throw/hang

// Act
var exitCode = await ParseAndInvokeWithCaptureAsync(initCommand, args);

// Assert — should succeed using defaults (same as --use-defaults behavior)
Assert.AreEqual(0, exitCode, "Init should succeed in non-interactive mode without prompting");

var configPath = Path.Combine(_tempDirectory.FullName, "winapp.yaml");
Assert.IsTrue(File.Exists(configPath),
"winapp.yaml should be created using defaults in non-interactive mode");
}

[TestMethod]
public async Task InitCommand_NonInteractiveShell_WithNestedProject_UsesCurrentDirectory()
{
// Arrange — non-interactive with a nested project (would normally prompt to confirm)
TestAnsiConsole.Profile.Capabilities.Interactive = false;

var subDir = _tempDirectory.CreateSubdirectory("my-app");
File.WriteAllText(Path.Combine(subDir.FullName, "Cargo.toml"), "[package]\nname = \"test\"");

var initCommand = GetRequiredService<InitCommand>();
var args = new[] { "--config-only" };

// Act — should not prompt, should use cwd directly
var exitCode = await ParseAndInvokeWithCaptureAsync(initCommand, args);

// Assert — uses cwd (--use-defaults behavior), not the nested project
Assert.AreEqual(0, exitCode, "Init should succeed in non-interactive mode");

var configPath = Path.Combine(_tempDirectory.FullName, "winapp.yaml");
Assert.IsTrue(File.Exists(configPath),
"winapp.yaml should be created in cwd when non-interactive");
}
}
10 changes: 9 additions & 1 deletion src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static InitCommand()
};
}

public InitCommand() : base("init", "Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default (use --use-defaults to skip prompts). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing.")
public InitCommand() : base("init", "Start here for initializing a Windows app with required setup. Sets up everything needed for Windows app development: creates Package.appxmanifest with default assets, downloads Windows SDK and Windows App SDK packages, and generates projections. When SDK packages are managed (--setup-sdks stable/preview/experimental), also creates winapp.yaml to pin versions for 'restore'/'update'; with --setup-sdks none (e.g., for Rust/Tauri projects that bring their own SDK bindings), no winapp.yaml is created. Interactive by default; automatically uses defaults in non-interactive environments (use --use-defaults to skip prompts explicitly). Use 'restore' instead if you cloned a repo that already has winapp.yaml. Use 'manifest generate' if you only need a manifest, or 'cert generate' if you need a development certificate for code signing.")
{
Arguments.Add(BaseDirectoryArgument);
Options.Add(ConfigDirOption);
Expand Down Expand Up @@ -88,6 +88,14 @@ public override async Task<int> InvokeAsync(ParseResult parseResult, Cancellatio
var useDefaults = parseResult.GetValue(UseDefaults);
var configOnly = parseResult.GetValue(ConfigOnlyOption);

// Detect non-interactive environments (piped stdin, CI, etc.) and fall back
// to --use-defaults behavior to avoid InvalidOperationException from prompts.
if (!useDefaults && !ansiConsole.Profile.Capabilities.Interactive)
{
logger.LogWarning("{Warning} Non-interactive environment detected. Using default values.", UiSymbols.Warning);
useDefaults = true;
Comment on lines +93 to +96
}

DirectoryInfo? selectedDirectory;

if (baseDirectoryExplicit)
Expand Down
Loading