mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-28 23:52:36 +01:00
Merge branch 'master' into URLChangeDoc
This commit is contained in:
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <summary>
|
||||
/// Directory to byond installation configuration
|
||||
/// </summary>
|
||||
const string ByondConfigDir = "byond/config";
|
||||
const string ByondConfigDir = "byond/cfg";
|
||||
/// <summary>
|
||||
/// BYOND's DreamDaemon config file
|
||||
/// </summary>
|
||||
|
||||
@@ -84,6 +84,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IPostWriteHandler postWriteHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdogFactory"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly IWatchdogFactory watchdogFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
@@ -100,7 +105,8 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler)
|
||||
/// <param name="watchdogFactory">The value of <see cref="watchdogFactory"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
@@ -115,6 +121,7 @@ namespace Tgstation.Server.Host.Components
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
|
||||
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -148,8 +155,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, loggerFactory, metadata.CloneMetadata());
|
||||
var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger<ReattachInfoHandler>(), metadata.CloneMetadata());
|
||||
var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, metadata.CloneMetadata());
|
||||
var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings);
|
||||
var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings);
|
||||
eventConsumer.SetWatchdog(watchdog);
|
||||
commandFactory.SetWatchdog(watchdog);
|
||||
try
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
@@ -11,9 +12,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IWatchdog"/>
|
||||
/// </summary>
|
||||
/// <param name="chat">The <see cref="IChat"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="IWatchdog"/> with</param>
|
||||
/// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="instance">The <see cref="Instance"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="settings">The initial <see cref="DreamDaemonSettings"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <returns>A new <see cref="IWatchdog"/></returns>
|
||||
IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings);
|
||||
IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
@@ -86,6 +87,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="Watchdog"/>
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for the <see cref="Watchdog"/>
|
||||
/// </summary>
|
||||
@@ -141,11 +147,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="initialLaunchParameters">The initial value of <see cref="ActiveLaunchParameters"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
/// <param name="autoStart">The value of <see cref="autoStart"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
|
||||
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
|
||||
{
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
@@ -155,6 +162,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
this.autoStart = autoStart;
|
||||
|
||||
@@ -642,7 +650,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!");
|
||||
|
||||
var reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null;
|
||||
var doesntNeedNewDmb = doReattach && reattachInfo.Alpha != null && reattachInfo.Bravo != null;
|
||||
var doesntNeedNewDmb = doReattach && reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
|
||||
var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2);
|
||||
|
||||
try
|
||||
@@ -697,7 +705,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
//both servers are now running, alpha is the active server, huzzah
|
||||
AlphaIsActive = doReattach ? reattachInfo.AlphaIsActive : true;
|
||||
AlphaIsActive = doReattach ? reattachInfo?.AlphaIsActive ?? true : true;
|
||||
LastLaunchResult = alphaLrt.Result;
|
||||
logger.LogInformation("Launched servers successfully");
|
||||
Running = true;
|
||||
@@ -789,8 +797,24 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (autoStart)
|
||||
await LaunchNoLock(true, true, true, cancellationToken).ConfigureAwait(false);
|
||||
if (!autoStart)
|
||||
return;
|
||||
|
||||
var job = new Models.Job
|
||||
{
|
||||
StartedBy = new Models.User
|
||||
{
|
||||
Id = 1 //just use admin for this cause whatever
|
||||
},
|
||||
Instance = new Models.Instance
|
||||
{
|
||||
Id = instance.Id
|
||||
},
|
||||
Description = "Instance startup watchdog launch",
|
||||
CancelRight = (ulong)DreamDaemonRights.Shutdown,
|
||||
CancelRightsType = RightsType.DreamDaemon
|
||||
};
|
||||
await jobManager.RegisterOperation(job, (j, serviceProvider, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -11,16 +11,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
sealed class WatchdogFactory : IWatchdogFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IChat"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IChat chat;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly ISessionControllerFactory sessionControllerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
@@ -31,11 +21,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly ILoggerFactory loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IReattachInfoHandler"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IReattachInfoHandler reattachInfoHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
@@ -47,42 +32,28 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
readonly IByondTopicSender byondTopicSender;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IEventConsumer"/> for the <see cref="WatchdogFactory"/>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly Api.Models.Instance instance;
|
||||
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="chat">The value of <see cref="chat"/></param>
|
||||
/// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param>
|
||||
/// <param name="serverUpdater">The value of <see cref="serverUpdater"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
/// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public WatchdogFactory(IChat chat, ISessionControllerFactory sessionControllerFactory, IServerControl serverUpdater, ILoggerFactory loggerFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, Api.Models.Instance instance)
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
public WatchdogFactory(IServerControl serverUpdater, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager)
|
||||
{
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWatchdog CreateWatchdog(IDmbFactory dmbFactory, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger<Watchdog>(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, settings, instance, settings.AutoStart.Value);
|
||||
public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, serverUpdater, loggerFactory.CreateLogger<Watchdog>(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, settings, instance, settings.AutoStart.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false);
|
||||
if (result == null)
|
||||
throw new InvalidOperationException("Watchdog already running!");
|
||||
throw new JobException("Watchdog already running!");
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return Accepted(job.ToApi());
|
||||
|
||||
@@ -287,6 +287,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true)
|
||||
return BadRequest(new ErrorMessage { Message = "Cannot test merge the same PR twice in one job!" });
|
||||
|
||||
if (model.CommitterName?.Length == 0)
|
||||
return BadRequest(new ErrorMessage { Message = "Cannot set empty committer name!" });
|
||||
|
||||
if (model.CommitterEmail?.Length == 0)
|
||||
return BadRequest(new ErrorMessage { Message = "Cannot set empty committer e=mail!" });
|
||||
|
||||
var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
|
||||
var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
|
||||
if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
|
||||
|
||||
@@ -3,13 +3,13 @@ using Cyberboss.AspNetCore.AsyncInitializer;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
@@ -22,6 +22,7 @@ using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.StaticFiles;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
using Tgstation.Server.Host.IO;
|
||||
@@ -200,8 +201,9 @@ namespace Tgstation.Server.Host.Core
|
||||
SendTimeout = 5000
|
||||
});
|
||||
|
||||
services.AddSingleton<InstanceFactory>();
|
||||
services.AddSingleton<IInstanceFactory>(x => x.GetRequiredService<InstanceFactory>());
|
||||
services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
|
||||
services.AddSingleton<IInstanceFactory, InstanceFactory>();
|
||||
|
||||
services.AddSingleton<InstanceManager>();
|
||||
services.AddSingleton<IInstanceManager>(x => x.GetRequiredService<InstanceManager>());
|
||||
services.AddSingleton<IHostedService>(x => x.GetRequiredService<InstanceManager>());
|
||||
@@ -221,7 +223,8 @@ namespace Tgstation.Server.Host.Core
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to configure</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="Application"/></param>
|
||||
public void Configure(IApplicationBuilder applicationBuilder, ILogger<Application> logger)
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the application</param>
|
||||
public void Configure(IApplicationBuilder applicationBuilder, ILogger<Application> logger, IServerControl serverControl)
|
||||
{
|
||||
if (applicationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||
@@ -229,6 +232,9 @@ namespace Tgstation.Server.Host.Core
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
logger.LogInformation(VersionString);
|
||||
|
||||
//attempt to restart the server if the configuration changes
|
||||
ChangeToken.OnChange(configuration.GetReloadToken, () => serverControl.Restart());
|
||||
|
||||
applicationBuilder.UseDeveloperExceptionPage(); //it is not worth it to limit this, you should only ever get it if you're an authorized user
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Tgstation.Server.CommandLine\Tgstation.Server.CommandLine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user