Skip to content

[release/13.2] Fix installing playwright-cli with aspire agent init on Windows#15559

Open
JamesNK wants to merge 3 commits intorelease/13.2from
backport/npm-sigstore-to-release-13.2
Open

[release/13.2] Fix installing playwright-cli with aspire agent init on Windows#15559
JamesNK wants to merge 3 commits intorelease/13.2from
backport/npm-sigstore-to-release-13.2

Conversation

@JamesNK
Copy link
Member

@JamesNK JamesNK commented Mar 25, 2026

Description

Backport NpmRunner, SigstoreNpmProvenanceChecker, and PlaywrightCliRunner improvements from mad-skills to release/13.2.

Changes

  • NpmRunner: Run npm commands in isolated temp directories to avoid picking up project .npmrc files; use --registry flag for public registry; improve Windows .cmd invocation via cmd.exe /c; add IsAvailable property; add NpmPackageInfo.FormatPackageSpecifier helper.
  • SigstoreNpmProvenanceChecker: Improve Sigstore SAN extraction with robust parsing; add SAN parsing tests; work around Sigstore SAN extraction bug on Windows.
  • PlaywrightCliRunner: Clean up empty .playwright directory left behind after playwright-cli install --skills.
  • Tests: Add NpmRunnerTests and additional SigstoreNpmProvenanceCheckerTests.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI review requested due to automatic review settings March 25, 2026 00:39
@github-actions
Copy link
Contributor

github-actions bot commented Mar 25, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15559

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15559"

@JamesNK JamesNK changed the title Backport NpmRunner and SigstoreNpmProvenanceChecker improvements to release/13.2 [release/13.2] Fix installing playwright-cli with aspire agent init on Windows Mar 25, 2026
@JamesNK JamesNK requested review from joperezr and mitchdenny March 25, 2026 00:43
@JamesNK JamesNK added area-cli Servicing-consider Issue for next servicing release review labels Mar 25, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Backports improvements to Aspire CLI’s npm/provenance/playwright tooling for the release/13.2 line, focusing on more reliable npm execution, stronger Sigstore SAN handling (including Windows quirks), and minor cleanup after Playwright skills install.

Changes:

  • Update NpmRunner to run in isolated temp directories, explicitly use the public npm registry via --registry, improve Windows .cmd invocation reliability, and add IsAvailable plus NpmPackageInfo.FormatPackageSpecifier.
  • Improve SigstoreNpmProvenanceChecker SAN extraction with robust parsing and a Windows workaround, plus additional debug logging.
  • Add/extend unit tests for npm start info creation and SAN parsing/extraction; clean up empty .playwright folders after install.

Reviewed changes

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

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Npm/NpmRunnerTests.cs Adds unit tests validating ProcessStartInfo creation across Windows/non-Windows.
tests/Aspire.Cli.Tests/Agents/SigstoreNpmProvenanceCheckerTests.cs Adds SAN parsing + portable SAN extraction tests using generated certs.
src/Aspire.Cli/Npm/SigstoreNpmProvenanceChecker.cs Adds SAN parsing helpers, Windows workaround path, and logging improvements.
src/Aspire.Cli/Npm/NpmRunner.cs Uses isolated temp dirs + public registry flag; adds Windows .cmd invocation via cmd.exe /c; exposes IsAvailable.
src/Aspire.Cli/Npm/INpmRunner.cs Adds INpmRunner.IsAvailable and NpmPackageInfo.FormatPackageSpecifier helpers.
src/Aspire.Cli/Agents/Playwright/PlaywrightCliRunner.cs Cleans up empty .playwright directory after install --skills.

/// <summary>
/// Gets a value indicating whether npm is available on the system PATH.
/// </summary>
bool IsAvailable { get; }
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

Adding the IsAvailable member to INpmRunner requires updating all other INpmRunner implementations (not just NpmRunner). In the current branch, at least some test fakes (e.g., FakeNpmRunner / TestNpmRunner) still only implement the original methods, which will cause compilation failures. Please either update those fakes to implement IsAvailable or provide a non-breaking alternative (e.g., an extension/helper) if you want to avoid touching all implementers.

Suggested change
bool IsAvailable { get; }
bool IsAvailable => true;

Copilot uses AI. Check for mistakes.
logger.LogDebug("Removed empty .playwright directory from {WorkingDirectory}", workingDirectory);
}
}
catch (IOException ex)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

CleanupEmptyPlaywrightDirectory is called from a finally block, but it only catches IOException. Other exceptions (e.g., UnauthorizedAccessException, DirectoryNotFoundException due to a race, or PathTooLongException) can escape the finally and cause InstallSkillsAsync to throw even when you intend to return false. Consider catching Exception (or at least the additional expected exception types) so cleanup best-effort failures never change the outcome of InstallSkillsAsync.

Suggested change
catch (IOException ex)
catch (Exception ex)

Copilot uses AI. Check for mistakes.
Comment on lines +324 to +330
// NOTE: When there is no SRI integrity digest and no DSSE envelope, this returns a generic
// VerificationResult with a failure reason string. The caller maps any verification failure
// to AttestationParseFailed, which differs from the previous behavior (PayloadDecodeFailed)
// that occurred when TryVerifyAsync was called with a null payload.
if (bundle.DsseEnvelope is null)
{
return (false, new VerificationResult { FailureReason = "No DSSE envelope found in bundle." });
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

VerifyBundleWithPolicyAsync now returns a generic verification failure when bundle.DsseEnvelope is null, and the caller maps all such failures to AttestationParseFailed (see the NOTE). This loses the more specific PayloadDecodeFailed outcome that the enum already defines and that the previous implementation produced for this case. Consider preserving the prior behavior by explicitly returning/mapping to PayloadDecodeFailed when there is no SRI digest and the DSSE envelope is missing.

Suggested change
// NOTE: When there is no SRI integrity digest and no DSSE envelope, this returns a generic
// VerificationResult with a failure reason string. The caller maps any verification failure
// to AttestationParseFailed, which differs from the previous behavior (PayloadDecodeFailed)
// that occurred when TryVerifyAsync was called with a null payload.
if (bundle.DsseEnvelope is null)
{
return (false, new VerificationResult { FailureReason = "No DSSE envelope found in bundle." });
// When there is no SRI integrity digest and no DSSE envelope, delegate to TryVerifyAsync
// with a null payload to preserve the previous behavior where the Sigstore library
// returns a PayloadDecodeFailed outcome instead of a generic verification failure.
if (bundle.DsseEnvelope is null)
{
return await verifier.TryVerifyAsync(
payload: null,
bundle: bundle,
policy: policy,
cancellationToken: cancellationToken).ConfigureAwait(false);

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 51 recordings uploaded (commit 4bf2cdb)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23519775170

if (OperatingSystem.IsWindows() && npmPath.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase))
{
startInfo.FileName = "cmd.exe";
startInfo.Arguments = @$"/c """"{npmPath}"" {string.Join(" ", args.Select(a => @$"""{a}"""))}""";
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to escape things here?

@joperezr joperezr added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-cli Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants