Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/Aspire.Cli/Agents/Playwright/PlaywrightCliInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,28 @@ internal sealed class PlaywrightCliInstaller(

// Step 1: Resolve the target version and integrity hash from the npm registry.
var versionOverride = configuration[VersionOverrideKey];
var effectiveRange = !string.IsNullOrEmpty(versionOverride) ? versionOverride : VersionRange;
string effectiveRange;

if (!string.IsNullOrEmpty(versionOverride))
{
// The override is forwarded directly to npm as an exact version specifier, so reject
// anything that is not a strict SemVer 2.0 version (e.g. ranges like ">=1.0.0", npm
// dist-tags like "latest", or arbitrary strings). This prevents a malformed config
// value from being interpreted by npm in unexpected ways and gives the user a clear
// error rather than a generic resolve failure.
// See https://semver.org/spec/v2.0.0.html for the accepted shape.
if (!SemVersion.TryParse(versionOverride, SemVersionStyles.Strict, out _))
{
return (PlaywrightInstallStatus.Failed, string.Format(CultureInfo.CurrentCulture, AgentCommandStrings.PlaywrightCliInstaller_InvalidVersionOverride, VersionOverrideKey, versionOverride));
}

effectiveRange = versionOverride;
logger.LogDebug("Using version override from '{ConfigKey}': {Version}", VersionOverrideKey, versionOverride);
}
else
{
effectiveRange = VersionRange;
}

logger.LogDebug("Resolving {Package}@{Range} from npm registry.", PackageName, effectiveRange);
var packageInfo = await npmRunner.ResolvePackageAsync(PackageName, effectiveRange, cancellationToken);
Expand Down
9 changes: 9 additions & 0 deletions src/Aspire.Cli/Resources/AgentCommandStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Aspire.Cli/Resources/AgentCommandStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
<data name="PlaywrightCliInstaller_FailedToResolvePackage" xml:space="preserve">
<value>Failed to resolve {0} from the npm registry.</value>
</data>
<data name="PlaywrightCliInstaller_InvalidVersionOverride" xml:space="preserve">
<value>The value of configuration setting '{0}' ('{1}') is not a valid SemVer 2.0 version.</value>
</data>
<data name="PlaywrightCliInstaller_ProvenanceVerificationFailed" xml:space="preserve">
<value>Provenance verification failed for {0}: {1}</value>
</data>
Expand Down
5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AgentCommandStrings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions tests/Aspire.Cli.Tests/Agents/PlaywrightCliInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,47 @@ public async Task InstallAsync_WhenVersionOverrideConfigured_UsesOverrideVersion
}
}

[Theory]
[InlineData(">=0.2.0")]
[InlineData("latest")]
[InlineData("0.2")]
[InlineData("not-a-version")]
[InlineData("v0.2.0")]
public async Task InstallAsync_WhenVersionOverrideIsNotStrictSemVer_ReturnsFailed(string invalidVersion)
{
var tempDir = CreateTestRepoRoot();

try
{
var npmRunner = new TestNpmRunner
{
ResolveResult = new NpmPackageInfo { Version = SemVersion.Parse("0.2.0", SemVersionStyles.Strict), Integrity = "sha512-abc123" }
};
var playwrightRunner = new TestPlaywrightCliRunner();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
[PlaywrightCliInstaller.VersionOverrideKey] = invalidVersion
})
.Build();
var installer = new PlaywrightCliInstaller(npmRunner, new TestNpmProvenanceChecker(), playwrightRunner, new TestInteractionService(), configuration, NullLogger<PlaywrightCliInstaller>.Instance);

var (status, message) = await installer.InstallAsync(tempDir, s_emptySkillDirs, CancellationToken.None);

Assert.Equal(PlaywrightInstallStatus.Failed, status);
Assert.NotNull(message);
Assert.Contains(invalidVersion, message);
Assert.Null(npmRunner.ResolvedVersionRange);
}
finally
{
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, recursive: true);
}
}
}

[Fact]
public async Task InstallAsync_WhenNoVersionOverride_UsesDefaultRange()
{
Expand Down
Loading