cabloy 5.1.215 → 5.1.217

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/playwright-e2e.yml +163 -13
  3. package/.github/workflows/vona-test-sqlite3.yml +77 -7
  4. package/CHANGELOG.md +22 -0
  5. package/CONTRIBUTING.md +4 -2
  6. package/package.json +1 -1
  7. package/repo-docs/frontend/scripts.md +2 -0
  8. package/repo-docs/reference/repo-scripts.md +27 -1
  9. package/repo-e2e/scripts/e2e.test.ts +1 -1
  10. package/repo-e2e/scripts/managedTest.test.ts +115 -3
  11. package/repo-e2e/scripts/managedTest.ts +69 -20
  12. package/repo-e2e/scripts/managedTestImports.test.ts +55 -5
  13. package/repo-e2e/scripts/runE2e.test.ts +109 -0
  14. package/repo-e2e/scripts/runE2e.ts +58 -39
  15. package/repo-e2e/scripts/runE2eArgs.test.ts +16 -1
  16. package/repo-e2e/scripts/runE2eArgs.ts +5 -0
  17. package/repo-e2e/specs/a-commerce.spec.ts +10 -2
  18. package/repo-e2e/specs/home-user-account.spec.ts +4 -0
  19. package/repo-e2e/specs/markdown.spec.ts +14 -3
  20. package/scripts/generateOpenapiIsolated.test.ts +5 -1
  21. package/scripts/runIsolatedTest.test.ts +403 -36
  22. package/scripts/runIsolatedTest.ts +139 -12
  23. package/scripts/testBrowserGuardianWindows.test.ts +327 -0
  24. package/scripts/testClaimCleanup.test.ts +25 -9
  25. package/scripts/testClaimCleanup.ts +14 -45
  26. package/scripts/testClaimProcesses.test.ts +81 -2
  27. package/scripts/testClaimProcesses.ts +46 -10
  28. package/scripts/testProcessGuardianWindows.test.ts +602 -0
  29. package/scripts/testProcessGuardianWindows.ts +155 -0
  30. package/vona/packages-cli/cli/package.json +1 -1
  31. package/vona/packages-cli/cli-set-api/package.json +1 -1
  32. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.build.ts +4 -1
  33. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.masterDetail.ts +4 -4
  34. package/vona/packages-cli/cli-set-api/test/binBuildEntries.test.ts +159 -0
  35. package/vona/packages-vona/vona/package.json +1 -1
  36. package/vona/packages-vona/vona-core/package.json +1 -1
  37. package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +298 -17
  38. package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +205 -97
  39. package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts +247 -0
  40. package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.ts +224 -0
  41. package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.cs +229 -0
  42. package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ps1 +20 -0
  43. package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ts +200 -0
  44. package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.cs +336 -0
  45. package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ps1 +20 -0
  46. package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ts +531 -0
  47. package/vona/packages-vona/vona-core/src/lib/framework/bootstrap.test.ts +61 -0
  48. package/vona/packages-vona/vona-core/src/lib/framework/index.ts +1 -0
  49. package/vona/packages-vona/vona-core/src/lib/framework/processWorker.test.ts +59 -0
  50. package/vona/packages-vona/vona-core/src/lib/framework/processWorker.ts +8 -5
  51. package/vona/packages-vona/vona-core/src/lib/framework/useApp.test.ts +84 -0
  52. package/vona/packages-vona/vona-core/src/lib/framework/useApp.ts +2 -2
  53. package/vona/packages-vona/vona-core/src/lib/utils/util.ts +1 -1
  54. package/vona/packages-vona/vona-mock/package.json +1 -1
  55. package/vona/pnpm-lock.yaml +22 -22
  56. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  57. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  58. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  60. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +2 -1
  61. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  62. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +2 -2
  63. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts +16 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.ts +6 -3
  65. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +80 -0
  66. package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
  67. package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +73 -2
  68. package/vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts +309 -0
  69. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  70. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/transactionAsyncLocalStorage_.ts +3 -3
  71. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-static/package.json +1 -1
  73. package/vona/src/suite-vendor/a-vona/modules/a-static/src/bean/middlewareSystem.static.ts +2 -2
  74. package/vona/src/suite-vendor/a-vona/modules/a-static/test/middlewareSystem.static.test.ts +101 -0
  75. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  76. package/zova/packages-cli/cli/package.json +2 -2
  77. package/zova/packages-cli/cli-set-front/package.json +2 -2
  78. package/zova/packages-utils/zova-vite/package.json +1 -1
  79. package/zova/packages-utils/zova-vite/src/configUtils.ts +1 -1
  80. package/zova/packages-utils/zova-vite/test/configUtils.test.ts +55 -0
  81. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +8 -4
  82. package/zova/src/suite/a-commerce/modules/commerce-trade/test/lib/cartModel.test.ts +71 -0
  83. /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoBaseName%>.tsx_ → {{detailDtoBaseName}}.tsx_} +0 -0
  84. /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoMutateName%>.tsx_ → {{detailDtoMutateName}}.tsx_} +0 -0
  85. /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoResItemName%>.tsx_ → {{detailDtoResItemName}}.tsx_} +0 -0
  86. /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoViewName%>.tsx_ → {{detailDtoViewName}}.tsx_} +0 -0
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.215
1
+ 5.1.217
@@ -5,15 +5,24 @@ on:
5
5
  - main
6
6
  jobs:
7
7
  basic-e2e:
8
- runs-on: ubuntu-latest
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ os: [ubuntu-latest, windows-latest, macos-latest]
12
+ runs-on: ${{ matrix.os }}
9
13
  env:
10
14
  DATABASE_DEFAULT_CLIENT: sqlite3
11
15
  PNPM_CONFIG_MINIMUM_RELEASE_AGE: 0
12
16
  services:
13
17
  redis:
14
- image: redis:latest
18
+ image: ${{ matrix.os == 'ubuntu-latest' && 'redis:latest' || '' }}
15
19
  ports:
16
20
  - 6379:6379
21
+ options: >-
22
+ --health-cmd "redis-cli ping"
23
+ --health-interval 5s
24
+ --health-timeout 3s
25
+ --health-retries 12
17
26
  steps:
18
27
  - uses: actions/checkout@v6
19
28
  - uses: actions/setup-node@v6
@@ -22,32 +31,106 @@ jobs:
22
31
  - uses: pnpm/action-setup@v5
23
32
  with:
24
33
  version: 11.5.2
34
+ - name: start Redis on macOS
35
+ if: runner.os == 'macOS'
36
+ shell: bash
37
+ run: |
38
+ brew install redis
39
+ redis_bin="$(brew --prefix redis)/bin"
40
+ redis_log="$RUNNER_TEMP/redis.log"
41
+ if ! "$redis_bin/redis-server" --bind 127.0.0.1 --port 6379 \
42
+ --daemonize yes --save '' --appendonly no --logfile "$redis_log"; then
43
+ if [[ -f "$redis_log" ]]; then
44
+ cat "$redis_log"
45
+ fi
46
+ exit 1
47
+ fi
48
+ for attempt in {1..30}; do
49
+ if response="$("$redis_bin/redis-cli" -h 127.0.0.1 -p 6379 ping 2>&1)" &&
50
+ [[ "$response" == PONG ]]; then
51
+ exit 0
52
+ fi
53
+ sleep 1
54
+ done
55
+ printf '::error::Redis readiness failed: %s\n' "$response"
56
+ if [[ -f "$redis_log" ]]; then
57
+ cat "$redis_log"
58
+ fi
59
+ exit 1
60
+ - name: start Redis on Windows
61
+ if: runner.os == 'Windows'
62
+ shell: pwsh
63
+ run: |
64
+ $ErrorActionPreference = 'Stop'
65
+ # Pinned third-party development build; retain its bundled Cygwin runtime.
66
+ $archive = Join-Path $env:RUNNER_TEMP 'redis-8.2.10.zip'
67
+ $directory = Join-Path $env:RUNNER_TEMP 'redis-8.2.10'
68
+ Invoke-WebRequest 'https://github.com/redis-windows/redis-windows/releases/download/8.2.10/Redis-8.2.10-Windows-x64-cygwin.zip' -OutFile $archive
69
+ $expected = '92d9bdde87ed52bf859a677e5197d8ac855ba78d9940d457881723ae960df429'
70
+ if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected) {
71
+ throw 'Redis archive checksum mismatch'
72
+ }
73
+ Expand-Archive $archive -DestinationPath $directory
74
+ $servers = @(Get-ChildItem $directory -Recurse -Filter redis-server.exe)
75
+ if ($servers.Count -ne 1) { throw 'Expected one Redis server executable' }
76
+ $redisDirectory = $servers[0].DirectoryName
77
+ $cli = Join-Path $redisDirectory 'redis-cli.exe'
78
+ $config = Join-Path $redisDirectory 'ci.conf'
79
+ @('bind 127.0.0.1', 'port 6379', 'protected-mode yes', 'save ""', 'appendonly no', 'daemonize no', 'logfile redis.log') |
80
+ Set-Content $config -Encoding utf8NoBOM
81
+ $server = Start-Process $servers[0].FullName -ArgumentList 'ci.conf' -WorkingDirectory $redisDirectory -PassThru
82
+ for ($attempt = 0; $attempt -lt 30; $attempt++) {
83
+ $server.Refresh()
84
+ if ($server.HasExited) { break }
85
+ $response = & $cli -h 127.0.0.1 -p 6379 ping 2>&1
86
+ if ($LASTEXITCODE -eq 0 -and "$response".Trim() -eq 'PONG') { exit 0 }
87
+ Start-Sleep -Seconds 1
88
+ }
89
+ $log = Join-Path $redisDirectory 'redis.log'
90
+ if (Test-Path $log) { Get-Content $log }
91
+ throw 'Redis did not become ready'
25
92
  - name: init
26
93
  run: npm run init
27
- - name: install Chromium
94
+ - name: install Chromium with Linux dependencies
95
+ if: runner.os == 'Linux'
28
96
  run: npx playwright install --with-deps chromium
97
+ - name: install Chromium on Windows and macOS
98
+ if: runner.os != 'Linux'
99
+ run: npx playwright install chromium
100
+ - name: run focused managed E2E regression gate
101
+ run: node --test zova/packages-utils/zova-vite/test/configUtils.test.ts scripts/testClaimProcesses.test.ts scripts/runIsolatedTest.test.ts scripts/generateOpenapiIsolated.test.ts scripts/testClaimCleanup.test.ts scripts/testProcessGuardianWindows.test.ts scripts/testBrowserGuardianWindows.test.ts vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts vona/packages-vona/vona-core/src/lib/framework/processWorker.test.ts vona/src/suite-vendor/a-vona/modules/a-static/test/middlewareSystem.static.test.ts vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts repo-e2e/scripts/managedTest.test.ts repo-e2e/scripts/managedTestImports.test.ts repo-e2e/scripts/e2e.test.ts repo-e2e/scripts/runE2eArgs.test.ts repo-e2e/scripts/runE2e.test.ts
29
102
  - name: run Basic E2E baseline
30
103
  run: npm run test:e2e -- cabloy-basic home-user-account
31
104
  - name: upload Playwright artifacts
32
105
  if: failure()
33
106
  uses: actions/upload-artifact@v7
34
107
  with:
35
- name: playwright-artifacts-basic
108
+ name: playwright-artifacts-basic-${{ matrix.os }}
36
109
  path: |
37
- playwright-report/
38
- test-results/
110
+ vona/.app-test-*/playwright-report/
111
+ vona/.app-test-*/test-results/
112
+ include-hidden-files: true
39
113
  if-no-files-found: ignore
40
114
 
41
115
  commerce-e2e:
42
- runs-on: ubuntu-latest
116
+ strategy:
117
+ fail-fast: false
118
+ matrix:
119
+ os: [ubuntu-latest, windows-latest, macos-latest]
120
+ runs-on: ${{ matrix.os }}
43
121
  env:
44
122
  DATABASE_DEFAULT_CLIENT: sqlite3
45
123
  PNPM_CONFIG_MINIMUM_RELEASE_AGE: 0
46
124
  services:
47
125
  redis:
48
- image: redis:latest
126
+ image: ${{ matrix.os == 'ubuntu-latest' && 'redis:latest' || '' }}
49
127
  ports:
50
128
  - 6379:6379
129
+ options: >-
130
+ --health-cmd "redis-cli ping"
131
+ --health-interval 5s
132
+ --health-timeout 3s
133
+ --health-retries 12
51
134
  steps:
52
135
  - uses: actions/checkout@v6
53
136
  - uses: actions/setup-node@v6
@@ -56,22 +139,89 @@ jobs:
56
139
  - uses: pnpm/action-setup@v5
57
140
  with:
58
141
  version: 11.5.2
142
+ - name: start Redis on macOS
143
+ if: runner.os == 'macOS'
144
+ shell: bash
145
+ run: |
146
+ brew install redis
147
+ redis_bin="$(brew --prefix redis)/bin"
148
+ redis_log="$RUNNER_TEMP/redis.log"
149
+ if ! "$redis_bin/redis-server" --bind 127.0.0.1 --port 6379 \
150
+ --daemonize yes --save '' --appendonly no --logfile "$redis_log"; then
151
+ if [[ -f "$redis_log" ]]; then
152
+ cat "$redis_log"
153
+ fi
154
+ exit 1
155
+ fi
156
+ for attempt in {1..30}; do
157
+ if response="$("$redis_bin/redis-cli" -h 127.0.0.1 -p 6379 ping 2>&1)" &&
158
+ [[ "$response" == PONG ]]; then
159
+ exit 0
160
+ fi
161
+ sleep 1
162
+ done
163
+ printf '::error::Redis readiness failed: %s\n' "$response"
164
+ if [[ -f "$redis_log" ]]; then
165
+ cat "$redis_log"
166
+ fi
167
+ exit 1
168
+ - name: start Redis on Windows
169
+ if: runner.os == 'Windows'
170
+ shell: pwsh
171
+ run: |
172
+ $ErrorActionPreference = 'Stop'
173
+ # Pinned third-party development build; retain its bundled Cygwin runtime.
174
+ $archive = Join-Path $env:RUNNER_TEMP 'redis-8.2.10.zip'
175
+ $directory = Join-Path $env:RUNNER_TEMP 'redis-8.2.10'
176
+ Invoke-WebRequest 'https://github.com/redis-windows/redis-windows/releases/download/8.2.10/Redis-8.2.10-Windows-x64-cygwin.zip' -OutFile $archive
177
+ $expected = '92d9bdde87ed52bf859a677e5197d8ac855ba78d9940d457881723ae960df429'
178
+ if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected) {
179
+ throw 'Redis archive checksum mismatch'
180
+ }
181
+ Expand-Archive $archive -DestinationPath $directory
182
+ $servers = @(Get-ChildItem $directory -Recurse -Filter redis-server.exe)
183
+ if ($servers.Count -ne 1) { throw 'Expected one Redis server executable' }
184
+ $redisDirectory = $servers[0].DirectoryName
185
+ $cli = Join-Path $redisDirectory 'redis-cli.exe'
186
+ $config = Join-Path $redisDirectory 'ci.conf'
187
+ @('bind 127.0.0.1', 'port 6379', 'protected-mode yes', 'save ""', 'appendonly no', 'daemonize no', 'logfile redis.log') |
188
+ Set-Content $config -Encoding utf8NoBOM
189
+ $server = Start-Process $servers[0].FullName -ArgumentList 'ci.conf' -WorkingDirectory $redisDirectory -PassThru
190
+ for ($attempt = 0; $attempt -lt 30; $attempt++) {
191
+ $server.Refresh()
192
+ if ($server.HasExited) { break }
193
+ $response = & $cli -h 127.0.0.1 -p 6379 ping 2>&1
194
+ if ($LASTEXITCODE -eq 0 -and "$response".Trim() -eq 'PONG') { exit 0 }
195
+ Start-Sleep -Seconds 1
196
+ }
197
+ $log = Join-Path $redisDirectory 'redis.log'
198
+ if (Test-Path $log) { Get-Content $log }
199
+ throw 'Redis did not become ready'
59
200
  - name: init
60
201
  run: npm run init
61
- - name: install Chromium
202
+ - name: install Chromium with Linux dependencies
203
+ if: runner.os == 'Linux'
62
204
  run: npx playwright install --with-deps chromium
205
+ - name: install Chromium on Windows and macOS
206
+ if: runner.os != 'Linux'
207
+ run: npx playwright install chromium
208
+ - name: run focused managed E2E regression gate
209
+ run: node --test zova/packages-utils/zova-vite/test/configUtils.test.ts scripts/testClaimProcesses.test.ts scripts/runIsolatedTest.test.ts scripts/generateOpenapiIsolated.test.ts scripts/testClaimCleanup.test.ts scripts/testProcessGuardianWindows.test.ts scripts/testBrowserGuardianWindows.test.ts vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts vona/packages-vona/vona-core/src/lib/framework/processWorker.test.ts vona/src/suite-vendor/a-vona/modules/a-static/test/middlewareSystem.static.test.ts vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts repo-e2e/scripts/managedTest.test.ts repo-e2e/scripts/managedTestImports.test.ts repo-e2e/scripts/e2e.test.ts repo-e2e/scripts/runE2eArgs.test.ts repo-e2e/scripts/runE2e.test.ts
210
+ - name: run Commerce cart model regression
211
+ run: node --test zova/src/suite/a-commerce/modules/commerce-trade/test/lib/cartModel.test.ts
63
212
  - name: build Commerce Zova artifacts
64
213
  run: npm run build:zova:commerce
65
214
  - name: synchronize Vona dependencies
66
215
  run: npm run deps:vona
67
216
  - name: run Commerce E2E baseline
68
- run: npm run test:e2e a-commerce markdown
217
+ run: npm run test:e2e -- a-commerce markdown
69
218
  - name: upload Playwright artifacts
70
219
  if: failure()
71
220
  uses: actions/upload-artifact@v7
72
221
  with:
73
- name: playwright-artifacts-commerce
222
+ name: playwright-artifacts-commerce-${{ matrix.os }}
74
223
  path: |
75
- playwright-report/
76
- test-results/
224
+ vona/.app-test-*/playwright-report/
225
+ vona/.app-test-*/test-results/
226
+ include-hidden-files: true
77
227
  if-no-files-found: ignore
@@ -8,15 +8,22 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
+ os: [ubuntu-latest, windows-latest, macos-latest]
11
12
  node-version: [24]
12
- runs-on: ubuntu-latest
13
+ runs-on: ${{ matrix.os }}
13
14
  env:
14
15
  PNPM_CONFIG_MINIMUM_RELEASE_AGE: 0
16
+ DATABASE_DEFAULT_CLIENT: sqlite3
15
17
  services:
16
18
  redis:
17
- image: redis:latest
19
+ image: ${{ matrix.os == 'ubuntu-latest' && 'redis:latest' || '' }}
18
20
  ports:
19
21
  - 6379:6379
22
+ options: >-
23
+ --health-cmd "redis-cli ping"
24
+ --health-interval 5s
25
+ --health-timeout 3s
26
+ --health-retries 12
20
27
  steps:
21
28
  - uses: actions/checkout@v6
22
29
  - uses: actions/setup-node@v6
@@ -25,19 +32,82 @@ jobs:
25
32
  - uses: pnpm/action-setup@v5
26
33
  with:
27
34
  version: 11.5.2
35
+ - name: start Redis on macOS
36
+ if: runner.os == 'macOS'
37
+ shell: bash
38
+ run: |
39
+ brew install redis
40
+ redis_bin="$(brew --prefix redis)/bin"
41
+ redis_log="$RUNNER_TEMP/redis.log"
42
+ if ! "$redis_bin/redis-server" --bind 127.0.0.1 --port 6379 \
43
+ --daemonize yes --save '' --appendonly no --logfile "$redis_log"; then
44
+ if [[ -f "$redis_log" ]]; then
45
+ cat "$redis_log"
46
+ fi
47
+ exit 1
48
+ fi
49
+ for attempt in {1..30}; do
50
+ if response="$("$redis_bin/redis-cli" -h 127.0.0.1 -p 6379 ping 2>&1)" &&
51
+ [[ "$response" == PONG ]]; then
52
+ exit 0
53
+ fi
54
+ sleep 1
55
+ done
56
+ printf '::error::Redis readiness failed: %s\n' "$response"
57
+ if [[ -f "$redis_log" ]]; then
58
+ cat "$redis_log"
59
+ fi
60
+ exit 1
61
+ - name: start Redis on Windows
62
+ if: runner.os == 'Windows'
63
+ shell: pwsh
64
+ run: |
65
+ $ErrorActionPreference = 'Stop'
66
+ # Pinned third-party development build; retain its bundled Cygwin runtime.
67
+ $archive = Join-Path $env:RUNNER_TEMP 'redis-8.2.10.zip'
68
+ $directory = Join-Path $env:RUNNER_TEMP 'redis-8.2.10'
69
+ Invoke-WebRequest 'https://github.com/redis-windows/redis-windows/releases/download/8.2.10/Redis-8.2.10-Windows-x64-cygwin.zip' -OutFile $archive
70
+ $expected = '92d9bdde87ed52bf859a677e5197d8ac855ba78d9940d457881723ae960df429'
71
+ if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected) {
72
+ throw 'Redis archive checksum mismatch'
73
+ }
74
+ Expand-Archive $archive -DestinationPath $directory
75
+ $servers = @(Get-ChildItem $directory -Recurse -Filter redis-server.exe)
76
+ if ($servers.Count -ne 1) { throw 'Expected one Redis server executable' }
77
+ $redisDirectory = $servers[0].DirectoryName
78
+ $cli = Join-Path $redisDirectory 'redis-cli.exe'
79
+ $config = Join-Path $redisDirectory 'ci.conf'
80
+ @('bind 127.0.0.1', 'port 6379', 'protected-mode yes', 'save ""', 'appendonly no', 'daemonize no', 'logfile redis.log') |
81
+ Set-Content $config -Encoding utf8NoBOM
82
+ $server = Start-Process $servers[0].FullName -ArgumentList 'ci.conf' -WorkingDirectory $redisDirectory -PassThru
83
+ for ($attempt = 0; $attempt -lt 30; $attempt++) {
84
+ $server.Refresh()
85
+ if ($server.HasExited) { break }
86
+ $response = & $cli -h 127.0.0.1 -p 6379 ping 2>&1
87
+ if ($LASTEXITCODE -eq 0 -and "$response".Trim() -eq 'PONG') { exit 0 }
88
+ Start-Sleep -Seconds 1
89
+ }
90
+ $log = Join-Path $redisDirectory 'redis.log'
91
+ if (Test-Path $log) { Get-Content $log }
92
+ throw 'Redis did not become ready'
28
93
  - name: init
29
94
  run: npm run init
95
+ - name: run isolated test infrastructure gate
96
+ run: node --test scripts/testClaimProcesses.test.ts scripts/runIsolatedTest.test.ts scripts/testClaimCleanup.test.ts scripts/testProcessGuardianWindows.test.ts vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts vona/packages-vona/vona-core/src/lib/framework/bootstrap.test.ts vona/packages-vona/vona-core/src/lib/framework/useApp.test.ts vona/packages-cli/cli-set-api/test/binBuildEntries.test.ts
30
97
  - name: run A-Pay focused gate
31
- run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run vona :bin:test -- test-pay/test/paymentSession.test.ts test-pay/test/payMock.test.ts test-pay/test/paypalProvider.test.ts test-pay/test/paypalLifecycle.test.ts test-pay/test/stripeProvider.test.ts test-pay/test/stripeLifecycle.test.ts test-pay/test/outbox.test.ts test-pay/test/webhook.test.ts --flavor=normal
98
+ run: npm run test -- test-pay/test/paymentSession.test.ts test-pay/test/payMock.test.ts test-pay/test/paypalProvider.test.ts test-pay/test/paypalLifecycle.test.ts test-pay/test/stripeProvider.test.ts test-pay/test/stripeLifecycle.test.ts test-pay/test/outbox.test.ts test-pay/test/webhook.test.ts
32
99
  working-directory: vona
33
100
  - name: run Commerce payment, shipment, and refund lifecycle gate
34
- run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run vona :bin:test -- commerce-trade/test/paymentOutcome.test.ts commerce-trade/test/reservationExpiry.test.ts commerce-trade/test/shipment.test.ts commerce-trade/test/refundLifecycle.test.ts --flavor=normal
101
+ run: npm run test -- commerce-trade/test/paymentOutcome.test.ts commerce-trade/test/reservationExpiry.test.ts commerce-trade/test/shipment.test.ts commerce-trade/test/refundLifecycle.test.ts
35
102
  working-directory: vona
36
- - run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run test
103
+ - run: npm run test
37
104
  working-directory: vona
38
- - run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run build -- --flavor=ci
105
+ - run: npm run build -- --flavor=ci
39
106
  working-directory: vona
107
+ env:
108
+ # The full backend bundle exceeds the macOS runner's default 2 GiB V8 heap.
109
+ NODE_OPTIONS: --max-old-space-size=4096
40
110
  - run: pnpm -C ./dist/ci install
41
111
  working-directory: vona
42
- - run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run play -- --mode=prod --flavor=ci
112
+ - run: npm run play -- --mode=prod --flavor=ci
43
113
  working-directory: vona
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.217
4
+
5
+ ### Features
6
+
7
+ - Add end-to-end testing on Windows.
8
+
9
+ ### Improvements
10
+
11
+ - Improve Windows support and end-to-end test coverage.
12
+ - Update the SQLite test workflow.
13
+ - Update claim tests.
14
+
15
+ ## 5.1.216
16
+
17
+ ### Features
18
+
19
+ - Apply feature updates.
20
+
21
+ ### Improvements
22
+
23
+ - Update the agent governance configuration.
24
+
3
25
  ## 5.1.215
4
26
 
5
27
  ### Features
package/CONTRIBUTING.md CHANGED
@@ -21,7 +21,7 @@ npm run init
21
21
 
22
22
  `npm run init` is more than a package install: it installs multiple workspaces, prepares generated local configuration, initializes and synchronizes Vona and Zova, and generates required frontend and backend artifacts. Run it intentionally rather than as a retry for an unrelated narrow change.
23
23
 
24
- Current CI workflows run on Ubuntu with Node.js 24 after pushes to `main`; do not assume that every pull request is automatically validated. For maintained edition and onboarding guidance, see [Cabloy Editions](https://cabloy.com/editions/overview#cabloy-basic).
24
+ CI workflows run after pushes to `main`; do not assume that every pull request is automatically validated. The SQLite unit-test and Chromium Basic/Commerce E2E workflows configure Ubuntu, Windows, and macOS with Node.js 24 and pnpm 11.5.2. Matrix configuration alone is not proof that the platform gates or all six Basic/Commerce E2E combinations passed. For maintained edition and onboarding guidance, see [Cabloy Editions](https://cabloy.com/editions/overview#cabloy-basic).
25
25
 
26
26
  ## Develop and validate
27
27
 
@@ -44,7 +44,9 @@ Start with the narrowest meaningful check, then broaden validation when a change
44
44
  | Synchronize Zova dependencies into Vona | `npm run deps:vona` |
45
45
  | Build the public documentation site | `npm run docs:build` |
46
46
 
47
- `npm run test:e2e` starts a fresh runner-managed local Vona worker in test mode and runs Playwright against Vona integrated SSR. Select flat spec basenames directly after the script, for example `npm run test:e2e a-commerce`; place Playwright filters after npm's `--` delimiter, for example `npm run test:e2e a-commerce -- --tag @smoke`. Browser commands consume existing SSR/REST artifacts and do not rebuild them. Confirm exclusive ownership of the effective test database, local Redis namespace, public/runtime paths, application identity, API origin, and listener before starting a test-mode runtime; an available port alone is not sufficient. Externally managed `E2E_BASE_URL` targets are unsupported.
47
+ `npm run test:e2e` starts a fresh runner-managed local Vona worker in test mode and runs Playwright against Vona integrated SSR. Select flat spec basenames directly after the script, for example `npm run test:e2e a-commerce`; place Playwright filters after npm's `--` delimiter, for example `npm run test:e2e a-commerce -- --tag @smoke`. Browser commands consume existing SSR/REST artifacts and do not rebuild them. The managed runner allocates run-owned database and Redis namespaces, runtime/public paths, application identity, API origin, and listener, so ordinary same-checkout development services are not a blocker by themselves. Coordinate shared build/dependency writes and check custom external resources separately; a free port alone is not ownership proof. Externally managed `E2E_BASE_URL` targets are unsupported.
48
+
49
+ For native Windows managed tests, use the same public npm commands from CMD or PowerShell. PowerShell 7 on `PATH`, local NTFS storage, and reachable Redis are required; E2E additionally needs installed Chromium (`npx playwright install chromium`) and ready SSR/REST artifacts. Windows workers and browsers must prove membership in their designated registered Job, and normal Vona teardown uses authenticated cooperative shutdown before whole-Job drainage is accepted. POSIX browsers retain independent process-group verification. Failed or interrupted runs retain claims and resources for inspection; force-stop or missing PIDs do not establish successful cleanup. See [Native Windows managed tests](https://cabloy.com/reference/repo-scripts#native-windows-managed-tests) for the lifecycle and limits: the browser scope is Cabloy Basic Chromium Basic/Commerce baselines, not all browsers, documentation E2E, or Cabloy Start.
48
50
 
49
51
  The tracked pre-commit hook runs `lint-staged`. It may format staged Markdown and configuration files, and may lint-fix and format staged source files. Review the resulting staged changes and run the relevant explicit checks before submitting.
50
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.215",
3
+ "version": "5.1.217",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -79,6 +79,8 @@ npm run test:e2e a-commerce -- --tag @web --tag @smoke
79
79
 
80
80
  For the complete Basic tag vocabulary and managed-runner argument boundaries, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks). `E2E_BASE_URL` must be unset: externally managed targets are unsupported.
81
81
 
82
+ Cabloy Basic's Chromium Basic/Commerce CI baselines configure Ubuntu, Windows, and macOS; a matrix declaration is not evidence that all six combinations passed. On native Windows, public commands work from CMD or PowerShell and require PowerShell 7 on `PATH`, local NTFS storage, reachable Redis, installed Chromium, and ready paired SSR/REST artifacts. Managed browsers inherit a registered non-breakaway Windows Job; both worker and browser membership are verified through its owning helper, and cleanup requires whole-Job drainage rather than browser close alone. Normal Windows server teardown uses authenticated cooperative shutdown; failures or interruption retain claims and resources. POSIX browsers retain independent process-group verification. See [Native Windows managed tests](/reference/repo-scripts#native-windows-managed-tests) for the prerequisites, lifecycle, and coverage limits; this is not a claim about Cabloy Start's Windows browser support.
83
+
82
84
  ## Cabloy Start root wrappers
83
85
 
84
86
  Cabloy Start is the public MIT-licensed edition. Its default root wrappers use the `cabloyStartAdmin` and `cabloyStartWeb` flavors:
@@ -60,7 +60,7 @@ Root `npm run test` delegates to Vona's `npm run test`; both it and direct `cd v
60
60
 
61
61
  Claims contain a versioned, nonsecret resource inventory: owned local roots, exact database allocation intents, Redis namespaces and endpoint identities, process ownership, and cleanup progress. Allocators must receive an acknowledgment that their intent is recorded before creating a database or using framework-managed Redis resources. Credentials, full environment/configuration snapshots, and readiness secrets are not stored in claims.
62
62
 
63
- Successful unit, coverage, and managed application E2E runs release inventoried database and Redis resources and remove their owned local roots after process shutdown is verified. Managed E2E uses a registered browser fixture to verify detached browser-group shutdown; its run-owned reports, traces, and temporary profiles are deleted on success too. The claim is deleted last. Failed or interrupted runs retain their claim and resources for inspection. Cleanup failure also makes the command fail and retains the journal for retry.
63
+ Successful unit, coverage, and managed application E2E runs release inventoried database and Redis resources and remove their owned local roots after process shutdown is verified. Managed E2E uses a registered browser fixture: POSIX browsers have independently verified process groups, while Windows workers and browsers must prove membership in the designated registered guardian Job. Cleanup requires verified process-group or whole-Job drainage, respectively; browser close or a missing PID is not sufficient proof. Run-owned reports, traces, and temporary profiles are deleted on success too. The claim is deleted last. Failed or interrupted runs retain their claim and resources for inspection. Cleanup failure also makes the command fail and retains the journal for retry.
64
64
 
65
65
  Before each new invocation, the runner checks retained claims against their immutable UTC creation timestamp. Claims older than one day are eligible for cleanup; set `VONA_TEST_CLAIM_TTL_SECONDS` to a positive integer to change the default `86400` seconds. Eligibility is not permission to delete a live or unowned resource: the runner retains malformed claims, mismatched owner markers, symlinked paths, changed service endpoints, live or uncertain process identities, incomplete launch registrations, and claims with an existing cleanup lock. Inspect these warnings manually; a lock is never taken over merely because it is old. Cleanup resolves current trusted configuration without starting the application, running seeds, or opening a listener. It drops only recorded database names and scans only exact recorded Redis prefixes, never flushing Redis. Partial progress is journaled so a later eligible cleanup can retry safely. Legacy `.vona-test-claim-<id>` directories and inventory-less run roots are not automatically migrated or deleted.
66
66
 
@@ -86,6 +86,32 @@ For Vona integrated SSR, the allocated E2E origin is supplied to Zova at runtime
86
86
 
87
87
  For deliberate **dev-mode** database preparation, `npm run db:reset` is a separate, destructive command: in a single process, it clears validated application-owned Redis keys, drops and recreates managed disposable development databases, reruns migrations and instance initialization, then runs module `seed()` hooks. It requires `database.testDatabase: true` in the dev configuration and a connection outside the disposable database. Ordinary `npm run dev` and `npm run dev:one` only reuse existing managed databases; if any required target is missing, startup fails promptly with an instruction to run `npm run db:reset`. They do not create databases, perform creation-time Redis cleanup, or seed. Never run `db:reset` against development data you need to retain. Upgrades do not reset existing development data or repeat its seeds. See [Development database preparation](#development-database-preparation) and [Migration and Changes](/backend/migration-and-changes#local-development-workflow).
88
88
 
89
+ ### Native Windows managed tests {#native-windows-managed-tests}
90
+
91
+ <span id="native-windows-unit-tests"></span>
92
+
93
+ The Cabloy Basic SQLite unit-test workflow and Chromium application E2E workflow configure native Windows runners alongside Ubuntu and macOS. Each E2E suite has its own three-OS matrix with `fail-fast: false`, Node.js 24, pnpm 11.5.2, and SQLite. Basic selects `cabloy-basic home-user-account`; Commerce selects `a-commerce markdown` and retains its explicit Commerce artifact build and Vona dependency synchronization. A configured matrix is not proof of a successful run: inspect the focused gates, real Windows Chromium lifecycle regressions, and all six hosted Basic/Commerce suite-and-OS results before claiming verified platform compatibility.
94
+
95
+ On Windows, the managed test entrypoints require Node.js with npm, PowerShell 7 (`pwsh.exe` on `PATH`), local NTFS storage, and a reachable Redis service. E2E also requires Playwright's Chromium installation and current SSR/REST artifacts with Vona dependencies synchronized. PowerShell is an internal helper dependency, not a required interactive terminal. From either CMD or PowerShell, use the same public commands:
96
+
97
+ ```text
98
+ npm run test
99
+ npx playwright install chromium
100
+ npm run test:e2e -- cabloy-basic home-user-account
101
+ ```
102
+
103
+ The Node runner explicitly invokes its checked-in Windows helper; it does not interpret PowerShell code in the caller's shell or download a helper runtime. It uses a non-breakaway Windows Job Object to contain the test process tree, with job membership established at process creation and kill-on-close ownership. Claim directories and files use protected private Windows ACLs and reject unsafe reparse points and hardlinks. Flushed file contents and same-volume atomic updates provide the supported local-NTFS process-crash publication contract; this is not a claim of POSIX directory-fsync equivalence or power-loss durability.
104
+
105
+ A missing native capability, unsafe claim storage, ambiguous process identity, or unproven job drainage fails closed. Failed/interrupted runs retain their claims; helper disappearance alone never authorizes cleanup. Do not bypass these checks with raw test commands or remove retained resources without verifying ownership. The npm CLI is resolved from the npm invocation or its standard location beside the Node installation; a pnpm executable is not substituted for npm's JavaScript entrypoint.
106
+
107
+ The Windows CI Redis setup uses a pinned, checksum-verified [third-party Redis Windows development distribution](https://github.com/redis-windows/redis-windows/releases/tag/8.2.10) with its bundled Cygwin runtime, bound to loopback. This is not an official Redis Windows port or a production deployment recommendation. Linux CI uses a Redis container with a health check; macOS starts native loopback Redis with bounded readiness polling. The native setups do not install persistent services. Local users must provision Redis deliberately; neither managed test command installs it. Chromium's `--with-deps` installation is Linux-only in CI; Windows and macOS use `playwright install chromium`.
108
+
109
+ For Windows E2E, the managed fixture verifies the Playwright worker's membership before browser launch, then verifies the actual browser child's membership before connecting. The claim broker authenticates the query and asks the live helper owning that registered Job to check exact PID/creation-time identity and membership in that specific Job. An inherited registration locator, an environment flag, or membership in some other Job is not ownership proof. The Job handle stays private, and Windows browsers do not fabricate POSIX process-group claims. Inherited or custom `TEMP`, `TMP`, and `TMPDIR` names are handled case-insensitively and normalized to the run-owned temporary root.
110
+
111
+ After authenticated readiness, normal Windows Vona teardown uses an authenticated, nonce-bound loopback shutdown request and acknowledgment, then awaits a zero server exit and the helper's whole-Job drainage proof. This path reuses the worker's awaited application-close lifecycle; it does not emulate POSIX graceful signals. Refusal, invalid proof, timeout, interruption, unexpected exit, or uncertain drainage fails the run and invokes failure-stop where possible. Force-stop is never reclassified as successful teardown. Controller/helper death and cancellation retain claims and resources; disappearing PIDs alone never authorize cleanup.
112
+
113
+ The supported application browser scope here is Cabloy Basic's Chromium Basic/Commerce baselines, not every browser, documentation E2E, Cabloy Start, or generic POSIX signal parity. The E2E workflow uploads failure-only reports from `vona/.app-test-*/playwright-report/` and `vona/.app-test-*/test-results/`, including hidden run directories, under suite-and-OS-specific artifact names. It never uploads whole run roots. Failed runs retain local claims/resources for inspection, while successful runs remove their run-owned reports and profiles along with other inventoried resources.
114
+
89
115
  ## Specification planning and derived charts
90
116
 
91
117
  The current Cabloy Basic root scripts also expose:
@@ -17,7 +17,7 @@ test('rejects a defined E2E_BASE_URL before loading local configuration', () =>
17
17
  });
18
18
 
19
19
  test('isolated E2E target follows the assigned listener', () => {
20
- const entrypoint = fileURLToPath(new URL('./e2e.ts', import.meta.url));
20
+ const entrypoint = new URL('./e2e.ts', import.meta.url).href;
21
21
  const result = spawnSync(
22
22
  process.execPath,
23
23
  [
@@ -21,6 +21,7 @@ async function fixture(
21
21
  group?: number;
22
22
  noProfile?: boolean;
23
23
  exited?: boolean;
24
+ platform?: NodeJS.Platform;
24
25
  } = {},
25
26
  ) {
26
27
  const root = await realpath(await mkdtemp(join(tmpdir(), 'cabloy-managed-browser-')));
@@ -72,7 +73,9 @@ async function fixture(
72
73
  VONA_TEST_CLAIM_URL: 'http://127.0.0.1:1234/event',
73
74
  VONA_TEST_CLAIM_TOKEN: 'a'.repeat(64),
74
75
  TMPDIR: temporaryRoot,
76
+ ...(options.platform === 'win32' ? { TEMP: temporaryRoot, TMP: temporaryRoot } : {}),
75
77
  },
78
+ platform: options.platform ?? 'linux',
76
79
  projectPath,
77
80
  sendEvent: async (_env: NodeJS.ProcessEnv, event: TestClaimEvent) => {
78
81
  const stage = event.type === 'process' ? event.process.stage : event.type;
@@ -85,13 +88,24 @@ async function fixture(
85
88
  if (options.fault === 'inspect') throw new Error('inspection failed');
86
89
  return {
87
90
  pid,
88
- pgid: options.group ?? pid,
89
- startTime: 'Wed Oct 14 01:02:03 2026',
91
+ ...(options.platform === 'win32' ? {} : { pgid: options.group ?? pid }),
92
+ startTime: options.platform === 'win32' ? '134000000000000000' : 'Wed Oct 14 01:02:03 2026',
90
93
  host: 'test-host',
91
94
  state: 'S',
92
95
  args: '',
93
96
  };
94
97
  },
98
+ assertWindowsJob: async (
99
+ _env: NodeJS.ProcessEnv,
100
+ identity: { pid: number; startTime: string; host: string },
101
+ ) => {
102
+ const role = identity.pid === process.pid ? 'worker' : 'browser';
103
+ calls.push(`job-${role}`);
104
+ assert.equal(identity.startTime, '134000000000000000');
105
+ assert.equal(identity.host, 'test-host');
106
+ assert.equal(Object.hasOwn(identity, 'pgid'), false);
107
+ if (options.fault === `job-${role}`) throw new Error('designated Job membership unavailable');
108
+ },
95
109
  waitForGroupExit: async (pgid: number) => {
96
110
  calls.push('wait');
97
111
  assert.equal(pgid, 4242);
@@ -277,7 +291,7 @@ for (const invalid of ['tmpdir', 'owner', 'transport', 'connect', 'symlink']) {
277
291
  if (invalid === 'transport') f.dependencies.env.VONA_TEST_CLAIM_TOKEN = '';
278
292
  if (invalid === 'symlink') {
279
293
  await rm(f.temporaryRoot, { recursive: true });
280
- await symlink(f.root, f.temporaryRoot);
294
+ await symlink(f.root, f.temporaryRoot, process.platform === 'win32' ? 'junction' : 'dir');
281
295
  }
282
296
  const options =
283
297
  invalid === 'connect'
@@ -364,6 +378,104 @@ test('rejects an explicit external profile argument before launch', async () =>
364
378
  }
365
379
  });
366
380
 
381
+ test('Windows proves worker and browser Job membership without per-browser POSIX claims', async () => {
382
+ const f = await fixture({ platform: 'win32' });
383
+ try {
384
+ await useManagedBrowser(
385
+ f.browserOptions,
386
+ async () => {
387
+ f.calls.push('use');
388
+ },
389
+ f.dependencies,
390
+ );
391
+ assert.deepEqual(f.calls, [
392
+ 'inspect',
393
+ 'job-worker',
394
+ 'launch',
395
+ 'inspect',
396
+ 'job-browser',
397
+ 'connect',
398
+ 'use',
399
+ 'close',
400
+ ]);
401
+ assert.deepEqual(f.events, []);
402
+ } finally {
403
+ await f.dispose();
404
+ }
405
+ });
406
+
407
+ for (const fault of ['job-worker', 'job-browser', 'launch', 'inspect', 'connect', 'close']) {
408
+ test(`Windows fails closed on ${fault} without acknowledging browser drainage`, async () => {
409
+ const f = await fixture({ platform: 'win32', fault });
410
+ try {
411
+ await assert.rejects(useManagedBrowser(f.browserOptions, async () => {}, f.dependencies));
412
+ assert.deepEqual(f.events, []);
413
+ assert.ok(!f.calls.includes('wait'));
414
+ if (['job-worker', 'inspect'].includes(fault)) assert.ok(!f.calls.includes('launch'));
415
+ if (fault === 'job-browser') {
416
+ assert.ok(f.calls.includes('close'));
417
+ assert.ok(!f.calls.includes('connect'));
418
+ }
419
+ } finally {
420
+ await f.dispose();
421
+ }
422
+ });
423
+ }
424
+
425
+ for (const variable of ['TEMP', 'TMP', 'TMPDIR', 'temp']) {
426
+ test(`Windows rejects inherited ${variable} outside its run before browser launch`, async () => {
427
+ const f = await fixture({ platform: 'win32' });
428
+ try {
429
+ const env: NodeJS.ProcessEnv = { ...f.dependencies.env, [variable]: '/unowned' };
430
+ await assert.rejects(
431
+ useManagedBrowser(f.browserOptions, async () => {}, { ...f.dependencies, env }),
432
+ /run-owned temporary paths/,
433
+ );
434
+ assert.deepEqual(f.calls, []);
435
+ } finally {
436
+ await f.dispose();
437
+ }
438
+ });
439
+ }
440
+
441
+ test('Windows normalizes custom temporary environment names without mutating caller options', async () => {
442
+ const f = await fixture({ platform: 'win32' });
443
+ const env = { temp: '/unowned', Tmp: '/unowned', tmpdir: '/unowned', CUSTOM: 'keep' };
444
+ try {
445
+ await useManagedBrowser(
446
+ { ...f.browserOptions, launchOptions: { env } },
447
+ async () => {},
448
+ f.dependencies,
449
+ );
450
+ assert.deepEqual(f.launchedOptions()?.env, {
451
+ TEMP: f.temporaryRoot,
452
+ TMP: f.temporaryRoot,
453
+ TMPDIR: f.temporaryRoot,
454
+ CUSTOM: 'keep',
455
+ });
456
+ assert.equal(env.temp, '/unowned');
457
+ assert.equal(env.Tmp, '/unowned');
458
+ assert.equal(env.tmpdir, '/unowned');
459
+ } finally {
460
+ await f.dispose();
461
+ }
462
+ });
463
+
464
+ test('Windows closes a browser with an escaping actual profile without connecting', async () => {
465
+ const f = await fixture({ platform: 'win32', profile: '/unowned/profile' });
466
+ try {
467
+ await assert.rejects(
468
+ useManagedBrowser(f.browserOptions, async () => {}, f.dependencies),
469
+ /outside its run-owned area/,
470
+ );
471
+ assert.deepEqual(f.events, []);
472
+ assert.ok(f.calls.includes('close'));
473
+ assert.ok(!f.calls.includes('connect'));
474
+ } finally {
475
+ await f.dispose();
476
+ }
477
+ });
478
+
367
479
  test('discovers exact Chromium and Firefox argv profiles; uses the temporary area for WebKit', () => {
368
480
  assert.equal(
369
481
  discoverBrowserProfile(['browser', '--user-data-dir=/run/tmp/with spaces']),