Skip to content

Commit 4bb38df

Browse files
Add watch option (#18)
1 parent 246f2be commit 4bb38df

18 files changed

+433
-109
lines changed

config/services.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
abstract_arg('path to typescript files'),
1919
abstract_arg('path to compiled directory'),
2020
param('kernel.project_dir'),
21+
abstract_arg('path to the binaries download directory'),
2122
abstract_arg('path to the swc binary'),
23+
abstract_arg('path to the watchexec binary'),
2224
])
2325
->set('sensiolabs_typescript.command.build', TypeScriptBuildCommand::class)
2426
->args([

doc/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Then run the command:
4545
.. code-block:: terminal
4646
4747
# To compile only the typescript files
48-
$ php bin/console typescript:build
48+
$ php bin/console typescript:build --watch
4949
5050
# To compile ALL your assets
5151
$ php bin/console asset-map:compile

src/Command/TypeScriptBuildCommand.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Symfony\Component\Console\Attribute\AsCommand;
77
use Symfony\Component\Console\Command\Command;
88
use Symfony\Component\Console\Input\InputInterface;
9+
use Symfony\Component\Console\Input\InputOption;
910
use Symfony\Component\Console\Output\OutputInterface;
1011
use Symfony\Component\Console\Style\SymfonyStyle;
1112

@@ -21,22 +22,42 @@ public function __construct(
2122
parent::__construct();
2223
}
2324

25+
public function configure(): void
26+
{
27+
$this->addOption('watch', 'w', InputOption::VALUE_NONE, 'Watch for changes and recompile');
28+
}
29+
2430
public function execute(InputInterface $input, OutputInterface $output): int
2531
{
2632
$io = new SymfonyStyle($input, $output);
2733
$this->typeScriptBuilder->setOutput($io);
2834

29-
foreach ($this->typeScriptBuilder->runBuild() as $process) {
30-
$process->wait(function ($type, $buffer) use ($io) {
31-
$io->write($buffer);
32-
});
35+
$watch = $input->getOption('watch');
36+
$processes = [];
37+
foreach ($this->typeScriptBuilder->createAllBuildProcess($watch) as $process) {
38+
$processes[] = $process;
39+
}
40+
41+
if (0 === \count($processes)) {
42+
$io->success('No TypeScript files to compile');
43+
44+
return self::SUCCESS;
45+
}
3346

34-
if (!$process->isSuccessful()) {
35-
$io->error('TypeScript build failed');
47+
do {
48+
foreach ($processes as $index => $process) {
49+
if ($process->isRunning()) {
50+
continue;
51+
}
52+
if (!$process->isSuccessful()) {
53+
$io->error('TypeScript build failed');
3654

37-
return self::FAILURE;
55+
return self::FAILURE;
56+
}
57+
unset($processes[$index]);
3858
}
39-
}
59+
usleep(100000);
60+
} while (\count($processes) > 0);
4061

4162
return self::SUCCESS;
4263
}

src/DependencyInjection/SensiolabsTypeScriptExtension.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public function load(array $configs, ContainerBuilder $container): void
2828
$container->findDefinition('sensiolabs_typescript.builder')
2929
->replaceArgument(0, $config['source_dir'])
3030
->replaceArgument(1, '%kernel.project_dir%/var/typescript')
31-
->replaceArgument(3, $config['binary']);
31+
->replaceArgument(3, $config['binary_download_dir'])
32+
->replaceArgument(4, $config['swc_binary'])
33+
->replaceArgument(5, $config['watchexec_binary']);
3234

3335
$container->findDefinition('sensiolabs_typescript.js_asset_compiler')
3436
->replaceArgument(0, $config['source_dir'])
@@ -57,10 +59,19 @@ public function getConfigTreeBuilder(): TreeBuilder
5759
->end()
5860
->defaultValue(['%kernel.project_dir%/assets'])
5961
->end()
60-
->scalarNode('binary')
62+
->scalarNode('binary_download_dir')
63+
->info('The directory where the SWC and Watchexec binaries will be downloaded')
64+
->defaultValue('%kernel.project_dir%/var')
65+
->end()
66+
->scalarNode('swc_binary')
6167
->info('The SWC binary to use')
6268
->defaultNull()
63-
->end();
69+
->end()
70+
->scalarNode('watchexec_binary')
71+
->info('The Watchexec binary to use')
72+
->defaultNull()
73+
->end()
74+
;
6475

6576
return $treeBuilder;
6677
}

src/EventListener/PreAssetsCompileListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function __invoke(PreAssetsCompileEvent $event): void
1818
$output = new SymfonyStyle(new ArrayInput([]), $event->getOutput());
1919
$this->typeScriptBuilder
2020
->setOutput($output);
21-
foreach ($this->typeScriptBuilder->runBuild() as $process) {
21+
foreach ($this->typeScriptBuilder->createAllBuildProcess() as $process) {
2222
$process->wait(function ($type, $buffer) use ($output) {
2323
$output->write($buffer);
2424
});

src/Tools/TypeScriptBinary.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Sensiolabs\TypeScriptBundle\Tools;
4+
5+
use Symfony\Component\Process\Process;
6+
7+
class TypeScriptBinary
8+
{
9+
public function __construct(
10+
private readonly string $pathToExecutable,
11+
) {
12+
if (!file_exists($this->pathToExecutable)) {
13+
throw new \Exception(sprintf('The Typescript binary could not be found at the provided path : "%s"', $this->pathToExecutable));
14+
}
15+
}
16+
17+
/**
18+
* @param non-empty-list<string> $args
19+
*/
20+
public function createProcess(array $args): Process
21+
{
22+
array_unshift($args, $this->pathToExecutable);
23+
24+
return new Process($args);
25+
}
26+
}
Lines changed: 73 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,93 @@
11
<?php
22

3-
namespace Sensiolabs\TypeScriptBundle;
3+
namespace Sensiolabs\TypeScriptBundle\Tools;
44

5+
use Symfony\Component\Console\Output\OutputInterface;
56
use Symfony\Component\Console\Style\SymfonyStyle;
67
use Symfony\Component\HttpClient\HttpClient;
7-
use Symfony\Component\Process\Process;
88
use Symfony\Contracts\HttpClient\HttpClientInterface;
99

10-
class TypeScriptBinary
10+
class TypescriptBinaryFactory
1111
{
1212
private const VERSION = 'v1.3.92';
1313
private const SWC_RELEASE_URL_PATTERN = 'https://github.com/swc-project/swc/releases/download/%s/%s';
1414

1515
public function __construct(
1616
private readonly string $binaryDownloadDir,
17-
private readonly ?string $binaryPath,
18-
private ?SymfonyStyle $output = null,
1917
private ?HttpClientInterface $httpClient = null,
20-
private ?string $binaryName = null,
18+
private ?OutputInterface $output = null,
2119
) {
2220
$this->httpClient = $httpClient ?? HttpClient::create();
2321
}
2422

25-
/**
26-
* @param array<string> $args
27-
*/
28-
public function createProcess(array $args): Process
23+
public function getBinaryFromPath($pathToExecutable): TypeScriptBinary
2924
{
30-
if (null === $this->binaryPath) {
31-
$binary = $this->getDefaultBinaryPath();
32-
if (!is_file($binary)) {
33-
$this->downloadBinary();
34-
}
35-
} else {
36-
$binary = $this->binaryPath;
37-
}
38-
39-
array_unshift($args, $binary);
40-
41-
return new Process($args);
25+
return new TypeScriptBinary($pathToExecutable);
4226
}
4327

44-
public function downloadBinary(): void
28+
public function getBinaryFromServerSpecs($os, $machine, $kernel): TypeScriptBinary
4529
{
46-
$targetPath = $this->getDefaultBinaryPath();
47-
if (file_exists($targetPath)) {
48-
return;
49-
}
50-
51-
if (!is_dir($this->binaryDownloadDir)) {
52-
mkdir($this->binaryDownloadDir, 0777, true);
30+
$binaryName = self::getBinaryNameFromServerSpecs($os, $machine, $kernel);
31+
if (!file_exists($this->binaryDownloadDir.'/'.$binaryName)) {
32+
$this->downloadAndExtract($binaryName);
5333
}
5434

55-
$url = sprintf(self::SWC_RELEASE_URL_PATTERN, self::VERSION, $this->getBinaryName());
56-
$this->output?->note(sprintf('Downloading TypeScript binary to %s...', $targetPath));
57-
58-
$response = $this->httpClient->request('GET', $url, [
59-
'on_progress' => function (int $dlNow, int $dlSize, array $info) use (&$progressBar): void {
60-
if (0 === $dlSize) {
61-
return;
62-
}
63-
64-
if (!$progressBar) {
65-
$progressBar = $this->output?->createProgressBar($dlSize);
66-
}
35+
return $this->getBinaryFromPath($this->binaryDownloadDir.'/'.$binaryName);
36+
}
6737

68-
$progressBar?->setProgress($dlNow);
69-
},
70-
]);
71-
$fileHandler = fopen($targetPath, 'w');
72-
foreach ($this->httpClient->stream($response) as $chunk) {
73-
fwrite($fileHandler, $chunk->getContent());
74-
}
38+
public function setOutput(?SymfonyStyle $output): self
39+
{
40+
$this->output = $output;
7541

76-
fclose($fileHandler);
77-
chmod($targetPath, 7770);
78-
$progressBar?->finish();
79-
$this->output?->writeln('');
42+
return $this;
8043
}
8144

82-
private function getBinaryName(): string
45+
public function setHttpClient(HttpClientInterface $client): self
8346
{
84-
if (null !== $this->binaryName) {
85-
return $this->binaryName;
86-
}
87-
$os = strtolower(\PHP_OS);
88-
$machine = strtolower(php_uname('m'));
47+
$this->httpClient = $client;
8948

49+
return $this;
50+
}
51+
52+
public static function getBinaryNameFromServerSpecs(
53+
$os,
54+
$machine,
55+
$kernel,
56+
) {
57+
list($os, $machine, $kernel) = [strtolower($os), strtolower($machine), strtolower($kernel)];
9058
if (str_contains($os, 'darwin')) {
9159
if ('arm64' === $machine) {
92-
return $this->binaryName = 'swc-darwin-arm64';
60+
return 'swc-darwin-arm64';
9361
}
9462

9563
if ('x86_64' === $machine) {
96-
return $this->binaryName = 'swc-darwin-x64';
64+
return 'swc-darwin-x64';
9765
}
9866

9967
throw new \Exception(sprintf('No matching machine found for Darwin platform (Machine: %s).', $machine));
10068
}
10169

10270
if (str_contains($os, 'linux')) {
103-
$kernel = strtolower(php_uname('r'));
10471
$kernelVersion = str_contains($kernel, 'musl') ? 'musl' : 'gnu';
10572
if ('arm64' === $machine || 'aarch64' === $machine) {
106-
return $this->binaryName = 'swc-linux-arm64-'.$kernelVersion;
73+
return 'swc-linux-arm64-'.$kernelVersion;
10774
}
10875
if ('x86_64' === $machine) {
109-
return $this->binaryName = 'swc-linux-x64-'.$kernelVersion;
76+
return 'swc-linux-x64-'.$kernelVersion;
11077
}
11178

11279
throw new \Exception(sprintf('No matching machine found for Linux platform (Machine: %s).', $machine));
11380
}
11481

11582
if (str_contains($os, 'win')) {
11683
if ('x86_64' === $machine) {
117-
return $this->binaryName = 'swc-win32-x64-msvc';
84+
return 'swc-win32-x64-msvc';
11885
}
11986
if ('amd64' === $machine) {
120-
return $this->binaryName = 'swc-win32-arm64-msvc';
87+
return 'swc-win32-arm64-msvc';
12188
}
12289
if ('i586' === $machine) {
123-
return $this->binaryName = 'swc-win32-ia32-msvc';
90+
return 'swc-win32-ia32-msvc';
12491
}
12592

12693
throw new \Exception(sprintf('No matching machine found for Windows platform (Machine: %s).', $machine));
@@ -129,8 +96,45 @@ private function getBinaryName(): string
12996
throw new \Exception(sprintf('Unknown platform or architecture (OS: %s, Machine: %s).', $os, $machine));
13097
}
13198

132-
private function getDefaultBinaryPath(): string
99+
private function downloadAndExtract($binaryName): void
133100
{
134-
return $this->binaryDownloadDir.'/'.$this->getBinaryName();
101+
if (!is_dir($this->binaryDownloadDir)) {
102+
mkdir($this->binaryDownloadDir, 0777, true);
103+
}
104+
$targetPath = $this->binaryDownloadDir.'/'.$binaryName;
105+
if (file_exists($targetPath)) {
106+
return;
107+
}
108+
$url = sprintf(self::SWC_RELEASE_URL_PATTERN, self::VERSION, $binaryName);
109+
110+
if ($this->output?->isVerbose()) {
111+
$this->output?->note(sprintf('Downloading SWC binary from "%s" to "%s"...', $url, $targetPath));
112+
} else {
113+
$this->output?->note('Downloading SWC binary ...');
114+
}
115+
116+
$response = $this->httpClient->request('GET', $url, [
117+
'on_progress' => function (int $dlNow, int $dlSize, array $info) use (&$progressBar): void {
118+
if (0 === $dlSize) {
119+
return;
120+
}
121+
122+
if (!$progressBar) {
123+
$progressBar = $this->output?->createProgressBar($dlSize);
124+
}
125+
126+
$progressBar?->setProgress($dlNow);
127+
},
128+
]);
129+
$fileHandler = fopen($targetPath, 'w');
130+
foreach ($this->httpClient->stream($response) as $chunk) {
131+
fwrite($fileHandler, $chunk->getContent());
132+
}
133+
134+
fclose($fileHandler);
135+
$progressBar?->finish();
136+
$this->output?->writeln('');
137+
138+
chmod($this->binaryDownloadDir.'/'.$binaryName, 7770);
135139
}
136140
}

0 commit comments

Comments
 (0)