diff --git a/config/services.php b/config/services.php index 7ac7aed..6c564e2 100644 --- a/config/services.php +++ b/config/services.php @@ -19,6 +19,7 @@ abstract_arg('path to source Tailwind CSS file'), param('kernel.project_dir').'/var/tailwind', abstract_arg('path to tailwind binary'), + abstract_arg('tailwind binary version') ]) ->set('tailwind.command.build', TailwindBuildCommand::class) diff --git a/doc/index.rst b/doc/index.rst index 7b3fd51..039a8ad 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -147,3 +147,16 @@ To instruct the bundle to use that binary instead, set the ``binary`` option: # config/packages/symfonycasts_tailwind.yaml symfonycasts_tailwind: binary: 'node_modules/.bin/tailwindcss' + +Using a Different Version +------------------------ + +By default the latest standalone Tailwind binary gets downloaded. However, +if you want to use a different version, you can specify the version to use, +set ``binary_version`` option: + +.. code-block:: yaml + + # config/packages/symfonycasts_tailwind.yaml + symfonycasts_tailwind: + binary_version: 'v3.3.0' diff --git a/src/DependencyInjection/TailwindExtension.php b/src/DependencyInjection/TailwindExtension.php index 131f86e..d8462b4 100644 --- a/src/DependencyInjection/TailwindExtension.php +++ b/src/DependencyInjection/TailwindExtension.php @@ -30,6 +30,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->findDefinition('tailwind.builder') ->replaceArgument(1, $config['input_css']) ->replaceArgument(3, $config['binary']) + ->replaceArgument(4, $config['binary_version']) ; } @@ -59,6 +60,9 @@ public function getConfigTreeBuilder(): TreeBuilder ->info('The tailwind binary to use instead of downloading a new one') ->defaultNull() ->end() + ->scalarNode('binary_version') + ->info('Tailwind CLI version to download - null means latest version') + ->defaultNull() ->end(); return $treeBuilder; diff --git a/src/TailwindBinary.php b/src/TailwindBinary.php index 6cd58dc..fabe6d5 100644 --- a/src/TailwindBinary.php +++ b/src/TailwindBinary.php @@ -21,13 +21,13 @@ */ class TailwindBinary { - private const VERSION = 'v3.3.5'; private HttpClientInterface $httpClient; public function __construct( private string $binaryDownloadDir, private string $cwd, private ?string $binaryPath, + private ?string $binaryVersion, private ?SymfonyStyle $output = null, HttpClientInterface $httpClient = null, ) { @@ -53,7 +53,7 @@ public function createProcess(array $arguments = []): Process private function downloadExecutable(): void { - $url = sprintf('https://github.com/tailwindlabs/tailwindcss/releases/download/%s/%s', self::VERSION, self::getBinaryName()); + $url = sprintf('https://github.com/tailwindlabs/tailwindcss/releases/download/%s/%s', $this->getVersion(), self::getBinaryName()); $this->output?->note(sprintf('Downloading TailwindCSS binary from %s', $url)); @@ -78,6 +78,14 @@ private function downloadExecutable(): void $progressBar?->setProgress($dlNow); }, ]); + + if (404 === $response->getStatusCode()) { + if (null !== $this->binaryVersion) { + throw new \Exception(sprintf('Cannot download Tailwind CLI binary. Please verify configured version `%s` exists for your machine.', $this->binaryVersion)); + } + throw new \Exception(sprintf('Cannot download latest Tailwind CLI binary. Response code: %d', $response->getStatusCode())); + } + $fileHandler = fopen($targetPath, 'w'); foreach ($this->httpClient->stream($response) as $chunk) { fwrite($fileHandler, $chunk->getContent()); @@ -89,6 +97,21 @@ private function downloadExecutable(): void chmod($targetPath, 0777); } + private function getVersion(): string + { + if ($this->binaryVersion) { + return $this->binaryVersion; + } + + try { + $response = $this->httpClient->request('GET', 'https://api.github.com/repos/tailwindlabs/tailwindcss/releases/latest'); + + return $response->toArray()['name'] ?? throw new \Exception('Cannot get vesion from response JSON.'); + } catch (\Throwable $e) { + throw new \Exception('Cannot determine latest Tailwind CLI binary version. Please specify a version in the configuration.', previous: $e); + } + } + /** * @internal */ diff --git a/src/TailwindBuilder.php b/src/TailwindBuilder.php index b2cec4d..a34ecb4 100644 --- a/src/TailwindBuilder.php +++ b/src/TailwindBuilder.php @@ -30,6 +30,7 @@ public function __construct( string $inputPath, private readonly string $tailwindVarDir, private readonly ?string $binaryPath = null, + private readonly ?string $binaryVersion = null, ) { if (is_file($inputPath)) { $this->inputPath = $inputPath; @@ -115,6 +116,6 @@ public function getOutputCssContent(): string private function createBinary(): TailwindBinary { - return new TailwindBinary($this->tailwindVarDir, $this->projectRootDir, $this->binaryPath, $this->output); + return new TailwindBinary($this->tailwindVarDir, $this->projectRootDir, $this->binaryPath, $this->binaryVersion, $this->output); } } diff --git a/tests/TailwindBinaryTest.php b/tests/TailwindBinaryTest.php index d28af54..baa8057 100644 --- a/tests/TailwindBinaryTest.php +++ b/tests/TailwindBinaryTest.php @@ -27,10 +27,11 @@ public function testBinaryIsDownloadedAndProcessCreated() $fs->mkdir($binaryDownloadDir); $client = new MockHttpClient([ + new MockResponse('{"name":"v3.3.6"}'), new MockResponse('fake binary contents'), ]); - $binary = new TailwindBinary($binaryDownloadDir, __DIR__, null, null, $client); + $binary = new TailwindBinary($binaryDownloadDir, __DIR__, null, null, null, $client); $process = $binary->createProcess(['-i', 'fake.css']); $this->assertFileExists($binaryDownloadDir.'/'.TailwindBinary::getBinaryName()); @@ -47,7 +48,7 @@ public function testCustomBinaryUsed() { $client = new MockHttpClient(); - $binary = new TailwindBinary('', __DIR__, 'custom-binary', null, $client); + $binary = new TailwindBinary('', __DIR__, 'custom-binary', null, null, $client); $process = $binary->createProcess(['-i', 'fake.css']); // on windows, arguments are not wrapped in quotes $expected = '\\' === \DIRECTORY_SEPARATOR ? 'custom-binary -i fake.css' : "'custom-binary' '-i' 'fake.css'";