Skip to content

VSC extension: workspace & multi-project support for all commands#586

Draft
chiaramooney wants to merge 9 commits into
mainfrom
chiaramooney/vsc-init-project-detection
Draft

VSC extension: workspace & multi-project support for all commands#586
chiaramooney wants to merge 9 commits into
mainfrom
chiaramooney/vsc-init-project-detection

Conversation

@chiaramooney

Copy link
Copy Markdown
Contributor

Summary

Adds project detection and multi-project support to the VS Code extension, allowing users to run WinApp commands from the root of a monorepo or multi-app workspace without needing to open VS Code at the project directory. Resolves #492.

Changes

New: Project detection module (src/winapp-VSC/src/project-detection.ts)

Updated: winapp init command

  • If project at workspace root: proceeds directly to SDK mode selection
  • If no project at root: searches workspace and shows QuickPick with discovered projects
  • 0 projects: offers to initialize in current directory
  • 1 project: shows it with current directory fallback
  • 2+ projects: full QuickPick list
  • 10+ projects: indicates search was capped

Updated: All other project-context commands

Commands now use a shared resolveProjectDirectory() helper:

  • restore, update, pack, run, create-debug-identity
  • manifest generate, manifest update-assets, manifest add-alias
  • cert generate, get-winapp-path, unregister

Behavior: root project auto-selects, otherwise searches and prompts.

Commands that take explicit file paths (sign, cert install, cert info) are unchanged.

New: winapp.appDirectories setting

jsonc // .vscode/settings.json { "winapp.appDirectories": ["apps/my-app", "apps/shell"] }
When configured, commands use these paths directly (no scanning). Single entry auto-selects; multiple entries show a QuickPick. Takes priority over auto-detection.

Tests

  • 25 unit tests covering all project detection logic
  • Run with: npm run test:unit from src/winapp-VSC

Documentation

  • Updated src/winapp-VSC/README.md with full workspace & multi-project support section

Resolves

chiaramooney and others added 7 commits June 19, 2026 12:05
When no project is found at the workspace root, the extension now
searches for compatible app projects (Tauri, Electron, Flutter, .NET,
Rust, C++) and presents them in a QuickPick for the user to select.

Mirrors the CLI's ProjectDetectionService behavior (PR #530):
- 0 projects: QuickPick with current directory fallback
- 1 project at root: proceeds directly to SDK mode selection
- 1 project nested: QuickPick with project + current directory
- 2+ projects: QuickPick with all projects + current directory
- 10+ projects: adds note that search stopped at 10 entries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Commands that need a project context (restore, update, pack, run,
create-debug-identity, manifest generate, manifest update-assets,
cert generate, get-winapp-path, manifest add-alias, unregister) now
use a shared resolveProjectDirectory() helper that:

1. If workspace root contains a project, uses it directly (no prompt)
2. Otherwise searches for projects in the workspace
3. Single project found: auto-selects it
4. Multiple projects found: shows QuickPick to choose
5. No projects found: falls back to workspace root

Commands that take explicit file paths (sign, cert install, cert info)
are unchanged.

Resolves: #492

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds: debug, release, .vscode, artifacts, TestResults, .gradle,
.nuget, .cargo

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Symlinks/junctions: use Dirent.isSymbolicLink() which on Windows
   correctly reports both symlinks and junctions (reparse points),
   matching the CLI's FileAttributes.ReparsePoint check.

2. csproj detection: use regex to extract <OutputType> and
   <IsTestProject> element values (case-insensitive) instead of
   naive string.includes() checks. Mirrors the CLI's XDocument-based
   parsing logic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When configured in .vscode/settings.json, commands use the listed
directories directly instead of scanning the workspace. Single entry
auto-selects; multiple entries show a QuickPick.

This is useful for users who know their project locations and want
to avoid the scan on every command invocation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
25 tests covering:
- detectProjectAt: all project types (.NET, Electron, Flutter, Rust,
  C++, Tauri), test project exclusion, library exclusion, priority
  ordering, display path formatting
- detectProjects: BFS traversal, maxProjects limit, ignored directory
  skipping (node_modules, bin, obj, etc.), hidden dir skipping, no
  recursion into detected projects
- getDisplayFilePath / getProjectLabel: formatting helpers

Run with: npm run test:unit

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

This PR adds workspace-aware, multi-project support to the WinApp VS Code extension (src/winapp-VSC), so commands can target an app project that is not at the workspace root (monorepos, multi-app repos, nested projects). It introduces a new TypeScript project-detection module that mirrors the CLI's ProjectDetectionService, a shared resolveProjectDirectory() helper used by all project-context commands, a smarter winapp init flow, and a new winapp.appDirectories setting that takes precedence over auto-detection. It resolves issue #492 and aligns extension behavior with CLI PR #530.

Changes:

  • New project-detection.ts (BFS detection of Tauri/Electron/Flutter/.NET/Rust/C++, skip-list, symlink handling) plus 25 mocha unit tests and a test:unit script.
  • extension.ts: resolveProjectDirectory() (setting → root project → workspace scan with QuickPick) wired into restore, update, pack, run, create-debug-identity, manifest *, cert generate, get-winapp-path, unregister; init gains its own detection/QuickPick flow.
  • New winapp.appDirectories configuration in package.json, README docs, and tsconfig.json mocha types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/winapp-VSC/src/project-detection.ts New detection module mirroring the CLI service (BFS, skip-list, csproj heuristic).
src/winapp-VSC/src/extension.ts Adds resolveProjectDirectory(), multi-project init flow, and routes all project-context commands through resolved dirs.
src/winapp-VSC/src/test/project-detection.test.ts New mocha unit tests covering detection logic.
src/winapp-VSC/package.json Adds winapp.appDirectories setting and test:unit script.
src/winapp-VSC/tsconfig.json Adds mocha to global types for tests.
src/winapp-VSC/README.md Documents workspace/multi-project support and updated init behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/winapp-VSC/package.json
Comment thread src/winapp-VSC/src/extension.ts
Comment thread src/winapp-VSC/src/project-detection.ts
Comment thread src/winapp-VSC/src/extension.ts Outdated
Comment thread src/winapp-VSC/src/project-detection.ts
…xplicit mocha dep

- Make detectProjects() async with fs/promises and periodic yielding so
  withProgress UI stays responsive during large workspace scans
- Use case-insensitive comparison for SKIP_DIRS (matches CLI behavior)
- Replace fragile .replace() pattern with getDisplayFilePath() import
- Add mocha as explicit devDependency (was only transitive)
- Update isExecutableCsproj docstring to reflect simplified heuristic

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@chiaramooney chiaramooney requested a review from zateutsch June 23, 2026 23:00

@zateutsch zateutsch 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.

Here's what my agent picked up on this. A lot of these are related to having project detected implemented twice, not sure if the structure of the extension would allow us to share the logic, but may be worth looking into.

I would definitely take a look at H2 to confirm that's a real security risk.

Summary

Critical: 0 High: 2 Medium: 9 Low: 3

Coverage

security ⚠ 2 findings
correctness ⚠ 2 findings
cli-ux ⚠ 1 finding
alternative-solution ⚠ 5 findings
test-coverage ⚠ 5 findings
docs-and-samples ⚠ 1 finding
packaging ✓ clean
multi-model ✓ 2/2 high confirmed (1 specialist "high" downgraded to medium)

Findings

H1 src/winapp-VSC/package.json:197-200 test-coverage New mocha unit tests are not wired into any CI workflow
H2 src/winapp-VSC/src/extension.ts:547-552 security PowerShell command injection via detected project folder name in init
M1 src/winapp-VSC/README.md:54-65 docs-and-samples README says init "auto-selects" single project; code shows a confirm QuickPick
M2 src/winapp-VSC/src/extension.ts:116-177 test-coverage resolveProjectDirectory branching logic has no tests
M3 src/winapp-VSC/src/extension.ts:121-130 security winapp.appDirectories accepts absolute/.. paths outside workspace
M4 src/winapp-VSC/src/extension.ts:464-476 cli-ux init command bypasses the winapp.appDirectories setting
M5 src/winapp-VSC/src/extension.ts:464-552 test-coverage init bespoke picker flow is untested
M6 src/winapp-VSC/src/project-detection.ts:44-48 alternative-solution TS detector duplicates C# ProjectDetectionService (drift risk)
M7 src/winapp-VSC/src/project-detection.ts:91-130 correctness "async" BFS calls synchronous fs APIs; blocks extension host
M8 src/winapp-VSC/src/project-detection.ts:106-129 test-coverage Symlink/error/yield/malformed-file branches untested
M9 src/winapp-VSC/src/project-detection.ts:766-784 correctness csproj regex parsing diverges from CLI's XML parsing
L1 src/winapp-VSC/src/extension.ts:5 alternative-solution getProjectLabel imported but unused in extension.ts
L2 src/winapp-VSC/src/extension.ts:464-539 alternative-solution init duplicates ~60 lines of resolveProjectDirectory picker logic
L3 src/winapp-VSC/src/project-detection.ts:44-48 test-coverage No test guards TS↔C# detection drift

Details

H1 src/winapp-VSC/package.json:197-200

  • Severity: high
  • Confidence: high
  • Domain: test-coverage, packaging
  • Multi-model: confirmed
  • Finding: The new test:unit mocha tests never run in CI, so the detection logic is unguarded on PRs.
  • Evidence: package.json adds "test:unit": "tsc -p ./ && mocha out/test/**/*.test.js", but .github/workflows/build-package.yml:59 only runs build-cli.ps1 -SkipDocs and scripts/package-vsc.ps1:159 only runs npm run compile. No workflow invokes npm run test:unit.
  • Recommendation: Add a CI step (in build-package.yml or package-vsc.ps1) that runs npm ci + npm run test:unit in src/winapp-VSC before packaging.

H2 src/winapp-VSC/src/extension.ts:547-552

  • Severity: high
  • Confidence: high
  • Domain: security
  • Multi-model: confirmed
  • Finding: A detected project directory name is interpolated into a PowerShell command string, allowing code execution during winapp.init.
  • Evidence: runWinappCommand sends raw text to a powershell.exe terminal: terminal.sendText(& "${cliPath}" ${command}) (extension.ts:66). The init path builds `init "${selectedPath}" --use-defaults` (line 547), where selectedPath derives from on-disk folder names via path.relative(...). A folder named with PowerShell expansion syntax (e.g. $(...)) is evaluated by PowerShell. (Other commands route the directory through createTerminal({ cwd }), which is not injectable — only the init command interpolates it into the command string.)
  • Recommendation: Don't compose a PowerShell command string from paths — pass arguments via a non-shell spawn/argv array, or PowerShell-escape every interpolated value centrally.

M1 src/winapp-VSC/README.md:54-65

  • Severity: medium
  • Confidence: high
  • Domain: docs-and-samples
  • Finding: README's init scenario table claims a single discovered project is auto-selected; the code shows a confirmation QuickPick instead.
  • Evidence: README: | No project at root, 1 project found elsewhere | Auto-selects that project |. But extension.ts:286-300 shows a QuickPick offering the project + "Current directory" and awaits the user's choice.
  • Recommendation: Reword to "offered for selection/confirmation (with current-directory alternative)", not "auto-selects".

M2 src/winapp-VSC/src/extension.ts:116-177

  • Severity: medium
  • Confidence: high
  • Domain: test-coverage
  • Finding: resolveProjectDirectory has substantial branching (appDirectories single/multiple, root vs scan, 0/1/many results, cancel) with no tests.
  • Evidence: Only test file imports project-detection; no test touches extension.ts.
  • Recommendation: Extract the resolver behind injectable VS Code/detection facades (or a pure helper) and unit-test each branch.

M3 src/winapp-VSC/src/extension.ts:121-130

  • Severity: medium
  • Confidence: high
  • Domain: security
  • Finding: winapp.appDirectories is documented as relative but path.resolve accepts absolute paths and .., letting workspace settings retarget commands outside the workspace.
  • Evidence: return path.resolve(workspacePath, appDirs[0]); / directory: path.resolve(workspacePath, dir) with no containment check.
  • Recommendation: Canonicalize and reject entries that are absolute or whose real path escapes the workspace root.

M4 src/winapp-VSC/src/extension.ts:464-476

  • Severity: medium
  • Confidence: high
  • Domain: cli-ux
  • Finding: The init command (the primary reason users configure appDirectories) ignores the setting — it goes straight to root-detect + scan.
  • Evidence: init uses detectProjectAt/detectProjects directly and never reads config.get('appDirectories'), unlike resolveProjectDirectory (lines 117-136).
  • Recommendation: Route init through shared resolution that honors appDirectories first, then layers the extra "Current directory" option.

M5 src/winapp-VSC/src/extension.ts:464-552

  • Severity: medium
  • Confidence: high
  • Domain: test-coverage
  • Finding: The init picker flow (root / 0 / 1 / many projects, cancel, relative-path conversion, command building) is untested.
  • Evidence: extension.ts:464-552 adds many branches culminating in init "${selectedPath}"; no covering tests.
  • Recommendation: Extract an init-selection/command-building helper and test each path, or add VS Code-API-mocked tests.

M6 src/winapp-VSC/src/project-detection.ts:44-48

  • Severity: medium
  • Confidence: high
  • Domain: alternative-solution
  • Finding: The extension re-implements the CLI's project detection in TS rather than reusing the existing source of truth, creating long-term drift risk.
  • Evidence: Comments state it "Mirrors ProjectDetectionService.DetectProject from WinApp.Cli"; the repo already has ProjectDetectionService.cs with the same rules. (See M9 for an already-present divergence.)
  • Recommendation: Consider a CLI JSON detection entry point reusing IProjectDetectionService that the extension spawns; the extra process is acceptable since detection already does filesystem scanning.

M7 src/winapp-VSC/src/project-detection.ts:91-130

  • Severity: medium
  • Confidence: high
  • Domain: correctness, alternative-solution
  • Finding: detectProjects is async and claims to "keep the UI responsive", but each node calls synchronous fs (readdirSync/readFileSync/existsSync) via detectProjectAt, blocking the extension host on large trees.
  • Evidence: await fsp.readdir is used for queue expansion, but detectProjectAt(current, root) (line 98) uses fs.readdirSync (line 705), fs.readFileSync (line 730), fs.existsSync throughout.
  • Recommendation: Make detection consistently async with fs/promises, or run the sync version off the UI-sensitive path.

M8 src/winapp-VSC/src/project-detection.ts:106-129

  • Severity: medium
  • Confidence: high
  • Domain: test-coverage
  • Finding: Safety/error branches lack coverage: symlink/junction skipping, stat/read failures, the periodic-yield path, malformed package.json/csproj, and Electron-in-devDependencies.
  • Evidence: Tests cover ignored/hidden dirs and maxProjects but none of the above try/catch or symlink branches.
  • Recommendation: Add mocha cases for symlink skip, inaccessible dirs, malformed files, devDependencies.electron, and >50 dirs to hit the yield branch.

M9 src/winapp-VSC/src/project-detection.ts:766-784

  • Severity: medium (specialist rated high; multi-model downgraded — real divergence but not a guaranteed break for common projects)
  • Confidence: high
  • Domain: correctness, alternative-solution
  • Finding: csproj classification uses first-match regex on /, while the CLI uses XDocument XML parsing over all PropertyGroups — so VSC can accept/reject .csproj projects differently than the CLI.
  • Evidence: project-detection.ts:768/778 use content.match(...); ProjectDetectionService.cs:265-285 uses XDocument.Load + Descendants(). Comments/conditional/multiple PropertyGroups produce mismatches.
  • Recommendation: Parse csproj as XML and mirror the C# "all property groups, comments ignored, LocalName match" behavior (ideally via M6's shared detection).

L1 src/winapp-VSC/src/extension.ts:5

  • Severity: low
  • Confidence: high
  • Domain: alternative-solution
  • Finding: getProjectLabel is imported but never used in extension.ts (only its definition + tests reference it).
  • Recommendation: Remove the unused import (or use it for QuickPick labels).

L2 src/winapp-VSC/src/extension.ts:464-539

  • Severity: low
  • Confidence: high
  • Domain: alternative-solution
  • Finding: init rebuilds the same scan + project QuickPick that resolveProjectDirectory already implements (~60 duplicated lines).
  • Recommendation: Add options to resolveProjectDirectory (e.g. includeCurrentDirectoryFallback, confirmSingleProject) and have init reuse it.

L3 src/winapp-VSC/src/project-detection.ts:44-48

  • Severity: low
  • Confidence: high
  • Domain: test-coverage
  • Finding: The TS detector mirrors C# but nothing guards cross-language drift; each side has independent fixtures.
  • Recommendation: Add shared golden fixtures or a comparison test so TS and C# detection agree.

Coverage notes
packaging: Inspected package.json (v0.1.1), package-lock.json (mocha + new peer markers; npm ci ran clean),
.vscodeignore (excludes src/, out/, test TS, lockfile), tsconfig mocha types, package-vsc.ps1, and the
VSIX workflow path. npm run package/esbuild succeeded. No version-bump or lockfile-drift blocker found.
(Note: M-severity overlaps — the missing CI hookup for test:unit is reported under H1.)

Security:
- H2: Add escapePowerShellArg() to prevent command injection via folder names
- M3: Validate appDirectories entries stay within workspace (reject ../ and
  absolute paths)

Correctness:
- M7: Make detectProjectAt and all helpers fully async (fs/promises) so the
  extension host is never blocked during detection
- M4/L2: Refactor init command to reuse resolveProjectDirectory, eliminating
  ~60 lines of duplicated picker logic and honoring appDirectories setting

Cleanup:
- L1: Remove unused getProjectLabel/DetectedProject imports from extension.ts
- M1: Update README init description to reflect unified resolution behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

[VS Code] Support for Apps in Workspaces

3 participants