From 483547276d67f35b9959a468ea82c6218d42de1f Mon Sep 17 00:00:00 2001 From: futrime <35801754+futrime@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:24:32 +0800 Subject: [PATCH] feat: lip list --- Lip.Tests/LipConfigTests.cs | 2 +- Lip.Tests/LipListTests.cs | 280 ++++++++++++++++++ Lip.Tests/PackageLockTests.cs | 21 +- Lip.Tests/PackageManifestTests.cs | 48 +-- Lip.Tests/PackageSpecifierTests.cs | 2 +- Lip.Tests/PathManagerTests.cs | 2 +- Lip.Tests/SchemaViolationExceptionTests.cs | 2 +- Lip/Lip.Init.cs | 4 +- Lip/Lip.List.cs | 12 +- Lip/Lip.cs | 11 +- Lip/PackageLock.cs | 6 +- Lip/PackageManifest.cs | 55 +++- Lip/PackageSpecifier.cs | 2 +- Lip/PathManager.cs | 2 +- ...e_config.schema.json => liprc.schema.json} | 0 docs/schemas/tooth_lock.v3.schema.json | 15 +- docs/user-guide/commands/lip-config.md | 2 +- .../{runtime-config-json.md => liprc-json.md} | 4 +- 18 files changed, 409 insertions(+), 61 deletions(-) create mode 100644 Lip.Tests/LipListTests.cs rename docs/schemas/{runtime_config.schema.json => liprc.schema.json} (100%) rename docs/user-guide/files/{runtime-config-json.md => liprc-json.md} (83%) diff --git a/Lip.Tests/LipConfigTests.cs b/Lip.Tests/LipConfigTests.cs index 5682df6..b26f671 100644 --- a/Lip.Tests/LipConfigTests.cs +++ b/Lip.Tests/LipConfigTests.cs @@ -7,7 +7,7 @@ namespace Lip.Tests; public class LipConfigTests { private static readonly string s_runtimeConfigPath = Path.Join( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "runtime_config.json"); + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "liprc.json"); [Fact] public async Task ConfigDelete_SingleItem_ResetsToDefault() diff --git a/Lip.Tests/LipListTests.cs b/Lip.Tests/LipListTests.cs new file mode 100644 index 0000000..cfa4acb --- /dev/null +++ b/Lip.Tests/LipListTests.cs @@ -0,0 +1,280 @@ +using System.IO.Abstractions.TestingHelpers; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; +using Moq; + +namespace Lip.Tests; + +public class LipListTests +{ + [Fact] + public async Task List_ReturnsListItems() + { + RuntimeConfig initialRuntimeConfig = new(); + + // Arrange. + var fileSystem = new MockFileSystem(new Dictionary + { + { "tooth_lock.json", new MockFileData($$""" + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "packages": [ + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "tooth": "example.com/pkg1", + "version": "1.0.0", + "variants": [ + { + "label": "variant1", + "platform": "{{RuntimeInformation.RuntimeIdentifier}}" + } + ] + }, + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "tooth": "example.com/pkg2", + "version": "1.0.1", + "variants": [ + { + "label": "variant2", + "platform": "{{RuntimeInformation.RuntimeIdentifier}}" + } + ] + } + ], + "locks": [ + { + "tooth": "example.com/pkg1", + "variant": "variant1", + "version": "1.0.0", + } + ] + } + """) } + }); + + Mock logger = new(); + + Mock pathManager = new(); + pathManager.SetupGet(m => m.PackageLockPath).Returns("tooth_lock.json"); + + Mock userInteraction = new(); + + Lip lip = new( + initialRuntimeConfig, + fileSystem, + logger.Object, + pathManager.Object, + userInteraction.Object, + runtimeIdentifier: RuntimeInformation.RuntimeIdentifier); + + // Act. + List listItems = await lip.List(new()); + + // Assert. + Assert.Equal(2, listItems.Count); + Assert.Equal("example.com/pkg1", listItems[0].Manifest.ToothPath); + Assert.Equal("1.0.0", listItems[0].Manifest.VersionText.ToString()); + Assert.Equal("variant1", listItems[0].Manifest.Variants![0].VariantLabel); + Assert.True(listItems[0].Locked); + Assert.Equal("example.com/pkg2", listItems[1].Manifest.ToothPath); + Assert.Equal("1.0.1", listItems[1].Manifest.VersionText.ToString()); + Assert.Equal("variant2", listItems[1].Manifest.Variants![0].VariantLabel); + Assert.False(listItems[1].Locked); + } + + [Fact] + public async Task List_LockFileNotExists_ReturnsEmptyList() + { + RuntimeConfig initialRuntimeConfig = new(); + + // Arrange. + var fileSystem = new MockFileSystem(); + + Mock logger = new(); + + Mock pathManager = new(); + pathManager.SetupGet(m => m.PackageLockPath).Returns("tooth_lock.json"); + + Mock userInteraction = new(); + + Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, pathManager.Object, userInteraction.Object); + + // Act. + List listItems = await lip.List(new()); + + // Assert. + Assert.Empty(listItems); + } + + [Fact] + public async Task List_MismatchedToothPath_ReturnsListItems() + { + RuntimeConfig initialRuntimeConfig = new(); + + // Arrange. + var fileSystem = new MockFileSystem(new Dictionary + { + { "tooth_lock.json", new MockFileData(""" + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "packages": [ + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "tooth": "example.com/pkg1", + "version": "1.0.0", + "variants": [ + { + "label": "variant1" + } + ] + } + ], + "locks": [ + { + "tooth": "example.com/pkg2", + "variant": "variant1", + "version": "1.0.0", + } + ] + } + """) } + }); + + Mock logger = new(); + + Mock pathManager = new(); + pathManager.SetupGet(m => m.PackageLockPath).Returns("tooth_lock.json"); + + Mock userInteraction = new(); + + Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, pathManager.Object, userInteraction.Object); + + // Act. + List listItems = await lip.List(new()); + + // Assert. + Assert.Single(listItems); + Assert.Equal("example.com/pkg1", listItems[0].Manifest.ToothPath); + Assert.Equal("1.0.0", listItems[0].Manifest.Version.ToString()); + Assert.Equal("variant1", listItems[0].Manifest.Variants![0].VariantLabel); + Assert.False(listItems[0].Locked); + } + + [Fact] + public async Task List_MismatchedVersion_ReturnsListItems() + { + RuntimeConfig initialRuntimeConfig = new(); + + // Arrange. + var fileSystem = new MockFileSystem(new Dictionary + { + { "tooth_lock.json", new MockFileData(""" + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "packages": [ + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "tooth": "example.com/pkg1", + "version": "1.0.0", + "variants": [ + { + "label": "variant1" + } + ] + } + ], + "locks": [ + { + "tooth": "example.com/pkg1", + "variant": "variant1", + "version": "1.0.1", + } + ] + } + """) } + }); + + Mock logger = new(); + + Mock pathManager = new(); + pathManager.SetupGet(m => m.PackageLockPath).Returns("tooth_lock.json"); + + Mock userInteraction = new(); + + Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, pathManager.Object, userInteraction.Object); + + // Act. + List listItems = await lip.List(new()); + + // Assert. + Assert.Single(listItems); + Assert.Equal("example.com/pkg1", listItems[0].Manifest.ToothPath); + Assert.Equal("1.0.0", listItems[0].Manifest.Version.ToString()); + Assert.Equal("variant1", listItems[0].Manifest.Variants![0].VariantLabel); + Assert.False(listItems[0].Locked); + } + + [Fact] + public async Task List_MismatchedVariantLabel_ReturnsListItems() + { + RuntimeConfig initialRuntimeConfig = new(); + + // Arrange. + var fileSystem = new MockFileSystem(new Dictionary + { + { "tooth_lock.json", new MockFileData(""" + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "packages": [ + { + "format_version": 3, + "format_uuid": "289f771f-2c9a-4d73-9f3f-8492495a924d", + "tooth": "example.com/pkg1", + "version": "1.0.0", + "variants": [ + { + "label": "variant1" + } + ] + } + ], + "locks": [ + { + "tooth": "example.com/pkg1", + "variant": "variant2", + "version": "1.0.0", + } + ] + } + """) } + }); + + Mock logger = new(); + + Mock pathManager = new(); + pathManager.SetupGet(m => m.PackageLockPath).Returns("tooth_lock.json"); + + Mock userInteraction = new(); + + Lip lip = new(initialRuntimeConfig, fileSystem, logger.Object, pathManager.Object, userInteraction.Object); + + // Act. + List listItems = await lip.List(new()); + + // Assert. + Assert.Single(listItems); + Assert.Equal("example.com/pkg1", listItems[0].Manifest.ToothPath); + Assert.Equal("1.0.0", listItems[0].Manifest.Version.ToString()); + Assert.Equal("variant1", listItems[0].Manifest.Variants![0].VariantLabel); + Assert.False(listItems[0].Locked); + } +} diff --git a/Lip.Tests/PackageLockTests.cs b/Lip.Tests/PackageLockTests.cs index 56731ff..0f25388 100644 --- a/Lip.Tests/PackageLockTests.cs +++ b/Lip.Tests/PackageLockTests.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using Semver; namespace Lip.Tests; @@ -63,7 +64,13 @@ public void FromBytes_MaximumJson_Passes() Assert.Equal(3, lockFile.FormatVersion); Assert.Equal("289f771f-2c9a-4d73-9f3f-8492495a924d", lockFile.FormatUuid); Assert.Single(lockFile.Packages); + Assert.Equal("example.com/pkg", lockFile.Packages[0].ToothPath); + Assert.Equal("1.0.0", lockFile.Packages[0].VersionText); Assert.Single(lockFile.Locks); + Assert.Equal("example.com/pkg", lockFile.Locks[0].ToothPath); + Assert.Equal("default", lockFile.Locks[0].VariantLabel); + Assert.Equal("1.0.0", lockFile.Locks[0].VersionText); + Assert.Equal(SemVersion.Parse("1.0.0"), lockFile.Locks[0].Version); } [Fact] @@ -161,14 +168,14 @@ public void ToBytes_MaximumJson_Passes() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "example.com/pkg", - Version = "1.0.0" + VersionText = "1.0.0" } ], Locks = [ new() { ToothPath = "example.com/pkg", VariantLabel = "default", - Version = "1.0.0" + VersionText = "1.0.0" } ] }; @@ -208,13 +215,13 @@ public void LockType_Constructor_ValidValues_Passes() { ToothPath = "example.com/package", VariantLabel = "default", - Version = "1.0.0" + VersionText = "1.0.0" }; // Assert Assert.Equal("example.com/package", lockType.ToothPath); Assert.Equal("default", lockType.VariantLabel); - Assert.Equal("1.0.0", lockType.Version); + Assert.Equal("1.0.0", lockType.VersionText); } [Fact] @@ -225,7 +232,7 @@ public void LockType_Constructor_InvalidToothPath_Throws() { ToothPath = "invalid/tooth", VariantLabel = "default", - Version = "1.0.0" + VersionText = "1.0.0" }); Assert.Equal("Invalid tooth path 'invalid/tooth'.", exception.Message); } @@ -238,7 +245,7 @@ public void LockType_Constructor_InvalidVariantLabel_Throws() { ToothPath = "example.com/package", VariantLabel = "invalid-variant", - Version = "1.0.0" + VersionText = "1.0.0" }); Assert.Equal("Invalid variant label 'invalid-variant'.", exception.Message); } @@ -251,7 +258,7 @@ public void LockType_Constructor_InvalidVersion_Throws() { ToothPath = "example.com/package", VariantLabel = "default", - Version = "invalid-version" + VersionText = "invalid-version" }); Assert.Equal("Invalid version 'invalid-version'.", exception.Message); } diff --git a/Lip.Tests/PackageManifestTests.cs b/Lip.Tests/PackageManifestTests.cs index eca4213..cfacbfb 100644 --- a/Lip.Tests/PackageManifestTests.cs +++ b/Lip.Tests/PackageManifestTests.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using Semver; namespace Lip.Tests; @@ -19,14 +20,14 @@ public void AssetType_Constructor_ValidValues_Passes( Type = PackageManifest.AssetType.TypeEnum.Self, Urls = urls?.ToList(), Place = null, - Preserve = preserve?.ToList(), + Preserve = preserve?.ToList(), Remove = remove?.ToList() }; // Assert. Assert.Equal(PackageManifest.AssetType.TypeEnum.Self, asset.Type); Assert.Equal(urls, asset.Urls); - Assert.Null(asset.Place); + Assert.Null(asset.Place); Assert.Equal(preserve, asset.Preserve); Assert.Equal(remove, asset.Remove); } @@ -46,7 +47,7 @@ public void AssetType_Constructor_InvalidUrl_Throws() Assert.Equal("urls", exception.Key); Assert.Equal("URL 'invalid' is invalid.", exception.Message); } - + [Fact] public void AssetType_Constructor_UnsafePreserve_Throws() { @@ -254,8 +255,8 @@ public void ScriptsType_Deserialize_InvalidAdditionalProperties_Ignores(string j public void VariantType_Constructor_ValidValues_Passes(int? dependencyIndex) { // Arrange & Act. - Dictionary? dependencies = dependencyIndex.HasValue ? - s_testDependencies.Skip(dependencyIndex.Value).Take(1).ToDictionary(x => x.Item1, x => x.Item2) : + Dictionary? dependencies = dependencyIndex.HasValue ? + s_testDependencies.Skip(dependencyIndex.Value).Take(1).ToDictionary(x => x.Item1, x => x.Item2) : null; var variant = new PackageManifest.VariantType @@ -317,7 +318,8 @@ public void FromBytes_MinimumJson_Passes() Assert.Equal(3, manifest.FormatVersion); Assert.Equal("289f771f-2c9a-4d73-9f3f-8492495a924d", manifest.FormatUuid); Assert.Equal("", manifest.ToothPath); - Assert.Equal("1.0.0", manifest.Version); + Assert.Equal("1.0.0", manifest.VersionText); + Assert.Equal(SemVersion.Parse("1.0.0"), manifest.Version); } [Fact] @@ -413,7 +415,7 @@ public void GetSpecifiedVariant_NullVariants_ReturnsNull() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0" + VersionText = "1.0.0" }; string variantLabel = ""; string platform = "platform"; @@ -434,7 +436,7 @@ public void GetSpecifiedVariant_EmptyVariants_ReturnsNull() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [] }; string variantLabel = ""; @@ -460,12 +462,12 @@ public void GetSpecifiedVariant_SingleVariant_ReturnsVariant( FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new PackageManifest.VariantType { - VariantLabel = manifestVariantLabel, + VariantLabelRaw = manifestVariantLabel, Platform = manifestPlatform, } ] @@ -476,6 +478,7 @@ public void GetSpecifiedVariant_SingleVariant_ReturnsVariant( // Assert. Assert.NotNull(variant); + Assert.Equal(variantLabel, variant.VariantLabelRaw); Assert.Equal(variantLabel, variant.VariantLabel); Assert.Equal(platform, variant.Platform); Assert.NotNull(variant.Dependencies); @@ -503,7 +506,7 @@ public void GetSpecifiedVariant_SingleFullVariant_ReturnsVariant() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new PackageManifest.VariantType @@ -521,6 +524,7 @@ public void GetSpecifiedVariant_SingleFullVariant_ReturnsVariant() // Assert. Assert.NotNull(variant); + Assert.Equal("", variant.VariantLabelRaw); Assert.Equal("", variant.VariantLabel); Assert.Equal("platform", variant.Platform); Assert.NotNull(variant.Dependencies); @@ -548,7 +552,7 @@ public void GetSpecifiedVariant_SingleVariantWithScripts_ReturnsVariant() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new PackageManifest.VariantType @@ -574,6 +578,7 @@ public void GetSpecifiedVariant_SingleVariantWithScripts_ReturnsVariant() // Assert. Assert.NotNull(variant); + Assert.Equal("", variant.VariantLabelRaw); Assert.Equal("", variant.VariantLabel); Assert.Equal("platform", variant.Platform); Assert.NotNull(variant.Dependencies); @@ -606,12 +611,12 @@ public void GetSpecifiedVariant_WildcardOnlySingleVariant_ReturnsNull( FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new PackageManifest.VariantType { - VariantLabel = manifestVariantLabel, + VariantLabelRaw = manifestVariantLabel, Platform = manifestPlatform, } ] @@ -637,12 +642,12 @@ public void GetSpecifiedVariant_MismatchedSingleVariant_ReturnsNull( FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new PackageManifest.VariantType { - VariantLabel = manifestVariantLabel, + VariantLabelRaw = manifestVariantLabel, Platform = manifestPlatform, } ] @@ -664,7 +669,7 @@ public void GetSpecifiedVariant_MultipleVariants_ReturnsMergedVariant() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new() @@ -739,6 +744,7 @@ public void GetSpecifiedVariant_MultipleVariants_ReturnsMergedVariant() // Assert. Assert.NotNull(variant); + Assert.Equal("", variant.VariantLabelRaw); Assert.Equal("", variant.VariantLabel); Assert.Equal("platform", variant.Platform); Assert.NotNull(variant.Dependencies); @@ -776,7 +782,7 @@ public void ToBytes_MinimumJson_Passes() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0" + VersionText = "1.0.0" }; // Act. @@ -802,7 +808,7 @@ public void WithTemplateParsed_CommonInput_Passes() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new(){ Assets = [ @@ -822,7 +828,7 @@ public void WithTemplateParsed_CommonInput_Passes() Assert.Equal(3, result.FormatVersion); Assert.Equal("289f771f-2c9a-4d73-9f3f-8492495a924d", result.FormatUuid); Assert.Equal("", result.ToothPath); - Assert.Equal("1.0.0", result.Version); + Assert.Equal("1.0.0", result.VersionText); Assert.NotNull(result.Variants); Assert.Single(result.Variants); Assert.NotNull(result.Variants[0].Assets); @@ -839,7 +845,7 @@ public void WithTemplateParsed_InvalidTemplate_Throws() FormatVersion = 3, FormatUuid = "289f771f-2c9a-4d73-9f3f-8492495a924d", ToothPath = "", - Version = "1.0.0", + VersionText = "1.0.0", Variants = [ new(){ Assets = [ diff --git a/Lip.Tests/PackageSpecifierTests.cs b/Lip.Tests/PackageSpecifierTests.cs index a800aa2..d26d59f 100644 --- a/Lip.Tests/PackageSpecifierTests.cs +++ b/Lip.Tests/PackageSpecifierTests.cs @@ -1,4 +1,4 @@ -using Semver; +using Semver; namespace Lip.Tests; diff --git a/Lip.Tests/PathManagerTests.cs b/Lip.Tests/PathManagerTests.cs index 315fa83..8f89bad 100644 --- a/Lip.Tests/PathManagerTests.cs +++ b/Lip.Tests/PathManagerTests.cs @@ -146,7 +146,7 @@ public void GetRuntimeConfigPath_WhenCalled_ReturnsCorrectPath() // Assert. Assert.Equal( - Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "runtime_config.json"), + Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "liprc.json"), runtimeConfigPath); } diff --git a/Lip.Tests/SchemaViolationExceptionTests.cs b/Lip.Tests/SchemaViolationExceptionTests.cs index 040cc4b..7c91c25 100644 --- a/Lip.Tests/SchemaViolationExceptionTests.cs +++ b/Lip.Tests/SchemaViolationExceptionTests.cs @@ -1,4 +1,4 @@ -namespace Lip.Tests; +namespace Lip.Tests; public class SchemaViolationExceptionTests { diff --git a/Lip/Lip.Init.cs b/Lip/Lip.Init.cs index bf050c8..e7cb90d 100644 --- a/Lip/Lip.Init.cs +++ b/Lip/Lip.Init.cs @@ -31,7 +31,7 @@ public async Task Init(InitArgs args) FormatVersion = PackageManifest.DefaultFormatVersion, FormatUuid = PackageManifest.DefaultFormatUuid, ToothPath = args.InitTooth ?? DefaultTooth, - Version = args.InitVersion ?? DefaultVersion, + VersionText = args.InitVersion ?? DefaultVersion, Info = new() { Name = args.InitName, @@ -55,7 +55,7 @@ public async Task Init(InitArgs args) FormatVersion = PackageManifest.DefaultFormatVersion, FormatUuid = PackageManifest.DefaultFormatUuid, ToothPath = tooth, - Version = version, + VersionText = version, Info = new() { Name = name, diff --git a/Lip/Lip.List.cs b/Lip/Lip.List.cs index 2135cc6..192db9a 100644 --- a/Lip/Lip.List.cs +++ b/Lip/Lip.List.cs @@ -1,4 +1,6 @@ -namespace Lip; +using System.Runtime.InteropServices; + +namespace Lip; public partial class Lip { @@ -14,7 +16,7 @@ public async Task> List(ListArgs args) { PackageLock packageLock = await GetPackageLock(); - List listItems = packageLock.Packages + List listItems = [.. packageLock.Packages .Select(package => new ListItem { Manifest = package, @@ -22,9 +24,11 @@ public async Task> List(ListArgs args) { return package.ToothPath == l.ToothPath && package.Version == l.Version - && package.Variants?.FirstOrDefault()?.VariantLabel == l.VariantLabel; + && package.GetSpecifiedVariant( + l.VariantLabel, + _runtimeIdentifier) is not null; }) - }).ToList(); + })]; return listItems; } diff --git a/Lip/Lip.cs b/Lip/Lip.cs index 97c4c8f..1f2c71b 100644 --- a/Lip/Lip.cs +++ b/Lip/Lip.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; namespace Lip; @@ -11,11 +12,19 @@ namespace Lip; /// The logger. /// The path manager. /// The user interaction wrapper. -public partial class Lip(RuntimeConfig runtimeConfig, IFileSystem fileSystem, ILogger logger, IPathManager pathManager, IUserInteraction userInteraction) +/// The simulated runtime identifier. If not provided, the actual runtime identifier is used. +public partial class Lip( + RuntimeConfig runtimeConfig, + IFileSystem fileSystem, + ILogger logger, + IPathManager pathManager, + IUserInteraction userInteraction, + string? runtimeIdentifier = null) { private readonly IFileSystem _fileSystem = fileSystem; private readonly ILogger _logger = logger; private readonly IPathManager _pathManager = pathManager; private readonly RuntimeConfig _runtimeConfig = runtimeConfig; + private readonly string _runtimeIdentifier = runtimeIdentifier ?? RuntimeInformation.RuntimeIdentifier; private readonly IUserInteraction _userInteraction = userInteraction; } diff --git a/Lip/PackageLock.cs b/Lip/PackageLock.cs index 39977f0..1078e88 100644 --- a/Lip/PackageLock.cs +++ b/Lip/PackageLock.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Semver; namespace Lip; @@ -25,8 +26,11 @@ public string VariantLabel : throw new SchemaViolationException("variant", $"Invalid variant label '{value}'."); } + [JsonIgnore] + public SemVersion Version => SemVersion.Parse(VersionText); + [JsonPropertyName("version")] - public string Version + public string VersionText { get => _version; init => _version = StringValidator.CheckVersion(value) diff --git a/Lip/PackageManifest.cs b/Lip/PackageManifest.cs index 57bf66a..d926acd 100644 --- a/Lip/PackageManifest.cs +++ b/Lip/PackageManifest.cs @@ -4,6 +4,7 @@ using DotNet.Globbing; using Scriban; using Scriban.Parsing; +using Semver; namespace Lip; @@ -275,8 +276,11 @@ public Dictionary> AdditionalScripts public record VariantType { + [JsonIgnore] + public string VariantLabel => VariantLabelRaw ?? ""; + [JsonPropertyName("label")] - public string? VariantLabel { get; init; } + public string? VariantLabelRaw { get; init; } [JsonPropertyName("platform")] public string? Platform { get; init; } @@ -350,8 +354,11 @@ public required string FormatUuid [JsonPropertyName("tooth")] public required string ToothPath { get; init; } + [JsonIgnore] + public SemVersion Version => SemVersion.Parse(VersionText); + [JsonPropertyName("version")] - public required string Version + public required string VersionText { get { @@ -406,26 +413,46 @@ public static PackageManifest FromJsonBytes(byte[] bytes) List matchedVariants = Variants? .Where(variant => { - if (variant.VariantLabel is null || variant.VariantLabel == "") + // Check if the variant label matches the specified label. + bool isVariantLabelMatched = false; + + if (variant.VariantLabel == variantLabel) { - if ("" != variantLabel) + isVariantLabelMatched = true; + } + else if (variant.VariantLabel.Length > 0) + { + var labelGlob = Glob.Parse(variant.VariantLabel); + + if (labelGlob.IsMatch(variantLabel)) { - return false; + isVariantLabelMatched = true; } } - else + + if (!isVariantLabelMatched) + { + return false; + } + + // Check if the platform matches the specified platform. + bool isPlatformMatched = false; + + if (variant.Platform == platform) + { + isPlatformMatched = true; + } + else if (variant.Platform?.Length > 0 || variant.Platform is null) { - var labelGlob = Glob.Parse(variant.VariantLabel!); + var platformGlob = Glob.Parse(variant.Platform ?? "*"); - if (!labelGlob.IsMatch(variantLabel)) + if (platformGlob.IsMatch(platform)) { - return false; + isPlatformMatched = true; } } - var platformGlob = Glob.Parse(variant.Platform ?? "*"); - - if (!platformGlob.IsMatch(platform)) + if (!isPlatformMatched) { return false; } @@ -436,7 +463,7 @@ public static PackageManifest FromJsonBytes(byte[] bytes) // However, there must exist at least one variant that matches the specified label and platform without any wildcards. if (!matchedVariants.Any( - variant => (variant.VariantLabel == variantLabel) || (variant.VariantLabel == null && variantLabel == ""))) + variant => variant.VariantLabel == variantLabel)) { return null; } @@ -449,7 +476,7 @@ public static PackageManifest FromJsonBytes(byte[] bytes) // Merge all matched variants into a single variant. VariantType mergedVariant = new() { - VariantLabel = variantLabel, + VariantLabelRaw = variantLabel, Platform = platform, Dependencies = matchedVariants .SelectMany(variant => variant.Dependencies ?? []) diff --git a/Lip/PackageSpecifier.cs b/Lip/PackageSpecifier.cs index 0176415..4f0d064 100644 --- a/Lip/PackageSpecifier.cs +++ b/Lip/PackageSpecifier.cs @@ -51,7 +51,7 @@ public static PackageSpecifierWithoutVersion Parse(string specifierText) } } -public record PackageSpecifier: PackageSpecifierWithoutVersion +public record PackageSpecifier : PackageSpecifierWithoutVersion { public required SemVersion Version { get; init; } diff --git a/Lip/PathManager.cs b/Lip/PathManager.cs index 5edce6d..a2f8efe 100644 --- a/Lip/PathManager.cs +++ b/Lip/PathManager.cs @@ -37,7 +37,7 @@ public class PathManager(IFileSystem fileSystem, string? baseCacheDir = null) : public string PackageLockPath => _fileSystem.Path.Join(WorkingDir, PackageLockFileName); public string RuntimeConfigPath => _fileSystem.Path.Join( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "runtime_config.json"); + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lip", "liprc.json"); public string WorkingDir => _fileSystem.Directory.GetCurrentDirectory(); diff --git a/docs/schemas/runtime_config.schema.json b/docs/schemas/liprc.schema.json similarity index 100% rename from docs/schemas/runtime_config.schema.json rename to docs/schemas/liprc.schema.json diff --git a/docs/schemas/tooth_lock.v3.schema.json b/docs/schemas/tooth_lock.v3.schema.json index 4bfe687..ab3f94a 100644 --- a/docs/schemas/tooth_lock.v3.schema.json +++ b/docs/schemas/tooth_lock.v3.schema.json @@ -31,8 +31,19 @@ "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" } - } + }, + "required": [ + "tooth", + "variant", + "version" + ] } } - } + }, + "required": [ + "format_version", + "format_uuid", + "packages", + "locks" + ] } diff --git a/docs/user-guide/commands/lip-config.md b/docs/user-guide/commands/lip-config.md index 6cf7f25..41e7ff0 100644 --- a/docs/user-guide/commands/lip-config.md +++ b/docs/user-guide/commands/lip-config.md @@ -13,7 +13,7 @@ lip config list Manage the lip configuration files. -lip stores configuration files at `%APPDATA%\lip\runtime_config.json` for Windows and `~/.config/lip/runtime_config.json` for POSIX-like systems. +lip stores configuration files at `%APPDATA%\lip\liprc.json` for Windows and `~/.config/lip/liprc.json` for POSIX-like systems. ## Sub-commands diff --git a/docs/user-guide/files/runtime-config-json.md b/docs/user-guide/files/liprc-json.md similarity index 83% rename from docs/user-guide/files/runtime-config-json.md rename to docs/user-guide/files/liprc-json.md index 1d9ea86..7406712 100644 --- a/docs/user-guide/files/runtime-config-json.md +++ b/docs/user-guide/files/liprc-json.md @@ -1,6 +1,6 @@ -# runtime_config.json +# liprc.json -The `runtime_config.json` file serves as the configuration file for lip, enabling you to configure settings such as caching, proxies, and script execution. lip stores this file at `%APPDATA%\lip\runtime_config.json` for Windows and `~/.config/lip/runtime_config.json` for POSIX-like systems. +The `liprc.json` file serves as the configuration file for lip, enabling you to configure settings such as caching, proxies, and script execution. lip stores this file at `%APPDATA%\lip\liprc.json` for Windows and `~/.config/lip/liprc.json` for POSIX-like systems. Project-specific settings take precedence over user-wide settings. All configuration fields are optional.