Compare commits

...

4 commits

Author SHA1 Message Date
D. Moonfire 72582fb703 fix: while creating calendars, DefaultGetInstant returns null if not found
All checks were successful
deploy / deploy (push) Successful in 18m15s
2024-06-02 10:37:45 -05:00
D. Moonfire 2fb4bc7bb5 feat: added ICS calendar creation
All checks were successful
deploy / deploy (push) Successful in 10m21s
2024-06-01 20:37:20 -05:00
D. Moonfire 63b59b101e docs: added news post
All checks were successful
deploy / deploy (push) Successful in 10m56s
2024-04-21 14:54:30 -05:00
D. Moonfire a7226db9e9 feat: renamed MfGames.Crypto to MfGames.Cryptography 2024-04-21 12:58:51 -05:00
44 changed files with 606 additions and 60 deletions

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
*.verified.txt text eol=lf working-tree-encoding=UTF-8
*.verified.xml text eol=lf working-tree-encoding=UTF-8
*.verified.json text eol=lf working-tree-encoding=UTF-8

1
.gitignore vendored
View file

@ -21,6 +21,7 @@ artifacts/
coverage
TestResults/
tests/artifacts/
*.received.*
# nixago: ignore-linked-files
/.prettierrc.json

View file

@ -95,7 +95,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.Exec.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Cryptography", "src\MfGames.Cryptography\MfGames.Cryptography.csproj", "{832DBBC9-3A53-4F6B-B98C-4059DF780124}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Crypto.Tests", "tests\MfGames.Crypto.Tests\MfGames.Crypto.Tests.csproj", "{7A773B2F-ADF4-450F-8F43-D3C3C689E1CE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Cryptography.Tests", "tests\MfGames.Cryptography.Tests\MfGames.Cryptography.Tests.csproj", "{7A773B2F-ADF4-450F-8F43-D3C3C689E1CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MfGames.Nitride.SpectreConsole", "src\MfGames.Nitride.SpectreConsole\MfGames.Nitride.SpectreConsole.csproj", "{0C5C8D0A-D283-477F-82C4-2FF026F6EC0C}"
EndProject

View file

@ -577,6 +577,21 @@ II.2.12 <HandlesEvent />
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=2c62818f_002D621b_002D4425_002Dadc9_002D78611099bfcb/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"&gt;&lt;ElementKinds&gt;&lt;Kind Name="TYPE_PARAMETER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=61a991a4_002Dd0a3_002D4d19_002D90a5_002Df8f4d75c30c1/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local variables"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_VARIABLE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=669e5282_002Dfb4b_002D4e90_002D91e7_002D07d269d04b60/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=8a85b61a_002D1024_002D4f87_002Db9ef_002D1fdae19930a1/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PARAMETER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=8b8504e3_002Df0be_002D4c14_002D9103_002Dc732f2bddc15/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ENUM_MEMBER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a7a3339e_002D4e89_002D4319_002D9735_002Da9dc4cb74cc7/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Interfaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="INTERFACE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=c873eafb_002Dd57f_002D481d_002D8c93_002D77f6863c2f88/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
@ -675,6 +690,7 @@ II.2.12 &lt;HandlesEvent /&gt;&#xD;
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EFormat_002ESettingsUpgrade_002EAlignmentTabFillStyleMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsCodeFormatterSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>

View file

@ -6,7 +6,7 @@ This [monorepo](https://en.wikipedia.org/wiki/Monorepo) is the collection of all
The documentation for the entire project can be found in the [./docs/ folder](./docs/index.md) of this project, but below are some notable links are:
- [MfGames.Gallium](./docs/gallium/index.md) - A toy Entity-Component-System (ECS) modeled after LINQ methods calls and suitable for environment where ease of use is more critical than performance.
- [MfGames.Gallium](./docs/gallium/index.md) - A simplified Entity-Component-System (ECS) modeled after LINQ methods calls and suitable for environment where ease of use is more critical than performance.
- [MfGames.Nitride](./docs/nitride/index.md) - A flexible, configuration-as-code, static site generator build on top of MfGames.Gallium.
## Installing Packages

View file

@ -1,3 +1,7 @@
# MfGames' Nitride
# MfGames's Nitride
Nitride is a C# static site generator built on a simple and designed around the concept of concurrent pipelines to produce the results. It is flexible in how it generates it files but does not come as a "batteries included" package.
## Modules
- [Temporal](./temporal/index.md)

View file

@ -0,0 +1,60 @@
# Creating an iCalendar File
There are many cases when being able to generate a calender from one or more entities inside Nitride. It can be used to give an overview of past and future pages for posts, to ensure a full buffer of content to handle a vacation, or create a schedule from some metadata.
The iCalendar parsing and serialization comes from [Ical.net](https://github.com/rianjs/ical.net).
## Pipeline Setup
Most of the configuration for the operation comes with setup, using the variety of `With*` commands to set the value.
```csharp
public class SitePipeline : PipelineBase
{
private CreateCalendar createCalendear;
public SitePipeline(CreateCalendar createCalendear)
{
this.createCalendar = createCalendar
.WithCreateEvent(
entity => new CalendarEvent
{
Summary = entity.Get<string>(),
})
.WithCreateCalendarEntity(
(_calendar, text) => new Entity()
.Set(new UPath("/calendar.ics"))
.SetTextContent(text));
}
}
```
There are two required properties that must be set (either by setting the property or using the `With*` method):
- `CreateEvent` takes an entity and create a calendar entry. If this returns null, then no entry will be added. It is recommended setting the `Summary` property at minimum. If a stable UID is wanted, then it also must be set here. The start, stop, and stamp do not have to be set as they will be after the object is returned.
- `CreateCalendarEntity` is used to create the calendar file itself. If using path-based operations, it requires setting a `UPath` component but may include additional information.
There are some additional optional properties:
- `GetInstant`: The callback method to get the `NodaTime.Instant` from the entity. This defaults to `entity.GetOptional<Instant>` but could be overridden as needed.
- `PostCreateEvent`: A final callback called for each event after `CreateEvent` is called, the date times are set for start and stop, and the event is about to be added to the calendar.
## Adding the Calendar
Calling the operation simply adds the calendar to the output stream.
```csharp
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default
)
{
return entities
.Run(this.createCalendar, cancellationToken)
.ToAsyncEnumerable();
// The last item in the entities list has a UPath of
// "/calendar.ics".
}
```

View file

@ -0,0 +1,53 @@
# MfGames.Nitride.Temporal
The package `MfGames.Nitride.Temporal` extends Nitride with time- and date-centric operations, extensions, and processes. It allows a site to be hide future posts, create a calendar of entries, or create a nested set of pages based on year, month, and day.
```sh
dotnet add package MfGames.Nitride.Temporal
```
This also has an extension method to include it into the builder.
```csharp
public static async Task<int> Main(string[] args)
{
NitrideBuilder builder = new NitrideBuilder(args)
.UseTemporal(
config =>
{
config
.WithDateTimeZone("America/Chicago")
.WithDateOptionCommandLineOption();
});
return await builder.RunAsync();
}
```
## NodaTime and Instants
This library is built on top of [NodaTime](https://nodatime.org/), provides good time zone support, and had date-only objects before they were available in the base library.
In most cases, adding an `Instant` to an entity is sufficient to use most of the temporal functionality.
```csharp
Instant instant;
var entityList = new Entity[]
{
new Entity(instant),
};
var op = context.Resolve<FilterOutFutureInstant>();
var filteredList = entityList.Run(op).ToList();
```
## Options
When the `WithDateOptionCommandLineOption()` is included in the module setup, it injects a `--date` option into the builders that allows a different date to be used for the "now" date which can be retrieved from the injectable `TimeService` as `CurrentInstant` and `CurrentDateTime`.
## Examples
- [Create an iCalendar File](./creating-an-icalendar-file.md)
- [Removing Future Entities](./removing-future-entities.md)

View file

@ -0,0 +1,60 @@
# Removing Future Entities
When trying to establish a cadence of posting, a buffer is useful to have to ensure there are enough posts when life throws a curve ball. As a static site generator, Nitride doesn't have automatic publishing but it can control which entities are available based on the `NodeTime.Instant` component of that entity by removing ones that happen in the future.
## Extensions
When working with removing future, it is helpful to be able to regenerate the site with a future date to see how it looks. This required modifying the module setup (`UseTemporal`) in the builder and including the `WithDateOptionCommandLineOption()` call.
```csharp
public static async Task<int> Main(string[] args)
{
NitrideBuilder builder = new NitrideBuilder(args)
.UseTemporal(
config =>
{
config
.WithDateTimeZone("America/Chicago")
.WithDateOptionCommandLineOption();
});
return await builder.RunAsync();
}
```
This provides a `--date` option which defaults to the current moment, but can be used to change the date into the past or future.
## Setup
The setup is fairly simple because `FilterOutFutureInstant` doesn't have any options.
```csharp
using MfGames.Nitride.Temporal;
public class SitePipeline : PipelineBase
{
private FilterOutFutureInstant filterOutFutureInstant;
public SitePipeline(FilterOutFutureInstant filterOutFutureInstant)
{
this.filterOutFutureInstant = filterOutFutureInstant;
}
}
```
## Operations
Likewise, since there aren't any options for this, it just has to be added into the pipeline's processing. This assumes that entities have a `NodeTime.Instant` component in them; if they don't, then they are ignored.
```csharp
/// <inheritdoc />
public override IAsyncEnumerable<Entity> RunAsync(
IEnumerable<Entity> entities,
CancellationToken cancellationToken = default
)
{
return entities
.Run(this.filterOutFutureInstant, cancellationToken)
.ToAsyncEnumerable();
}
```

View file

@ -0,0 +1,9 @@
# Online Documentation
When we started Nitride in May 2021, we always intended to convert all our websites to use the new system. Admitedly, we started with the most complex sites because that would iron out the more complex problems we were trying to solve with Nitride. And there were some rather significant patterns to iron out over the years.
But [mfgames.com](/) was one of those lynchpins for the entire migration because if a program or library isn't documented, it can't be ever be done. And we had examples and patterns, but no good place to point someone who might be interested in using Nitride with something narrative instead of "look at the tests" or "look at these examples."
Now we have it. Adding documentation to our `./docs/` folder will automatically show up on the [website](//mfgames.com/mfgames-cil/). That will also open up the ability to write tutorials on how to use the library, news of what is going on that can be subscribed with feed readers (and maybe a mailing list), and generally those little polish things needed to call something "done."
Here is to the next big step.

View file

@ -0,0 +1,7 @@
# Renaming MfGames.Crypto to MfGames.Cryptography
For many years, "crypto" really meant only one thing in the programming world: cryptozoology. No, actually "cryptography." However, with the advent of web3 and cryptocurrencies, the world "crypto" is becoming muddled and no-longer obvious of its purpose.
Recently, [BouncyCastle](https://www.bouncycastle.org/) has gone through a little refactoring themselves after starting with `BouncyCastle` then renaming themselves to `Portable.BouncyCastle` and finally `BouncyCastle.Cryptography`. Overally, we like the final name and it was inspiration for us doing the same.
So, it isn't really marked as a "breaking" change, but `MfGames.Crypto` is being renamed to `MfGames.Cryptography`. And, to avoid the struggle we had with BouncyCastle, the namespace also was changed to reflect that change.

View file

@ -0,0 +1,52 @@
# Create iCalendar Files with Nitride
One of the more useful features in previous versions of [Dylan's](//d.moonfire.us) static site generators was the ability to create an iCalendar file of the posts, both past and future. It wasn't only just to see little entries pop up for the dopamine rush, but also to give them a head's up when a new chapter for [Fedran](//fedran.com) needs to be written or its been a few months since a blog post has been written.
We use iCalendar because it's a solid standard format that can easily be added into Nextcloud, put on a phone, or otherwise used anywhere to keep it in mind.
(Technicaly, it could also be used to create task lists for sites that are missing links or other things but out of scope for this iteration).
We put this into the `MfGames.Nitride.Temporal` even though it adds an extra dependency ([Ical.Net](https://github.com/rianjs/ical.net)) because it is a relatively small feature and it hit that ratio of package management verses three additional classes. Like the rest of the temporal packages, this built to use [Noda.Time](https://nodatime.org/) for coordinating times.
The [documentation](//mfgames.com/mfgames-cil/docs/nitride/temporal/) has some notes on how to include and use it inside a Nitride project.
## New Entity Constructor
We also added new constructor to `MfGames.Gallium.Entity` that allows multiple components to be set as part of the constructor.
```csharp
// These are all the same.
var entity1 = new Entity();
entity1 = entity1.Add("bob");
entity1 = entity1.Add(13);
var entity2 = new Entity().Add("bob").Add(13);
var entity3 = new Entity().SetAll("bob", 13);
// These are the new features.
var entity4 = new Entity("bob", 13);
var entity5 = new Entity(13, "bob");
```
It seemed like a no-brainer, mainly because it is a pattern we use a lot in the code, but Dylan finds that the formatting is less than "pretty" when it comes to multi-line chaining operations.
```csharp
var notPretty = new Entity(
"bob",
13,
instant,
path)
.AddTextContent(content);
var pretty = new Entity()
.SetAll(
"bob",
13,
instant,
path)
.AddTextContent(content);
```
Either work though, so it will end up reducing some of the clutter for common use cases.

View file

@ -1,4 +1,4 @@
namespace MfGames.Crypto;
namespace MfGames.Cryptography;
public enum ByteStringFormat
{

View file

@ -1,11 +1,11 @@
using MfGames.Crypto.Hashes;
using MfGames.Cryptography.Hashes;
namespace MfGames.Crypto.Extensions;
namespace MfGames.Cryptography.Extensions;
/// <summary>
/// Extension methods on byte arrays.
/// </summary>
public static class CryptoByteArrayExtensions
public static class CryptographyByteArrayExtensions
{
/// <summary>
/// Converts the input into a hash string, such as hex or base64.

View file

@ -1,12 +1,12 @@
using System.Text;
using MfGames.Crypto.Hashes;
using MfGames.Cryptography.Hashes;
namespace MfGames.Crypto.Extensions;
namespace MfGames.Cryptography.Extensions;
/// <summary>
/// Extension methods for generating hashes for strings.
/// </summary>
public static class CryptoStringExtensions
public static class CryptographyStringExtensions
{
/// <summary>
/// Gets the encoded byte array of the given string.

View file

@ -1,4 +1,4 @@
namespace MfGames.Crypto.Hashes;
namespace MfGames.Cryptography.Hashes;
/// <summary>
/// An enumeration of various hash types.

View file

@ -1,6 +1,6 @@
using System.Security.Cryptography;
namespace MfGames.Crypto.Hashes;
namespace MfGames.Cryptography.Hashes;
public static class HashTypeExtensions
{

View file

@ -16,6 +16,17 @@ public record Entity
this.Components = ImmutableDictionary.Create<Type, object>();
}
public Entity(params object[] componentList)
{
ImmutableDictionary<Type, object> componentSet = componentList.ToImmutableDictionary(
component => component.GetType(),
component => component
);
this.Id = Interlocked.Increment(ref nextId);
this.Components = componentSet;
}
/// <summary>
/// Gets or sets the optional formatting for an entity. This is used to
/// override the ToString functionality to provide additional information.

View file

@ -1,13 +1,13 @@
using MfGames.Crypto;
using MfGames.Crypto.Extensions;
using MfGames.Crypto.Hashes;
using MfGames.Cryptography;
using MfGames.Cryptography.Extensions;
using MfGames.Cryptography.Hashes;
namespace MfGames.IO.Extensions;
/// <summary>
/// Extensions for generating
/// </summary>
public static class CryptoFileInfoExtensions
public static class CryptographyFileInfoExtensions
{
/// <summary>
/// Hashes the given input and returns the results.

View file

@ -0,0 +1,148 @@
using FluentValidation;
using Ical.Net;
using Ical.Net.CalendarComponents;
using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using MfGames.Gallium;
using MfGames.Nitride.Generators;
using Microsoft.Extensions.Logging;
using NodaTime;
namespace MfGames.Nitride.Temporal;
/// <summary>
/// Constructs an iCalendar file for the instances that are provided to the
/// operation.
/// </summary>
[WithProperties]
public partial class CreateCalendar : OperationBase
{
private readonly ILogger<CreateCalendar> logger;
private readonly IValidator<CreateCalendar> validator;
static CreateCalendar()
{
DefaultGetInstant = (entity) => entity.TryGet(out Instant instant) ? instant : null;
}
public CreateCalendar(
ILogger<CreateCalendar> logger,
TimeService timeService,
IValidator<CreateCalendar> validator
)
{
this.TimeService = timeService;
this.logger = logger;
this.validator = validator;
this.GetInstant = DefaultGetInstant;
}
/// <summary>
/// Gets the default callback for getting the instant from the entity.
/// This calls `entity.GetOptional<Instant>()`.
/// </summary>
public static Func<Entity, Instant?> DefaultGetInstant { get; }
/// <summary>
/// Gets or sets the callback method for creating the calendar itself.
/// </summary>
public Func<Calendar, string, Entity?>? CreateCalendarEntity { get; set; }
/// <summary>
/// Gets or sets the callback used to create a calendar event from the given
/// entity. The start and stop will be set after the return of this value
/// but any summary, categories, or other values should be set before
/// returning.
/// </summary>
public Func<Entity, CalendarEvent?>? CreateEvent { get; set; }
/// <summary>
/// Gets or sets the callback used to get the instant from the given entity.
/// This defaults to DefaultGetInstant which gets the Instant from the
/// entity.
/// </summary>
public Func<Entity, Instant?> GetInstant { get; set; }
/// <summary>
/// An optional event to update the created event further.
/// </summary>
public Action<Entity, CalendarEvent>? PostCreateEvent { get; set; }
/// <summary>
/// Gets or sets the time service associated with the operation.
/// </summary>
public TimeService TimeService { get; set; }
/// <inheritdoc />
public override IEnumerable<Entity> Run(
IEnumerable<Entity> input,
CancellationToken cancellationToken = default
)
{
// Validate our input.
this.validator.ValidateAndThrow(this);
// Loop through the entry, returning it as it is parsed.
var calendar = new Calendar();
foreach (Entity entity in input)
{
yield return this.ProcessEntity(entity, calendar);
}
// Serialize the calendar.
CalendarSerializer serializer = new();
string? text = serializer.SerializeToString(calendar);
if (text == null)
{
this.logger.LogWarning("Could not create calendar entry");
yield break;
}
// Create the calendar entry and emit it.
Entity? calendarEntity = this.CreateCalendarEntity!(calendar, text);
if (calendarEntity != null)
{
yield return calendarEntity;
}
}
private Entity ProcessEntity(Entity entity, Calendar calendar)
{
// See if we have an instant.
Instant? instant = this.GetInstant(entity);
if (!instant.HasValue)
{
return entity;
}
// See if we have a title.
CalendarEvent? entry = this.CreateEvent!(entity);
if (entry == null)
{
return entity;
}
// Set the dates in the entry. We don't use timezones here so they
// show up as "all day" events for everyone. We also strip off minutes
// to make it more obvious.
var when = this.TimeService.ToDateTime(instant.Value);
var calendarWhen = new CalDateTime(when.Year, when.Month, when.Day) { HasTime = false };
entry.Start = calendarWhen;
entry.End = calendarWhen.AddDays(1);
entry.Created = new CalDateTime(when);
entry.DtStamp = new CalDateTime(when);
this.PostCreateEvent?.Invoke(entity, entry);
calendar.Events.Add(entry);
return entity;
}
}

View file

@ -17,6 +17,7 @@
<ItemGroup>
<PackageReference Include="Autofac" Version="8.0.0" />
<PackageReference Include="Ical.Net" Version="4.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="NodaTime.Testing" Version="3.1.11" />

View file

@ -0,0 +1,17 @@
using FluentValidation;
namespace MfGames.Nitride.Temporal.Validators;
public class CreateCalendarValidator : AbstractValidator<CreateCalendar>
{
public CreateCalendarValidator()
{
this.RuleFor(a => a.CreateCalendarEntity).NotNull();
this.RuleFor(a => a.GetInstant).NotNull();
this.RuleFor(a => a.CreateEvent).NotNull();
this.RuleFor(a => a.TimeService).NotNull();
}
}

View file

@ -1,10 +1,10 @@
using MfGames.Crypto;
using MfGames.Crypto.Extensions;
using MfGames.Crypto.Hashes;
using MfGames.Cryptography;
using MfGames.Cryptography.Extensions;
using MfGames.Cryptography.Hashes;
namespace MfGames.Nitride.Contents;
public static class CryptoContentExtensions
public static class CryptographyContentExtensions
{
/// <summary>
/// Hashes the given input and returns the results.

View file

@ -39,7 +39,7 @@
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Xunit" Version="3.0.5" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit" Version="2.8.1" />
</ItemGroup>
</Project>

View file

@ -1,10 +1,10 @@
using MfGames.Crypto.Extensions;
using MfGames.Crypto.Hashes;
using MfGames.Cryptography.Extensions;
using MfGames.Cryptography.Hashes;
using Xunit;
namespace MfGames.Crypto.Tests;
namespace MfGames.Cryptography.Tests;
public class CryptoStringExtensionsTests
public class CryptographyStringExtensionsTests
{
[Fact]
public void PasswordAsBase64Md5()

View file

@ -26,8 +26,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View file

@ -22,8 +22,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -1,12 +1,12 @@
using System.IO;
using MfGames.Crypto;
using MfGames.Crypto.Hashes;
using MfGames.Cryptography;
using MfGames.Cryptography.Hashes;
using MfGames.IO.Extensions;
using Xunit;
namespace MfGames.IO.Tests;
public class CryptoFileInfoTests
public class CryptographyFileInfoTests
{
[Fact]
public void HashPasswordFileAsLowercaseHexSha256()

View file

@ -21,8 +21,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -26,8 +26,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View file

@ -21,8 +21,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -22,8 +22,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -21,8 +21,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -30,8 +30,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -21,8 +21,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -28,8 +28,8 @@
</PackageReference>
<PackageReference Include="Slugify.Core" Version="4.0.1" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -27,8 +27,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -28,8 +28,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -0,0 +1,31 @@
BEGIN:VCALENDAR
PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
CREATED;VALUE=DATE:20240601
DTEND;VALUE=DATE:20240602
DTSTAMP;VALUE=DATE:20240601
DTSTART;VALUE=DATE:20240601
SEQUENCE:0
SUMMARY:page1
UID:486897e5-67dc-408c-af77-158caa8c64fc
END:VEVENT
BEGIN:VEVENT
CREATED;VALUE=DATE:20240602
DTEND;VALUE=DATE:20240603
DTSTAMP;VALUE=DATE:20240602
DTSTART;VALUE=DATE:20240602
SEQUENCE:0
SUMMARY:page2
UID:09866a66-58f1-4f2c-9057-af7a69b8100b
END:VEVENT
BEGIN:VEVENT
CREATED;VALUE=DATE:20250602
DTEND;VALUE=DATE:20250603
DTSTAMP;VALUE=DATE:20250602
DTSTART;VALUE=DATE:20250602
SEQUENCE:0
SUMMARY:page3
UID:b0358515-6fa0-4d6c-86f0-143b590a7a91
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ical.Net.CalendarComponents;
using MfGames.Gallium;
using MfGames.Nitride.Contents;
using VerifyXunit;
using Xunit;
using Xunit.Abstractions;
using Zio;
namespace MfGames.Nitride.Temporal.Tests;
public class CreateCalendarTests : TemporalTestBase
{
public CreateCalendarTests(ITestOutputHelper output)
: base(output) { }
[Fact]
public Task CreateSimpleCalendar()
{
// Set up the test and run the operation.
using TemporalTestContext context = this.CreateContext();
var timeService = context.Resolve<TimeService>();
CreateCalendar op = context
.Resolve<CreateCalendar>()
.WithCreateEvent(entity => new CalendarEvent
{
Summary = entity.Get<string>(),
Uid = entity.Get<Guid>().ToString(),
})
.WithCreateCalendarEntity(
(calendar, text) =>
new Entity().SetAll("calendar", new UPath("/calendar.ics")).SetTextContent(text)
);
List<Entity> input =
new()
{
new Entity(
"page1",
new Guid("486897e5-67dc-408c-af77-158caa8c64fc"),
timeService.CreateInstant(2024, 6, 1)
),
new Entity(
"page2",
new Guid("09866a66-58f1-4f2c-9057-af7a69b8100b"),
timeService.CreateInstant(2024, 6, 2)
),
new Entity(
"page3",
new Guid("b0358515-6fa0-4d6c-86f0-143b590a7a91"),
timeService.CreateInstant(2025, 6, 2)
),
};
List<Entity> output = input.Run(op).ToList();
// Verify the output files.
var expected = new List<string> { "calendar", "page1", "page2", "page3", };
Assert.Equal(expected, output.SelectComponent<string>().OrderBy(x => x));
// Verify the calendar output.
Entity entity = output.Single(x => x.Get<string>() == "calendar");
string? text = entity.GetTextContentString();
return Verifier.Verify(text);
}
}

View file

@ -28,8 +28,9 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="Verify.Xunit" Version="25.0.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -23,8 +23,8 @@
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.5" />
<PackageReference Include="JunitXml.TestLogger" Version="3.1.12" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View file

@ -21,8 +21,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -27,8 +27,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>