Skip to content

Commit c5f06c0

Browse files
committed
Merge branch 'TailwindcssHosted'
# Conflicts: # src/tailwindcss-dotnet/tailwindcss-dotnet.csproj
2 parents 7744b54 + 9792ba9 commit c5f06c0

10 files changed

Lines changed: 300 additions & 245 deletions

File tree

src/tailwindcss-dotnet/Cli/CliExe.cs

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/tailwindcss-dotnet/Cli/TailwindCli.cs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Runtime.InteropServices;
2-
using Microsoft.VisualBasic.CompilerServices;
32
using Tailwindcss.DotNetTool.Infrastructure;
43

54
namespace Tailwindcss.DotNetTool.Cli;
@@ -50,11 +49,11 @@ public async Task InitializeAsync(string? version = null)
5049
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
5150
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
5251
{
53-
await ProcessUtil.RunAsync("chmod", "+x " + tempBinPath);
52+
await ProcessUtil.ExecuteAsync("chmod", "+x " + tempBinPath);
5453

5554
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
5655
{
57-
await ProcessUtil.RunAsync("xattr", "-d com.apple.quarantine " + tempBinPath);
56+
await ProcessUtil.ExecuteAsync("xattr", "-d com.apple.quarantine " + tempBinPath);
5857
}
5958
}
6059

@@ -84,10 +83,11 @@ public string Executable()
8483
return _binPath!;
8584
}
8685

87-
public CliExe CompileCommand(string rootPath, bool debug = false)
86+
public string[] CompileCommand(string rootPath, bool debug = false)
8887
{
8988
IEnumerable<string> args = new[]
9089
{
90+
Executable(),
9191
"-i", Path.GetFullPath(Path.Combine("styles", "app.tailwind.css"), rootPath),
9292
"-o", Path.GetFullPath(Path.Combine("wwwroot", "css", "app.css"), rootPath),
9393
};
@@ -97,24 +97,21 @@ public CliExe CompileCommand(string rootPath, bool debug = false)
9797
args = args.Append("--minify");
9898
}
9999

100-
return new CliExe(Executable(), string.Join(' ', args), rootPath);
100+
return args.ToArray();
101101
}
102102

103-
public CliExe WatchCommand(string rootPath, bool debug = false, bool poll = false)
103+
public string[] WatchCommand(string rootPath, bool debug = false, bool poll = false)
104104
{
105-
var exe = CompileCommand(rootPath, debug);
105+
var compileCommand = CompileCommand(rootPath, debug);
106106

107-
IEnumerable<string> args = new[]
108-
{
109-
exe.Arguments!,
110-
"-w"
111-
};
107+
IEnumerable<string> result = compileCommand;
108+
result = result.Append("-w");
112109

113110
if (poll)
114111
{
115-
args = args.Append("-p");
112+
result = result.Append("-p");
116113
}
117114

118-
return new CliExe(exe.FileName, string.Join(' ', args), rootPath);
115+
return result.ToArray();
119116
}
120117
}

src/tailwindcss-dotnet/CommandLineBuilderExtensions.cs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public static CommandLineBuilder UseProjectOption(
8585
projectPath = projectOptionResult.GetValueOrDefault<string?>();
8686
}
8787

88-
var projectInfo = ResolveProject(projectPath);
88+
var projectInfo = DotnetTool.ResolveProject(projectPath);
8989
if (projectInfo?.ProjectRoot == null)
9090
{
9191
context.Console.Error.WriteLine("Project file was not found. Use --project option or change current folder.");
@@ -101,44 +101,6 @@ public static CommandLineBuilder UseProjectOption(
101101
return builder;
102102
}
103103

104-
private static ProjectInfo? ResolveProject(string? path)
105-
{
106-
if (path == null)
107-
{
108-
path = Directory.GetCurrentDirectory();
109-
}
110-
else
111-
{
112-
path = Path.GetFullPath(path);
113-
114-
if (File.Exists(path))
115-
{
116-
// It's not a directory
117-
return new ProjectInfo
118-
{
119-
ProjectFilePath = Path.GetDirectoryName(path)!,
120-
ProjectRoot = path
121-
};
122-
}
123-
}
124-
125-
if (!Directory.Exists(path))
126-
return null;
127-
128-
var projectFile = Directory
129-
.EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly)
130-
.FirstOrDefault(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase));
131-
132-
if (projectFile == null)
133-
return null;
134-
135-
return new ProjectInfo
136-
{
137-
ProjectFilePath = projectFile,
138-
ProjectRoot = path
139-
};
140-
}
141-
142104
public static CommandLineBuilder UseExecRun(
143105
this CommandLineBuilder builder, AppInvocationContext appContext)
144106
{

src/tailwindcss-dotnet/Commands/BuildCommand.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.CommandLine;
22
using System.CommandLine.Builder;
33
using Tailwindcss.DotNetTool.Cli;
4+
using Tailwindcss.DotNetTool.Infrastructure;
45

56
namespace Tailwindcss.DotNetTool.Commands;
67

@@ -29,11 +30,12 @@ public void Setup(CommandLineBuilder builder, AppInvocationContext appContext)
2930

3031
public async Task<int> Execute(AppInvocationContext context, bool debug)
3132
{
32-
CliExe cliExe = context.Cli.CompileCommand(context.GetProjectRoot(), debug);
33+
string rootPath = context.GetProjectRoot();
34+
var command = context.Cli.CompileCommand(rootPath, debug);
3335

34-
context.Console.WriteLine("Starting Build command...");
35-
context.Console.WriteLine(cliExe.ToString());
36+
Console.WriteLine("Starting Build command...");
37+
Console.WriteLine("Execute command: {0}.", string.Join(" ", command));
3638

37-
return await cliExe.RunAsync();
39+
return await ProcessUtil.ExecuteAsync(command, rootPath);
3840
}
3941
}

src/tailwindcss-dotnet/Commands/ExecCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.CommandLine;
22
using System.CommandLine.Builder;
33
using Tailwindcss.DotNetTool.Cli;
4+
using Tailwindcss.DotNetTool.Infrastructure;
45

56
namespace Tailwindcss.DotNetTool.Commands;
67

@@ -21,7 +22,6 @@ public Task<int> Execute(TailwindCli cli, string[] args)
2122
{
2223
string binPath = cli.Executable();
2324

24-
var exe = new CliExe(binPath, string.Join(' ', args), null);
25-
return exe.RunAsync();
25+
return ProcessUtil.ExecuteAsync(binPath, string.Join(' ', args));
2626
}
2727
}

src/tailwindcss-dotnet/Commands/WatchCommand.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.CommandLine;
22
using System.CommandLine.Builder;
33
using Tailwindcss.DotNetTool.Cli;
4+
using Tailwindcss.DotNetTool.Infrastructure;
45

56
namespace Tailwindcss.DotNetTool.Commands;
67

@@ -32,11 +33,11 @@ public void Setup(CommandLineBuilder builder, AppInvocationContext appContext)
3233

3334
public async Task<int> Execute(AppInvocationContext context, bool debug, bool poll)
3435
{
35-
CliExe cliExe = context.Cli.WatchCommand(context.GetProjectRoot(), debug, poll);
36+
var command = context.Cli.WatchCommand(context.GetProjectRoot(), debug, poll);
3637

37-
context.Console.WriteLine("Starting Watch command...");
38-
context.Console.WriteLine(cliExe.ToString());
38+
Console.WriteLine("Starting Watch command...");
39+
Console.WriteLine("Execute command: {0}.", string.Join(" ", command));
3940

40-
return await cliExe.RunAsync();
41+
return await ProcessUtil.ExecuteAsync(command);
4142
}
4243
}

src/tailwindcss-dotnet/DotnetTool.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,42 @@ static DotnetTool()
88
{
99
InstallationFolder = Path.GetDirectoryName(typeof(Program).Assembly.Location)!;
1010
}
11+
12+
public static ProjectInfo? ResolveProject(string? path)
13+
{
14+
if (path == null)
15+
{
16+
path = Directory.GetCurrentDirectory();
17+
}
18+
else
19+
{
20+
path = Path.GetFullPath(path);
21+
22+
if (File.Exists(path))
23+
{
24+
// It's not a directory
25+
return new ProjectInfo
26+
{
27+
ProjectFilePath = Path.GetDirectoryName(path)!,
28+
ProjectRoot = path
29+
};
30+
}
31+
}
32+
33+
if (!Directory.Exists(path))
34+
return null;
35+
36+
var projectFile = Directory
37+
.EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly)
38+
.FirstOrDefault(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase));
39+
40+
if (projectFile == null)
41+
return null;
42+
43+
return new ProjectInfo
44+
{
45+
ProjectFilePath = projectFile,
46+
ProjectRoot = path
47+
};
48+
}
1149
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using Microsoft.Extensions.Hosting;
2+
using Microsoft.Extensions.Logging;
3+
using Tailwindcss.DotNetTool.Infrastructure;
4+
5+
namespace Tailwindcss.DotNetTool.Hosting;
6+
7+
public class TailwindcssHostedService : IHostedService
8+
{
9+
private readonly ILogger<TailwindcssHostedService> _logger;
10+
11+
private readonly IHostApplicationLifetime _hostApplicationLifetime;
12+
private IAsyncDisposable? _tailwindDisposable;
13+
14+
public TailwindcssHostedService(ILogger<TailwindcssHostedService> logger,
15+
IHostApplicationLifetime hostApplicationLifetime)
16+
{
17+
_hostApplicationLifetime = hostApplicationLifetime;
18+
_logger = logger;
19+
}
20+
21+
public async Task StartAsync(CancellationToken cancellationToken)
22+
{
23+
// Blocking app start until all deps are downloaded and initialized
24+
ProjectInfo? projectInfo = DotnetTool.ResolveProject(null);
25+
26+
if (projectInfo == null)
27+
{
28+
throw new ArgumentNullException(nameof(projectInfo));
29+
}
30+
31+
var appContext = new AppInvocationContext
32+
{
33+
Project = projectInfo
34+
};
35+
await appContext.Cli.InitializeAsync();
36+
37+
EnsureTailwindProcessRunning(appContext);
38+
}
39+
40+
public async Task StopAsync(CancellationToken cancellationToken)
41+
{
42+
_logger.LogInformation("Stopping tailwind...");
43+
44+
if (_tailwindDisposable is { } disposable)
45+
{
46+
_tailwindDisposable = null;
47+
48+
try
49+
{
50+
await disposable.DisposeAsync().AsTask().ConfigureAwait(false);
51+
}
52+
catch (OperationCanceledException)
53+
{
54+
// Failed with timeout, ignore
55+
}
56+
catch (Exception ex)
57+
{
58+
_logger.LogCritical(ex, "Tailwindcss process termination failed with an error.");
59+
}
60+
}
61+
}
62+
63+
private void EnsureTailwindProcessRunning(AppInvocationContext appContext)
64+
{
65+
var watchCommand = appContext.Cli.WatchCommand(appContext.GetProjectRoot(), true);
66+
_logger.LogInformation("Starting tailwind. Execute command: {Command}.", string.Join(" ", watchCommand));
67+
68+
var procSpec = CreateTailwindProcSpec(watchCommand, appContext.GetProjectRoot());
69+
(var resultTask, _tailwindDisposable) = ProcessUtil.Run(procSpec);
70+
71+
resultTask.ContinueWith(task =>
72+
{
73+
if (!_hostApplicationLifetime.ApplicationStopping.IsCancellationRequested)
74+
{
75+
_logger.LogCritical("Tailwind process died. Exit code {ExitCode}. Stopping the application...",
76+
task.Result.ExitCode);
77+
_hostApplicationLifetime.StopApplication();
78+
}
79+
});
80+
}
81+
82+
private ProcessSpec CreateTailwindProcSpec(string[] command, string workingDirectory)
83+
{
84+
ProcessSpec procSpec = new ProcessSpec(command[0])
85+
{
86+
WorkingDirectory = workingDirectory,
87+
Arguments = string.Join(" ", command.Skip(1)),
88+
OnOutputData = Console.Out.WriteLine,
89+
OnErrorData = Console.Error.WriteLine,
90+
InheritEnv = true,
91+
ThrowOnNonZeroReturnCode = false,
92+
};
93+
94+
return procSpec;
95+
}
96+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace Tailwindcss.DotNetTool.Infrastructure;
2+
3+
internal static partial class ProcessUtil
4+
{
5+
public static Task<int> ExecuteAsync(string[] command, string? workingDirectory = null)
6+
{
7+
return ExecuteAsync(command[0], string.Join(" ", command.Skip(0)), workingDirectory);
8+
}
9+
10+
public static async Task<int> ExecuteAsync(string fileName, string? arguments = null, string? workingDirectory = null)
11+
{
12+
ProcessSpec procSpec = new ProcessSpec(fileName)
13+
{
14+
WorkingDirectory = workingDirectory ?? Directory.GetCurrentDirectory(),
15+
Arguments = arguments ?? "",
16+
OnOutputData = Console.Out.Write,
17+
OnErrorData = Console.Error.Write,
18+
InheritEnv = false,
19+
};
20+
21+
var (result, _) = ProcessUtil.Run(procSpec);
22+
return (await result).ExitCode;
23+
}
24+
}

0 commit comments

Comments
 (0)