Skip to content

Commit 5a91cee

Browse files
committed
feat: implement SymlinkCleaner service and integrate into theme builders #88
1 parent 977bee0 commit 5a91cee

5 files changed

Lines changed: 122 additions & 3 deletions

File tree

src/Service/SymlinkCleaner.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenForgeProject\MageForge\Service;
6+
7+
use Magento\Framework\App\State;
8+
use Magento\Framework\Filesystem\Driver\File;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
11+
/**
12+
* Service for cleaning symlinks in theme CSS directories
13+
*
14+
* In developer mode, symlinks in {theme}/web/css/ can cause issues during
15+
* the build process. This service detects and removes all symlinks before
16+
* the build starts to ensure a clean workflow.
17+
*/
18+
class SymlinkCleaner
19+
{
20+
public function __construct(
21+
private readonly File $fileDriver,
22+
private readonly State $state
23+
) {
24+
}
25+
26+
/**
27+
* Remove all symlinks from {theme}/web/css/ directory in developer mode
28+
*
29+
* @param string $themePath Path to theme directory
30+
* @param SymfonyStyle $io Symfony style output
31+
* @param bool $isVerbose Whether to show verbose output
32+
* @return bool True on success or if no action needed, false on error
33+
*/
34+
public function cleanSymlinks(
35+
string $themePath,
36+
SymfonyStyle $io,
37+
bool $isVerbose
38+
): bool {
39+
try {
40+
// Only clean symlinks in developer mode
41+
if ($this->state->getMode() !== State::MODE_DEVELOPER) {
42+
return true;
43+
}
44+
45+
$cssPath = rtrim($themePath, '/') . '/web/css';
46+
47+
// Nothing to clean if directory doesn't exist
48+
if (!$this->fileDriver->isDirectory($cssPath)) {
49+
return true;
50+
}
51+
52+
$items = $this->fileDriver->readDirectory($cssPath);
53+
$deletedCount = 0;
54+
55+
foreach ($items as $item) {
56+
// Check if item is a symlink
57+
if (is_link($item)) {
58+
$this->fileDriver->deleteFile($item);
59+
$deletedCount++;
60+
61+
if ($isVerbose) {
62+
$io->writeln(sprintf(
63+
' <fg=yellow>⚠</> Removed symlink: %s',
64+
basename($item)
65+
));
66+
}
67+
}
68+
}
69+
70+
if ($deletedCount > 0 && $isVerbose) {
71+
$io->success(sprintf(
72+
'Removed %d symlink(s) from web/css/',
73+
$deletedCount
74+
));
75+
}
76+
77+
return true;
78+
} catch (\Exception $e) {
79+
// Don't fail the build process if symlink cleanup fails
80+
// Just warn the user and continue
81+
if ($isVerbose) {
82+
$io->warning(sprintf(
83+
'Could not clean symlinks: %s',
84+
$e->getMessage()
85+
));
86+
}
87+
return true;
88+
}
89+
}
90+
}

src/Service/ThemeBuilder/HyvaThemes/Builder.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use OpenForgeProject\MageForge\Service\CacheCleaner;
1010
use OpenForgeProject\MageForge\Service\StaticContentCleaner;
1111
use OpenForgeProject\MageForge\Service\StaticContentDeployer;
12+
use OpenForgeProject\MageForge\Service\SymlinkCleaner;
1213
use OpenForgeProject\MageForge\Service\ThemeBuilder\BuilderInterface;
1314
use Symfony\Component\Console\Output\OutputInterface;
1415
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -22,7 +23,8 @@ public function __construct(
2223
private readonly File $fileDriver,
2324
private readonly StaticContentDeployer $staticContentDeployer,
2425
private readonly StaticContentCleaner $staticContentCleaner,
25-
private readonly CacheCleaner $cacheCleaner
26+
private readonly CacheCleaner $cacheCleaner,
27+
private readonly SymlinkCleaner $symlinkCleaner
2628
) {
2729
}
2830

@@ -71,6 +73,11 @@ public function build(string $themeCode, string $themePath, SymfonyStyle $io, Ou
7173
return false;
7274
}
7375

76+
// Clean symlinks in web/css/ directory before build
77+
if (!$this->symlinkCleaner->cleanSymlinks($themePath, $io, $isVerbose)) {
78+
return false;
79+
}
80+
7481
if (!$this->generateHyvaConfig($io, $isVerbose)) {
7582
return false;
7683
}

src/Service/ThemeBuilder/MagentoStandard/Builder.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use OpenForgeProject\MageForge\Service\CacheCleaner;
1010
use OpenForgeProject\MageForge\Service\StaticContentCleaner;
1111
use OpenForgeProject\MageForge\Service\StaticContentDeployer;
12+
use OpenForgeProject\MageForge\Service\SymlinkCleaner;
1213
use OpenForgeProject\MageForge\Service\ThemeBuilder\BuilderInterface;
1314
use Symfony\Component\Console\Output\OutputInterface;
1415
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -22,7 +23,8 @@ public function __construct(
2223
private readonly File $fileDriver,
2324
private readonly StaticContentDeployer $staticContentDeployer,
2425
private readonly StaticContentCleaner $staticContentCleaner,
25-
private readonly CacheCleaner $cacheCleaner
26+
private readonly CacheCleaner $cacheCleaner,
27+
private readonly SymlinkCleaner $symlinkCleaner
2628
) {
2729
}
2830

@@ -52,6 +54,11 @@ public function build(string $themeCode, string $themePath, SymfonyStyle $io, Ou
5254
return false;
5355
}
5456

57+
// Clean symlinks in web/css/ directory before build
58+
if (!$this->symlinkCleaner->cleanSymlinks($themePath, $io, $isVerbose)) {
59+
return false;
60+
}
61+
5562
// Run grunt tasks
5663
try {
5764
if ($isVerbose) {

src/Service/ThemeBuilder/TailwindCSS/Builder.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use OpenForgeProject\MageForge\Service\CacheCleaner;
1010
use OpenForgeProject\MageForge\Service\StaticContentCleaner;
1111
use OpenForgeProject\MageForge\Service\StaticContentDeployer;
12+
use OpenForgeProject\MageForge\Service\SymlinkCleaner;
1213
use OpenForgeProject\MageForge\Service\ThemeBuilder\BuilderInterface;
1314
use Symfony\Component\Console\Output\OutputInterface;
1415
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -22,7 +23,8 @@ public function __construct(
2223
private readonly File $fileDriver,
2324
private readonly StaticContentDeployer $staticContentDeployer,
2425
private readonly StaticContentCleaner $staticContentCleaner,
25-
private readonly CacheCleaner $cacheCleaner
26+
private readonly CacheCleaner $cacheCleaner,
27+
private readonly SymlinkCleaner $symlinkCleaner
2628
) {
2729
}
2830

@@ -71,6 +73,11 @@ public function build(string $themeCode, string $themePath, SymfonyStyle $io, Ou
7173
return false;
7274
}
7375

76+
// Clean symlinks in web/css/ directory before build
77+
if (!$this->symlinkCleaner->cleanSymlinks($themePath, $io, $isVerbose)) {
78+
return false;
79+
}
80+
7481
// Build Hyva theme
7582
$tailwindPath = rtrim($themePath, '/') . '/web/tailwind';
7683
if (!$this->fileDriver->isDirectory($tailwindPath)) {

src/etc/di.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,12 @@
5656
<argument name="themeCleaner" xsi:type="object">OpenForgeProject\MageForge\Service\ThemeCleaner</argument>
5757
</arguments>
5858
</type>
59+
60+
<!-- Configure SymlinkCleaner Service -->
61+
<type name="OpenForgeProject\MageForge\Service\SymlinkCleaner">
62+
<arguments>
63+
<argument name="fileDriver" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
64+
<argument name="state" xsi:type="object">Magento\Framework\App\State</argument>
65+
</arguments>
66+
</type>
5967
</config>

0 commit comments

Comments
 (0)