mirror of https://github.com/oliverbooth/fdup.git
feat: initial commit
This commit is contained in:
commit
f8018096c5
|
@ -0,0 +1,133 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
yiliansource@gmail.com or me@olivr.me.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
ko_fi: oliverbooth
|
||||||
|
custom: ['https://buymeacoffee.com/oliverbooth']
|
|
@ -0,0 +1,53 @@
|
||||||
|
name: .NET
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
- '*/*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
- '*/*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "Build & Test"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: |
|
||||||
|
6.0.x
|
||||||
|
7.0.x
|
||||||
|
8.0.x
|
||||||
|
|
||||||
|
- name: Add NuGet source
|
||||||
|
run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json"
|
||||||
|
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build --no-restore --configuration Release
|
||||||
|
|
||||||
|
- name: Test .NET 6
|
||||||
|
run: dotnet test --no-build --verbosity normal --configuration Release --framework net6.0 --collect:"XPlat Code Coverage" --results-directory test-results/net6.0
|
||||||
|
|
||||||
|
- name: Test .NET 7
|
||||||
|
run: dotnet test --no-build --verbosity normal --configuration Release --framework net7.0 --collect:"XPlat Code Coverage" --results-directory test-results/net7.0
|
||||||
|
|
||||||
|
- name: Test .NET 8
|
||||||
|
run: dotnet test --no-build --verbosity normal --configuration Release --framework net8.0 --collect:"XPlat Code Coverage" --results-directory test-results/net8.0
|
||||||
|
|
||||||
|
- name: Upload coverage reports to Codecov
|
||||||
|
uses: codecov/codecov-action@v4.0.0
|
||||||
|
with:
|
||||||
|
directory: test-results
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
slug: oliverbooth/X10D
|
|
@ -0,0 +1,37 @@
|
||||||
|
*.swp
|
||||||
|
*.*~
|
||||||
|
project.lock.json
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
nupkg/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
msbuild.log
|
||||||
|
msbuild.err
|
||||||
|
msbuild.wrn
|
||||||
|
|
||||||
|
# Visual Studio 2015
|
||||||
|
.vs/
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FindDuplicates", "FindDuplicates\FindDuplicates.csproj", "{543F0776-01CD-420B-973A-BCE11F47C758}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{543F0776-01CD-420B-973A-BCE11F47C758}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{543F0776-01CD-420B-973A-BCE11F47C758}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{543F0776-01CD-420B-973A-BCE11F47C758}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{543F0776-01CD-420B-973A-BCE11F47C758}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AssemblyName>fdup</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Spectre.Console.Cli" Version="0.48.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,108 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace FindDuplicates;
|
||||||
|
|
||||||
|
internal sealed class ListCommand : AsyncCommand<ListSettings>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, List<FileInfo>> _fileHashMap = new();
|
||||||
|
|
||||||
|
public override async Task<int> ExecuteAsync(CommandContext context, ListSettings settings)
|
||||||
|
{
|
||||||
|
var inputDirectory = new DirectoryInfo(settings.InputPath);
|
||||||
|
if (!inputDirectory.Exists)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"[red]{inputDirectory} does not exist![/]");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Searching [cyan]{inputDirectory.FullName}[/]");
|
||||||
|
AnsiConsole.MarkupLine($"Recursive mode is {(settings.Recursive ? "[green]ON" : "[red]OFF")}[/]");
|
||||||
|
|
||||||
|
await SearchAsync(inputDirectory, settings);
|
||||||
|
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
|
int duplicates = 0;
|
||||||
|
foreach ((string hash, List<FileInfo> files) in _fileHashMap)
|
||||||
|
{
|
||||||
|
int fileCount = files.Count;
|
||||||
|
|
||||||
|
if (fileCount > 1)
|
||||||
|
{
|
||||||
|
duplicates += fileCount;
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Found [cyan]{fileCount}[/] identical files");
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"SHA512 [green]{hash}[/]:");
|
||||||
|
|
||||||
|
foreach (FileInfo file in files)
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"- {file.FullName}");
|
||||||
|
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicates == 0)
|
||||||
|
AnsiConsole.MarkupLine("[green]No duplicates found![/]");
|
||||||
|
else
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"[yellow]Found [cyan]{duplicates}[/] duplicates![/]");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SearchAsync(DirectoryInfo inputDirectory, ListSettings settings)
|
||||||
|
{
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
var directoryStack = new Stack<DirectoryInfo>([inputDirectory]);
|
||||||
|
while (directoryStack.Count > 0)
|
||||||
|
{
|
||||||
|
DirectoryInfo currentDirectory = directoryStack.Pop();
|
||||||
|
string relativePath = Path.GetRelativePath(inputDirectory.FullName, currentDirectory.FullName);
|
||||||
|
if (relativePath != ".")
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Searching [cyan]{relativePath}[/]");
|
||||||
|
|
||||||
|
if (settings.Recursive)
|
||||||
|
{
|
||||||
|
foreach (DirectoryInfo childDirectory in currentDirectory.EnumerateDirectories())
|
||||||
|
directoryStack.Push(childDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FileInfo file in currentDirectory.EnumerateFiles())
|
||||||
|
{
|
||||||
|
string relativeFilePath = Path.GetRelativePath(inputDirectory.FullName, file.FullName);
|
||||||
|
AnsiConsole.MarkupLineInterpolated($"Checking hash for [cyan]{relativeFilePath}[/]");
|
||||||
|
tasks.Add(Task.Run(() => ProcessFile(file)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessFile(FileInfo file)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[64];
|
||||||
|
using FileStream stream = file.OpenRead();
|
||||||
|
using BufferedStream bufferedStream = new BufferedStream(stream, 1048576 /* 1MB */);
|
||||||
|
SHA512.HashData(bufferedStream, buffer);
|
||||||
|
string hash = ByteSpanToString(buffer);
|
||||||
|
Trace.WriteLine($"{file.FullName}: {hash}");
|
||||||
|
|
||||||
|
if (!_fileHashMap.TryGetValue(hash, out List<FileInfo>? cache))
|
||||||
|
_fileHashMap[hash] = cache = new List<FileInfo>();
|
||||||
|
|
||||||
|
lock (cache)
|
||||||
|
cache.Add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ByteSpanToString(ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (byte b in buffer)
|
||||||
|
builder.Append($"{b:X2}");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace FindDuplicates;
|
||||||
|
|
||||||
|
internal sealed class ListSettings : CommandSettings
|
||||||
|
{
|
||||||
|
[CommandArgument(0, "<path>")]
|
||||||
|
[Description("The path to search.")]
|
||||||
|
public string InputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[CommandOption("-r|--recursive")]
|
||||||
|
[Description("When this flag is set, the directory will be scanned recursively. This may take longer.")]
|
||||||
|
[DefaultValue(false)]
|
||||||
|
public bool Recursive { get; set; } = false;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
using FindDuplicates;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
var app = new CommandApp<ListCommand>();
|
||||||
|
await app.RunAsync(args).ConfigureAwait(false);
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Oliver Booth
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Find Duplicates (fdup)
|
||||||
|
|
||||||
|
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/oliverbooth/fdup/dotnet.yml?style=flat-square)
|
||||||
|
![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/oliverbooth/fdup?style=flat-square)
|
||||||
|
![GitHub License](https://img.shields.io/github/license/oliverbooth/fdup?style=flat-square)
|
||||||
|
|
||||||
|
## About
|
||||||
|
fdup is a small command-line utility written in C# to quickly and easily find duplicate files. It can also search recursively to find duplicate files in child directories.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```bash
|
||||||
|
$ fdup --help
|
||||||
|
USAGE:
|
||||||
|
fdup <path> [OPTIONS]
|
||||||
|
|
||||||
|
ARGUMENTS:
|
||||||
|
<path> The path to search
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Prints help information
|
||||||
|
-v, --version Prints version information
|
||||||
|
-r, --recursive When this flag is set, the directory will be scanned recursively. This may take longer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```bash
|
||||||
|
$ echo "Hello World" > file1
|
||||||
|
$ echo "Goodbye World" > file2
|
||||||
|
$ fdup .
|
||||||
|
Searching /home/user/example
|
||||||
|
Recursive mode is OFF
|
||||||
|
Checking hash for file2
|
||||||
|
Checking hash for file1
|
||||||
|
|
||||||
|
No duplicates found!
|
||||||
|
$ echo "Hello World" > file2
|
||||||
|
$ fdup .
|
||||||
|
Searching /home/user/example
|
||||||
|
Recursive mode is OFF
|
||||||
|
Checking hash for file2
|
||||||
|
Checking hash for file1
|
||||||
|
|
||||||
|
Found 2 identical files
|
||||||
|
SHA512 E1C112FF908FEBC3B98B1693A6CD3564EAF8E5E6CA629D084D9F0EBA99247CACDD72E369FF8941397C2807409FF66BE64BE908DA17AD7B8A49A2A26C0E8086AA:
|
||||||
|
- /home/user/example/file1
|
||||||
|
- /home/user/example/file2
|
||||||
|
|
||||||
|
Found 2 duplicates!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
X10D is released under the MIT License. See [here](https://github.com/oliverbooth/X10D/blob/main/LICENSE.md) for more details.
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue