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+ }
0 commit comments