Skip to content

Commit 25ed020

Browse files
dunglashenderkes
andauthored
feat: Windows support (#2119)
Closes #83 #880 #1286. Working patch for Windows support. Supports linking to the [official PHP release (TS version)](https://www.php.net/downloads.php). Includes some work from #1286 (thanks @TenHian!!) This patch allows using Visual Studio to compile the cgo code. To do so, it must be compiled with Go 1.26 (RC) with the following setup: ```powershell winget install -e --id Microsoft.VisualStudio.2022.Community --override "--passive --wait --add Microsoft.VisualStudio.Workload.NativeDesktop --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --includeRecommended" winget install -e --id GoLang.Go $env:PATH += ';C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin' cd c:\ gh repo clone microsoft/vcpkg .\vcpkg\bootstrap-vcpkg.bat .\vcpkg\vcpkg install pthreads brotli # build watcher Invoke-WebRequest -Uri "https://github.com/e-dant/watcher/releases/download/0.14.3/x86_64-pc-windows-msvc.tar" -OutFile "$env:TEMP\watcher.tar" tar -xf "$env:TEMP\watcher.tar" -C C:\ Rename-Item -Path "C:\x86_64-pc-windows-msvc" -NewName "watcher-x86_64-pc-windows-msvc" Remove-Item "$env:TEMP\watcher.tar" # download php Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php.zip" Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "C:\" Remove-Item "$env:TEMP\php.zip" # download php development package Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-devel-pack-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php-devel.zip" Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "C:\" Remove-Item "$env:TEMP\php-devel.zip" $env:GOTOOLCHAIN = 'go1.26rc1' $env:CC = 'clang' $env:CXX = 'clang++' $env:CGO_CFLAGS = "-I$env:C:\vcpkg\installed\x64-windows\include -IC:\watcher-x86_64-pc-windows-msvc -IC:\php-8.5.1-devel-vs17-x64\include -IC:\php-8.5.1-devel-vs17-x64\include\main -IC:\php-8.5.1-devel-vs17-x64\include\TSRM -IC:\php-8.5.1-devel-vs17-x64\include\Zend -IC:\php-8.5.1-devel-vs17-x64\include\ext" $env:CGO_LDFLAGS = '-LC:\vcpkg\installed\x64-windows\lib -lbrotlienc -LC:\watcher-x86_64-pc-windows-msvc -llibwatcher-c -LC:\php-8.5.1-Win32-vs17-x64 -LC:\php-8.5.1-devel-vs17-x64\lib -lphp8ts -lphp8embed' # clone frankenphp and build git clone -b windows https://github.com/php/frankenphp.git cd frankenphp\caddy\frankenphp go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx # Tests $env:PATH += ";$env:VCPKG_ROOT\installed\x64-windows\bin;C:\watcher-x86_64-pc-windows-msvc";C:\php-8.5.1-Win32-vs17-x64" "opcache.enable=0`r`nopcache.enable_cli=0" | Out-File -Encoding ascii php.ini $env:PHPRC = Get-Location go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx . ``` TODO: - [x] Fix remaining skipped tests (scaling and watcher) - [x] Test if the watcher mode works as expected - [x] Automate the build with GitHub Actions --------- Signed-off-by: Marc <m@pyc.ac> Co-authored-by: Kévin Dunglas <kevin@dunglas.dev> Co-authored-by: DubbleClick <m@pyc.ac>
1 parent 0ae3386 commit 25ed020

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+532
-149
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.github/workflows/static.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,12 @@ jobs:
453453
ref: ${{ needs.prepare.outputs.ref }}
454454
persist-credentials: false
455455
- uses: actions/setup-go@v6
456-
with:
456+
with: # zizmor: ignore[cache-poisoning]
457457
go-version: "1.26"
458458
cache-dependency-path: |
459459
go.sum
460460
caddy/go.sum
461-
cache: false
461+
cache: ${{ github.event_name != 'release' }}
462462
- name: Set FRANKENPHP_VERSION
463463
run: |
464464
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then

.github/workflows/tests.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
env:
3636
GOMAXPROCS: 10
3737
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
38+
GOFLAGS: "-tags=nobadger,nomysql,nopgx"
3839
steps:
3940
- uses: actions/checkout@v6
4041
with:
@@ -69,7 +70,7 @@ jobs:
6970
run: ./frankenphp.test -test.v
7071
- name: Run Caddy module tests
7172
working-directory: caddy/
72-
run: go test -tags nobadger,nomysql,nopgx -race -v ./...
73+
run: go test -race -v ./...
7374
- name: Run Fuzzing Tests
7475
working-directory: caddy/
7576
run: go test -fuzz FuzzRequest -fuzztime 20s
@@ -100,6 +101,8 @@ jobs:
100101
fail-fast: false
101102
matrix:
102103
php-versions: ["8.3", "8.4", "8.5"]
104+
env:
105+
XCADDY_GO_BUILD_FLAGS: "-tags=nobadger,nomysql,nopgx"
103106
steps:
104107
- uses: actions/checkout@v6
105108
with:
@@ -141,6 +144,7 @@ jobs:
141144
runs-on: macos-latest
142145
env:
143146
HOMEBREW_NO_AUTO_UPDATE: 1
147+
GOFLAGS: "-tags=nowatcher,nobadger,nomysql,nopgx"
144148
steps:
145149
- uses: actions/checkout@v6
146150
with:
@@ -172,4 +176,4 @@ jobs:
172176
run: go test -tags nowatcher -race -v ./...
173177
- name: Run Caddy module tests
174178
working-directory: caddy/
175-
run: go test -tags nowatcher,nobadger,nomysql,nopgx -race -v ./...
179+
run: go test -race -v ./...

.github/workflows/windows.yaml

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
name: Build Windows release
2+
3+
concurrency:
4+
cancel-in-progress: true
5+
group: ${{ github.workflow }}-${{ github.ref }}
6+
7+
on:
8+
pull_request:
9+
branches:
10+
- main
11+
paths:
12+
- "docker-bake.hcl"
13+
- "vcpkg.json"
14+
- ".github/workflows/static.yaml"
15+
- "**cgo.go"
16+
- "**Dockerfile"
17+
- "**.c"
18+
- "**.h"
19+
- "**.sh"
20+
- "**.stub.php"
21+
push:
22+
branches:
23+
- main
24+
tags:
25+
- v*.*.*
26+
workflow_dispatch:
27+
inputs:
28+
#checkov:skip=CKV_GHA_7
29+
version:
30+
description: "FrankenPHP version"
31+
required: false
32+
type: string
33+
schedule:
34+
- cron: "0 8 * * *"
35+
36+
permissions:
37+
contents: read
38+
39+
env:
40+
GOTOOLCHAIN: local
41+
GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx"
42+
PHP_DOWNLOAD_BASE: "https://downloads.php.net/~windows/releases/"
43+
CC: clang
44+
CXX: clang++
45+
46+
jobs:
47+
build:
48+
runs-on: windows-latest
49+
defaults:
50+
run:
51+
shell: powershell
52+
53+
steps:
54+
- name: Configure Git
55+
run: |
56+
git config --global core.autocrlf false
57+
git config --global core.eol lf
58+
59+
- name: Checkout Code
60+
uses: actions/checkout@v6
61+
with:
62+
path: frankenphp
63+
persist-credentials: false
64+
65+
- name: Set FRANKENPHP_VERSION
66+
run: |
67+
if ($env:GITHUB_REF_TYPE -eq "tag") {
68+
$frankenphpVersion = $env:GITHUB_REF_NAME.Substring(1)
69+
} elseif ($env:GITHUB_EVENT_NAME -eq "schedule") {
70+
$frankenphpVersion = $env:GITHUB_REF
71+
} else {
72+
$frankenphpVersion = $env:GITHUB_SHA
73+
}
74+
75+
echo "FRANKENPHP_VERSION=$frankenphpVersion" | Out-File -FilePath $env:GITHUB_ENV -Append
76+
77+
- name: Setup Go
78+
uses: actions/setup-go@v6
79+
with: # zizmor: ignore[cache-poisoning]
80+
go-version: "1.26"
81+
cache-dependency-path: |
82+
frankenphp/go.sum
83+
frankenphp/caddy/go.sum
84+
cache: ${{ github.event_name != 'release' }}
85+
check-latest: true
86+
87+
- name: Install Vcpkg Libraries
88+
working-directory: frankenphp
89+
run: "vcpkg install"
90+
91+
- name: Download Watcher
92+
run: |
93+
$latestTag = gh release list --repo e-dant/watcher --limit 1 --exclude-drafts --exclude-pre-releases --json tagName --jq '.[0].tagName'
94+
Write-Host "Latest Watcher version: $latestTag"
95+
96+
gh release download $latestTag --repo e-dant/watcher --pattern "*x86_64-pc-windows-msvc.tar" -O watcher.tar
97+
98+
tar -xf "watcher.tar" -C "$env:GITHUB_WORKSPACE"
99+
Rename-Item -Path "$env:GITHUB_WORKSPACE\x86_64-pc-windows-msvc" -NewName "watcher"
100+
env:
101+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
102+
103+
- name: Download PHP
104+
run: |
105+
$webContent = Invoke-WebRequest -Uri $env:PHP_DOWNLOAD_BASE -UseBasicParsing
106+
$links = $webContent.Links.Href | Where-Object { $_ -match "php-\d+\.\d+\.\d+-Win32-vs17-x64\.zip$" }
107+
108+
if (-not $links) { throw "Could not find PHP zip files at $env:PHP_DOWNLOAD_BASE" }
109+
110+
$latestFile = $links | Sort-Object { if ($_ -match '(\d+\.\d+\.\d+)') { [version]$matches[1] } } | Select-Object -Last 1
111+
112+
$version = if ($latestFile -match '(\d+\.\d+\.\d+)') { $matches[1] }
113+
Write-Host "Detected latest PHP version: $version"
114+
115+
"PHP_VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append
116+
117+
$phpZip = "php-$version-Win32-vs17-x64.zip"
118+
$develZip = "php-devel-pack-$version-Win32-vs17-x64.zip"
119+
120+
$dirName = "frankenphp-$env:FRANKENPHP_VERSION-php-$version-Win32-vs17-x64"
121+
122+
echo "DIR_NAME=$dirName" | Out-File -FilePath $env:GITHUB_ENV -Append
123+
124+
Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$phpZip" -OutFile "$env:TEMP\php.zip"
125+
Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "$env:GITHUB_WORKSPACE\$dirName"
126+
127+
Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$develZip" -OutFile "$env:TEMP\php-devel.zip"
128+
Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "$env:GITHUB_WORKSPACE\php-devel"
129+
130+
- name: Prepare env
131+
run: |
132+
$vcpkgRoot = "$env:GITHUB_WORKSPACE\frankenphp\vcpkg_installed\x64-windows"
133+
$watcherRoot = "$env:GITHUB_WORKSPACE\watcher"
134+
$phpBin = "$env:GITHUB_WORKSPACE\$env:DIR_NAME"
135+
$phpDevel = "$env:GITHUB_WORKSPACE\php-devel\php-$env:PHP_VERSION-devel-vs17-x64"
136+
137+
echo "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
138+
echo "$vcpkgRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
139+
echo "$watcherRoot" | Out-File -FilePath $env:GITHUB_PATH -Append
140+
echo "$phpBin" | Out-File -FilePath $env:GITHUB_PATH -Append
141+
142+
echo "CGO_CFLAGS=-DFRANKENPHP_VERSION=$env:FRANKENPHP_VERSION -I$vcpkgRoot\include -I$watcherRoot -I$phpDevel\include -I$phpDevel\include\main -I$phpDevel\include\TSRM -I$phpDevel\include\Zend -I$phpDevel\include\ext" | Out-File -FilePath $env:GITHUB_ENV -Append
143+
echo "CGO_LDFLAGS=-L$vcpkgRoot\lib -lbrotlienc -L$watcherRoot -llibwatcher-c -L$phpBin -L$phpDevel\lib -lphp8ts -lphp8embed" | Out-File -FilePath $env:GITHUB_ENV -Append
144+
145+
- name: Embed Windows icon and metadata
146+
working-directory: frankenphp\caddy\frankenphp
147+
run: |
148+
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest
149+
150+
$major = 0; $minor = 0; $patch = 0; $build = 0
151+
if ($env:FRANKENPHP_VERSION -match '^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$') {
152+
$major = [int]$Matches['major']
153+
$minor = [int]$Matches['minor']
154+
$patch = [int]$Matches['patch']
155+
}
156+
157+
$json = @{
158+
FixedFileInfo = @{
159+
FileVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $build }
160+
ProductVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $build }
161+
}
162+
StringFileInfo = @{
163+
CompanyName = "FrankenPHP"
164+
FileDescription = "The modern PHP app server"
165+
FileVersion = $env:FRANKENPHP_VERSION
166+
InternalName = "frankenphp"
167+
OriginalFilename = "frankenphp.exe"
168+
LegalCopyright = "(c) 2022 Kévin Dunglas, MIT License"
169+
ProductName = "FrankenPHP"
170+
ProductVersion = $env:FRANKENPHP_VERSION
171+
Comments = "https://frankenphp.dev/"
172+
}
173+
VarFileInfo = @{
174+
Translation = @{ LangID = 9; CharsetID = 1200 }
175+
}
176+
} | ConvertTo-Json -Depth 10
177+
[System.IO.File]::WriteAllText("versioninfo.json", $json, [System.Text.Encoding]::Default)
178+
179+
goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso
180+
181+
- name: Build FrankenPHP
182+
run: |
183+
$customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy"
184+
go build -ldflags="-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion' -X 'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy'"
185+
working-directory: frankenphp\caddy\frankenphp
186+
187+
- name: Create Directory
188+
run: |
189+
Copy-Item frankenphp\caddy\frankenphp\frankenphp.exe $env:DIR_NAME
190+
Copy-Item watcher\libwatcher-c.dll $env:DIR_NAME
191+
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlienc.dll $env:DIR_NAME
192+
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlidec.dll $env:DIR_NAME
193+
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll $env:DIR_NAME
194+
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll $env:DIR_NAME
195+
196+
- name: Upload Artifact
197+
if: github.event_name != 'release'
198+
uses: actions/upload-artifact@v6
199+
with:
200+
name: ${{ env.DIR_NAME }}
201+
path: ${{ env.DIR_NAME }}
202+
if-no-files-found: error
203+
204+
- name: Zip Release Artifact
205+
if: github.event_name == 'release'
206+
run: Compress-Archive -Path "$env:DIR_NAME\*" -DestinationPath "$env:DIR_NAME.zip"
207+
208+
- name: Upload Release Asset
209+
if: github.event_name == 'release'
210+
run: gh release upload $env:GITHUB_EVENT_RELEASE_TAG_NAME "$env:DIR_NAME.zip" --clobber
211+
env:
212+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
213+
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
214+
215+
- name: Run Tests
216+
run: |
217+
"opcache.enable=0`r`nopcache.enable_cli=0" | Out-File php.ini
218+
$env:PHPRC = Get-Location
219+
220+
go test ./...
221+
cd caddy
222+
go test ./...
223+
working-directory: ${{ github.workspace }}\frankenphp

caddy/caddy_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ func waitForServerReady(t *testing.T, url string) {
4242

4343
var testPort = "9080"
4444

45+
// skipIfSymlinkNotValid skips the test if the given path is not a valid symlink
46+
func skipIfSymlinkNotValid(t *testing.T, path string) {
47+
t.Helper()
48+
49+
info, err := os.Lstat(path)
50+
if err != nil {
51+
t.Skipf("symlink test skipped: cannot stat %s: %v", path, err)
52+
}
53+
54+
if info.Mode()&os.ModeSymlink == 0 {
55+
t.Skipf("symlink test skipped: %s is not a symlink (git may not support symlinks on this platform)", path)
56+
}
57+
}
58+
59+
// escapeMetricLabel escapes backslashes in label values for Prometheus text format
60+
func escapeMetricLabel(s string) string {
61+
return strings.ReplaceAll(s, "\\", "\\\\")
62+
}
63+
4564
func TestPHP(t *testing.T) {
4665
var wg sync.WaitGroup
4766
tester := caddytest.NewTester(t)
@@ -573,6 +592,7 @@ func TestWorkerMetrics(t *testing.T) {
573592
`, "caddyfile")
574593

575594
workerName, _ := fastabs.FastAbs("../testdata/index.php")
595+
workerName = escapeMetricLabel(workerName)
576596

577597
// Make some requests
578598
for i := range 10 {
@@ -760,6 +780,7 @@ func TestAutoWorkerConfig(t *testing.T) {
760780
`, "caddyfile")
761781

762782
workerName, _ := fastabs.FastAbs("../testdata/index.php")
783+
workerName = escapeMetricLabel(workerName)
763784

764785
// Make some requests
765786
for i := range 10 {
@@ -835,6 +856,7 @@ func TestAllDefinedServerVars(t *testing.T) {
835856
expectedBody = strings.ReplaceAll(expectedBody, "{documentRoot}", documentRoot)
836857
expectedBody = strings.ReplaceAll(expectedBody, "\r\n", "\n")
837858
expectedBody = strings.ReplaceAll(expectedBody, "{testPort}", testPort)
859+
expectedBody = strings.ReplaceAll(expectedBody, documentRoot+"/", documentRoot+string(filepath.Separator))
838860
tester := caddytest.NewTester(t)
839861
tester.InitServer(`
840862
{
@@ -1545,6 +1567,7 @@ func TestLog(t *testing.T) {
15451567
func TestSymlinkWorkerPaths(t *testing.T) {
15461568
cwd, _ := os.Getwd()
15471569
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
1570+
skipIfSymlinkNotValid(t, publicDir)
15481571

15491572
t.Run("NeighboringWorkerScript", func(t *testing.T) {
15501573
// Scenario: neighboring worker script
@@ -1680,6 +1703,7 @@ func TestSymlinkResolveRoot(t *testing.T) {
16801703
cwd, _ := os.Getwd()
16811704
testDir := filepath.Join(cwd, "..", "testdata", "symlinks", "test")
16821705
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
1706+
skipIfSymlinkNotValid(t, publicDir)
16831707

16841708
t.Run("ResolveRootSymlink", func(t *testing.T) {
16851709
// Tests that resolve_root_symlink directive works correctly
@@ -1738,6 +1762,7 @@ func TestSymlinkResolveRoot(t *testing.T) {
17381762
func TestSymlinkWorkerBehavior(t *testing.T) {
17391763
cwd, _ := os.Getwd()
17401764
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
1765+
skipIfSymlinkNotValid(t, publicDir)
17411766

17421767
t.Run("WorkerScriptFailsWithoutWorkerMode", func(t *testing.T) {
17431768
// Tests that accessing a worker-only script without configuring it as a worker actually results in an error

caddy/frankenphp/cbrotli.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build !nobrotli
2+
3+
package main
4+
5+
import _ "github.com/dunglas/caddy-cbrotli"

caddy/frankenphp/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
// plug in Caddy modules here.
77
_ "github.com/caddyserver/caddy/v2/modules/standard"
8-
_ "github.com/dunglas/caddy-cbrotli"
98
_ "github.com/dunglas/frankenphp/caddy"
109
_ "github.com/dunglas/mercure/caddy"
1110
_ "github.com/dunglas/vulcain/caddy"

caddy/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ require (
6464
github.com/dunglas/skipfilter v1.0.0 // indirect
6565
github.com/dunglas/vulcain v1.2.1 // indirect
6666
github.com/dustin/go-humanize v1.0.1 // indirect
67-
github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a // indirect
67+
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be // indirect
6868
github.com/felixge/httpsnoop v1.0.4 // indirect
6969
github.com/fsnotify/fsnotify v1.9.0 // indirect
7070
github.com/fxamacker/cbor/v2 v2.9.0 // indirect

caddy/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjsp
165165
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
166166
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
167167
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
168-
github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a h1:e/m9m8cJgjzw2Ol7tKTu4B/lM5F3Ym7ryKI+oyw0T8Y=
169-
github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM=
168+
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be h1:vqHrvilasyJcnru/0Z4FoojsQJUIfXGVplte7JtupfY=
169+
github.com/e-dant/watcher v0.0.0-20260223030516-06f84a1314be/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0=
170170
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
171171
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
172172
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=

0 commit comments

Comments
 (0)