diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml new file mode 100644 index 0000000..62fcace --- /dev/null +++ b/.github/workflows/browser.yml @@ -0,0 +1,37 @@ +name: Deterministic documentation browser flows +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +concurrency: + group: browser-${{ github.ref }} + cancel-in-progress: true +jobs: + browser: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: 24.8.0 + - run: npm ci + - run: npm ci --prefix tests/browser + - run: npx tsc --project tests/browser/tsconfig.json + - run: npm run docs:check + - run: npm run docs:build + - run: npx --no-install e2e-web install chromium --with-deps + working-directory: tests/browser + - run: npm test + working-directory: tests/browser + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + if: always() + with: + name: documentation-browser-evidence + path: tests/browser/.e2e/ + include-hidden-files: true + retention-days: 7 diff --git a/.gitignore b/.gitignore index dbc40d4..be5da60 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ /docs/next-env.d.ts /docs/tsconfig.tsbuildinfo /*.tgz + +# Deterministic browser traces +tests/browser/.e2e/ + +# Isolated browser-tool dependencies +tests/browser/node_modules/ diff --git a/README.md b/README.md index 11f0123..6f23e77 100644 --- a/README.md +++ b/README.md @@ -236,3 +236,33 @@ FontTools interoperability check in both directions. ## license [MIT](LICENSE) + +## Deterministic browser verification + +The documentation UI is tested with pinned `tester-army/e2e` packages in an +isolated private tooling package; the published CLI's Node >=18 requirement is +unchanged. With Node >=24.8 for browser tooling: + +```sh +npm ci +npm ci --prefix tests/browser +npm run docs:check +npm run docs:build +cd tests/browser +npx --no-install e2e-web install chromium --with-deps +npm test +``` + +The suite opens all documentation pages, retrieves their Markdown endpoints, +and checks mobile layout. It makes no model calls and uses no font-provider +credentials. Native `go test ./cmd/... ./internal/... ./e2e/...` additionally +runs the compiled CLI, real loopback font-download fixtures, conversion and PTY +flows. Live third-party provider availability is a separate acceptance surface. + +Ranking now memoizes query relevance by filename/name within each call. The +frozen-reference regression compares every result, score and ordering across +24 query/weight combinations and checks that caller input stays unchanged. +On a synthetic 600-row fixture, three-run median time was 13.51 ms before versus +1.66 ms after; allocations fell from about 67,666 to 7,263 per operation. This +is a focused ranking benchmark, not an end-to-end search/network speed claim. +Reproduce with `go test ./internal/rank -bench BenchmarkRanking -benchmem`. diff --git a/internal/provider/github_test.go b/internal/provider/github_test.go index 1fc66c5..61e7ef3 100644 --- a/internal/provider/github_test.go +++ b/internal/provider/github_test.go @@ -544,7 +544,7 @@ func TestGitHubRetryAfter(t *testing.T) { want time.Duration }{ {"seconds", http.Header{"Retry-After": {"7"}}, 7 * time.Second}, - {"date", http.Header{"Retry-After": {now.Add(9 * time.Second).Format(http.TimeFormat)}}, 9 * time.Second}, + {"date", http.Header{"Retry-After": {now.Add(9 * time.Second).UTC().Format(http.TimeFormat)}}, 9 * time.Second}, {"reset", http.Header{"X-Ratelimit-Reset": {fmt.Sprint(now.Add(11 * time.Second).Unix())}}, 11 * time.Second}, {"retry after wins", http.Header{"Retry-After": {"7"}, "X-Ratelimit-Reset": {fmt.Sprint(now.Add(11 * time.Second).Unix())}}, 7 * time.Second}, } diff --git a/internal/rank/cache_benchmark_test.go b/internal/rank/cache_benchmark_test.go new file mode 100644 index 0000000..4b2d069 --- /dev/null +++ b/internal/rank/cache_benchmark_test.go @@ -0,0 +1,126 @@ +package rank + +import ( + "fmt" + "github.com/microck/moji/internal/provider" + "math" + "reflect" + "sort" + "strings" + "testing" +) + +// Frozen pre-optimization implementation, retained to compare full ordering, +// scores, and metadata rather than only the first result. +func baselineResults(results []provider.Result, query, wantedWeight string, weights Weights) []provider.Result { + normalizedQuery := strings.ToLower(NormalizeQuery(query)) + compactQuery := strings.ReplaceAll(normalizedQuery, " ", "") + ranked := make([]provider.Result, 0, len(results)) + for _, result := range results { + if compactQuery == "" || relevance(result, normalizedQuery, compactQuery) > 0 { + ranked = append(ranked, result) + } + } + familySizes := make(map[string]map[string]struct{}) + for i := range ranked { + tags := ParseFilename(ranked[i].Filename) + if ranked[i].Format == "" { + ranked[i].Format = tags.Format + } + if ranked[i].Weight == "" { + ranked[i].Weight = tags.Weight + } + if tags.Variable { + ranked[i].Variable = true + } + key := tags.Family + "\x00" + familyGroupOf(ranked[i]) + if familySizes[key] == nil { + familySizes[key] = make(map[string]struct{}) + } + if ranked[i].Weight != "" { + familySizes[key][ranked[i].Weight] = struct{}{} + } + } + for i := range ranked { + tags := ParseFilename(ranked[i].Filename) + formatRank := map[string]float64{"otf": 3, "ttf": 2, "dfont": 1.5, "pfb": 1, "woff2": 0.75, "woff": 0.5, "pfm": 0.1}[ranked[i].Format] + score := weights.Format * formatRank + score += weights.FamilySize * math.Log2(1+float64(len(familySizes[tags.Family+"\x00"+familyGroupOf(ranked[i])]))) + if ranked[i].Trusted { + score += weights.Trusted + } + if wantedWeight != "" && ranked[i].Weight == wantedWeight { + score += weights.WeightBonus * 3 + } + if ranked[i].Variable { + score += weights.VariableBonus + } + if ranked[i].SizeBytes > 0 && ranked[i].SizeBytes < 10_000 { + score -= weights.SizePenalty + } + ranked[i].Score = score + } + sort.Slice(ranked, func(i, j int) bool { + leftRelevance := relevance(ranked[i], normalizedQuery, compactQuery) + rightRelevance := relevance(ranked[j], normalizedQuery, compactQuery) + if leftRelevance != rightRelevance { + return leftRelevance > rightRelevance + } + if ranked[i].Score != ranked[j].Score { + return ranked[i].Score > ranked[j].Score + } + leftReliability := sourceReliability(ranked[i]) + rightReliability := sourceReliability(ranked[j]) + if leftReliability != rightReliability { + return leftReliability > rightReliability + } + if ranked[i].Filename != ranked[j].Filename { + return ranked[i].Filename < ranked[j].Filename + } + if ranked[i].Source != ranked[j].Source { + return ranked[i].Source < ranked[j].Source + } + return ranked[i].URL < ranked[j].URL + }) + return ranked +} + +func rankingFixture() []provider.Result { + results := make([]provider.Result, 600) + families := []string{"Inter", "Inter var", "Example", "Font", "SourceSans", "AvenirNext", "ProximaNova", "HelveticaNeue"} + weights := []string{"Regular", "Bold", "Light", "SemiBoldItalic"} + for i := range results { + results[i] = provider.Result{Name: families[i%len(families)], Filename: fmt.Sprintf("%s-%s.ttf", families[i%len(families)], weights[(i/7)%len(weights)]), Source: fmt.Sprintf("source-%d", i%9), URL: fmt.Sprintf("https://example.test/font-%d.ttf", i), SizeBytes: int64(8000 + i*10), Trusted: i%3 == 0} + } + return results +} +func TestCachedRankingPreservesCompleteResults(t *testing.T) { + input := rankingFixture() + before := append([]provider.Result(nil), input...) + for _, query := range []string{"", "Inter", "Source Sans", "Avenir", "unmatched", "FONT", "Proxima Nova", "Helvetic"} { + for _, weight := range []string{"", "bold", "light"} { + got := Results(input, query, weight, DefaultWeights()) + want := baselineResults(input, query, weight, DefaultWeights()) + if !reflect.DeepEqual(got, want) { + t.Fatalf("ranking changed for %q/%q", query, weight) + } + } + } + if !reflect.DeepEqual(input, before) { + t.Fatal("ranking mutated caller results") + } +} +func BenchmarkRankingBaseline(b *testing.B) { + input := rankingFixture() + b.ReportAllocs() + for b.Loop() { + baselineResults(input, "Inter", "", DefaultWeights()) + } +} +func BenchmarkRankingCached(b *testing.B) { + input := rankingFixture() + b.ReportAllocs() + for b.Loop() { + Results(input, "Inter", "", DefaultWeights()) + } +} diff --git a/internal/rank/rank.go b/internal/rank/rank.go index c73d8a0..901c84a 100644 --- a/internal/rank/rank.go +++ b/internal/rank/rank.go @@ -178,9 +178,22 @@ func AdaptiveQueries(query string) []string { func Results(results []provider.Result, query, wantedWeight string, weights Weights) []provider.Result { normalizedQuery := strings.ToLower(NormalizeQuery(query)) compactQuery := strings.ReplaceAll(normalizedQuery, " ", "") + // Relevance depends only on these two identity fields for this query. + // Sorting compares each result repeatedly, so parse each identity once. + type identity struct{ filename, name string } + relevanceByIdentity := make(map[identity]int, len(results)) + cachedRelevance := func(result provider.Result) int { + key := identity{result.Filename, result.Name} + if score, ok := relevanceByIdentity[key]; ok { + return score + } + score := relevance(result, normalizedQuery, compactQuery) + relevanceByIdentity[key] = score + return score + } ranked := make([]provider.Result, 0, len(results)) for _, result := range results { - if compactQuery == "" || relevance(result, normalizedQuery, compactQuery) > 0 { + if compactQuery == "" || cachedRelevance(result) > 0 { ranked = append(ranked, result) } } @@ -224,8 +237,8 @@ func Results(results []provider.Result, query, wantedWeight string, weights Weig ranked[i].Score = score } sort.Slice(ranked, func(i, j int) bool { - leftRelevance := relevance(ranked[i], normalizedQuery, compactQuery) - rightRelevance := relevance(ranked[j], normalizedQuery, compactQuery) + leftRelevance := cachedRelevance(ranked[i]) + rightRelevance := cachedRelevance(ranked[j]) if leftRelevance != rightRelevance { return leftRelevance > rightRelevance } diff --git a/tests/browser/docs.e2e.ts b/tests/browser/docs.e2e.ts new file mode 100644 index 0000000..a148f3d --- /dev/null +++ b/tests/browser/docs.e2e.ts @@ -0,0 +1,31 @@ +import {readFileSync,readdirSync} from 'node:fs'; +import {join,relative,sep} from 'node:path'; +import {parse} from 'yaml'; +import {test} from '@e2e-dev/web'; +import {expect} from 'e2e'; +const root='../../docs/content/docs'; +function pages(directory=root):{route:string;title:string}[]{ + return readdirSync(directory,{withFileTypes:true}).flatMap(entry=>{ + const path=join(directory,entry.name); + if(entry.isDirectory())return pages(path); + if(!/\.mdx?$/.test(path))return []; + const front=readFileSync(path,'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1]; + const data=front===undefined?null:parse(front); + if(typeof data?.title!=='string'||!data.title.trim())throw new Error(`Missing title: ${path}`); + return [{title:data.title,route:'/docs/'+relative(root,path).split(sep).join('/').replace(/\.mdx?$/,'').replace(/(^|\/)index$/,'')}]; + }); +} +for(const page of pages())test(`rendered documentation ${page.route}`,async({app,screen,browser})=>{ + await app.open(page.route); + await expect(screen.getByRole('heading',{name:page.title,exact:true})).toBeVisible(); + const markdown=await browser.evaluate(async path=>{const response=await fetch('/llms.mdx'+path+'/content.md');return {status:response.status,text:await response.text()};},page.route.replace(/\/$/,'')); + expect(markdown.status).toBe(200); + expect(markdown.text.length).toBeGreaterThan(20); + expect(markdown.text.startsWith(`# ${page.title} (${page.route.replace(/\/$/,'')})\n`)).toBe(true); +}); +test('mobile docs navigation and readable content',async({app,browser,screen})=>{ + await browser.setViewport({width:390,height:844}); + await app.open('/docs'); + await expect(screen.getByRole('heading',{name:pages().find(p=>p.route==='/docs/')!.title,exact:true})).toBeVisible(); + expect(await browser.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true); +}); diff --git a/tests/browser/e2e.config.ts b/tests/browser/e2e.config.ts new file mode 100644 index 0000000..8e98b3b --- /dev/null +++ b/tests/browser/e2e.config.ts @@ -0,0 +1,3 @@ +import type {E2EConfig} from 'e2e'; +import {web} from '@e2e-dev/web'; +export default {tests:'*.e2e.ts',workers:1,retries:0,trace:'on',timeout:30000,targets:[{name:'chromium',engine:web(),app:{url:'http://127.0.0.1:4192',command:{executable:'node',args:['serve.mjs'],log:'.e2e/server.log'}}}]} satisfies E2EConfig; diff --git a/tests/browser/package-lock.json b/tests/browser/package-lock.json new file mode 100644 index 0000000..3a7fff0 --- /dev/null +++ b/tests/browser/package-lock.json @@ -0,0 +1,1237 @@ +{ + "name": "moji-browser-verification", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "moji-browser-verification", + "devDependencies": { + "@e2e-dev/web": "0.15.2", + "e2e": "0.21.2", + "yaml": "2.9.1" + }, + "engines": { + "node": ">=24.8.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "4.0.26", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.26.tgz", + "integrity": "sha512-BZ443uBEq6HuMA+Z4b7Hpm65GpZSo7pWqnYqpf/7tAO3C48JbtvAhOE2JnODFj+jodsZzfRHkAyQb64xuD2tZQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@clack/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.5.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@e2e-dev/web": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@e2e-dev/web/-/web-0.15.2.tgz", + "integrity": "sha512-XidBXbpiEeJQqPsXcEwMOon5KDk8PEaaXR65rDu5P1u115bW+2KK7DlWqYvLg/RpjK/pnOa5iNuD9JthqODLVQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.0" + }, + "bin": { + "e2e-web": "dist/bin.js" + }, + "engines": { + "node": "^22.22.3 || >=24.8.0" + }, + "peerDependencies": { + "e2e": ">=0.18.0 <1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz", + "integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz", + "integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/core": "2.3.1", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.153.0.tgz", + "integrity": "sha512-BcWu25dMwMjHpaDCEidmpAi7rGg1qZoihJeUUH9bD55pVZ0z4Wa5CQgqSTo0R41H81oozisQKW3WqvAN2m77IQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.153.0.tgz", + "integrity": "sha512-9l5rdp2DhRqVriBHsNpu5cEf+hizZ4B58rt93HzyQEBUgZiC6qe2oFpaAAnXGCxHGnQExI1s7S2G0QhFacUZ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.153.0.tgz", + "integrity": "sha512-EcpsyRXHhENDpekpG4QNaSZj2F/BAz4Gh4o89zZwNss86R8KyRSQ6sXbAj74Xyeb4z3j/yBZZ6joQZhraIWyfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.153.0.tgz", + "integrity": "sha512-hlj80HUQy07/2lYp6rmxH+3u+H2ik3rjLR52CUdPApxmZCNxASDUf/+ykTzAHS/fehftmVMATKvPaWmuLHuasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.153.0.tgz", + "integrity": "sha512-fWRcv2pMSh1qtez46MOu8vuUJ5fiaQZPHSphbWulbL7SHBMKfRgZl2PzjXZYQJbq7NEXyXbgIuIuUm+/Rmkv8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.153.0.tgz", + "integrity": "sha512-paZeUK1GXShl/bOJQET1/8AQxayFeuN4SIWAkgTRxE7ZQwv9uYSB2n0648smygP22N7jcI3zxAm0Z+ALNiIKxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.153.0.tgz", + "integrity": "sha512-qFPp5NwMf4K1C0O+DCVGmcbOaYyYAq98Rvv9p4VSfb777iUwlJCqMUxf6QG9N5f5ls4TEX6LitOZHi+6GL6rYQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.153.0.tgz", + "integrity": "sha512-oufEhZCX7saeo27Logra8cvBR1jQ/yw2zUI2hHmWw3qA/XYqEW31FOvLQBv7DWN32NjVdlcaP11WBBZTJo4k9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.153.0.tgz", + "integrity": "sha512-zXYzuAqng85h6+LI3iRm0zeMLX1/K2z41ZAMWxVRgt0D10VvZUaV5q+TEB46KDhoKBpdexoTLC6QoB/EiP2oqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.153.0.tgz", + "integrity": "sha512-J7+NLwLEzdkKM9cKyPHH/AjXXstWOs98KYgLrPPnO4N9MO8EZRBfPNnBcS3S67uMOBkRpUmCl2FhRnWdQ5CbBA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.153.0.tgz", + "integrity": "sha512-sD0K11GQTLWylln54nPMMTtgmBbQrR5It/vMpZpy/fogiw8O2AvIE+9LuWUXddrWk9zvFIHGRcSgFn4IHOfoYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.153.0.tgz", + "integrity": "sha512-7L66uql2BhvSz51LwfExjOdn6et/ydTiQxl23VApdKzJhTc3TczA1Qt6VR/UcvlQrL2ZbytIJb447vh7p5wmUw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.153.0.tgz", + "integrity": "sha512-hXV3T7mNyrPR19rbPYkglkQJtoM/UNo/xwbZeeqdAxEg8PXgb4LXXuxPpGgI4bzRRCWwLUxdDSS58xIyC2u1TA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.153.0.tgz", + "integrity": "sha512-Da8Vqs1xdtoDYtEjmozD7wGgJeLBQkMbR0WUBECOSxodyX5DXy8ySlvJWbukyO2Q0+TM9EK+4LT6tbq0jCg4hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.153.0.tgz", + "integrity": "sha512-zGAPxNTIIw6X5WyTRscIFE/oDjpdNtrrl32ing6NOXdGjzRucadnxBilexLdeYQ9YxixF93GujpXyQ0jw7ddSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.153.0.tgz", + "integrity": "sha512-GoHzpqP5XyPIp9DHokoEdCp+K4UPl/h1neFj1uXnUB3pJYyOQ7NOhFVOttNUgCGcj2eywAvu5cRivTOSq4S10g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.153.0.tgz", + "integrity": "sha512-xGcy12tO9k7zFbeyQzEuilfWfQpyRq23/fLPfN3wjSWuWv58k46fQT1hsO1KFNykZuVGW9xSJE/z592TmAoSDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.153.0.tgz", + "integrity": "sha512-o1ykUjWncvXX6N5a4KebgLjtD8zRui+f1llMhSGoSC9oP2KOqapRpdwmZ3NcNG0axFouwVKmhGBjd+IhA8BksA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.153.0.tgz", + "integrity": "sha512-gxQz7G96Up136fd+2zWrP+o6FYGs9NO4Dyv55IoeJFJUDwLFHcLYY11hhA5wQKs0HoM57Q8esQ3nEVhnCdsjAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.153.0.tgz", + "integrity": "sha512-rXJvtlsVG4xJNq64hiaUTIatE7jbBgpa6l4QgmEa2jLgT3ULH9scUBkpsC9N1DfmqixLEyT6DuK6OSXozziIlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@oxc-transform/binding-android-arm-eabi": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-android-arm-eabi/-/binding-android-arm-eabi-0.153.0.tgz", + "integrity": "sha512-EUYebwgzuoDXutZJYbMdL6vzwnQoJyO3fRWGuo5AXSeKRO2YBySW1u2gcVwAA2lTcFXMPJh2Fw1OM23ZfFQF1A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-android-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-android-arm64/-/binding-android-arm64-0.153.0.tgz", + "integrity": "sha512-UwwX/zjbEFbLuUdV1+9+SKWhjqwO9tnHqKNQYN4dR3ypK61MFa7s0C2z+vASSSZt7CmcAbl5ArGPcykFiga38A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-darwin-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-darwin-arm64/-/binding-darwin-arm64-0.153.0.tgz", + "integrity": "sha512-J6r1FDN0eqhrddJ8Fxe01DMABxsRTGzrn0d14eDXT2a6gqj5sdtEOiEksN7FimuqDPrMsX1QGjEacSNAuzboGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-darwin-x64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-darwin-x64/-/binding-darwin-x64-0.153.0.tgz", + "integrity": "sha512-6ad61F1FuyozXDGkPl9VHi/aSOCNUS4ebqXhUqc2JsGXG2S1CFVOsN11u5haNWCLdctbognhzKexK4zpFAsLXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-freebsd-x64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-freebsd-x64/-/binding-freebsd-x64-0.153.0.tgz", + "integrity": "sha512-eVcvny0FJzAV7Rr49JkWupfQxoaghJ189nwiwQBv//wI52GtDj+IWkrpPEd7x5bCf1GRX8hoBJmHk/QvKS5S/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm-gnueabihf": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.153.0.tgz", + "integrity": "sha512-8DiPMKx18GaMvt4KvHxHb7kHbOnZL9D0Xjxofc+N67RgeWNDdXiqLaUXVmptpOXmIK+sG4+RtQtHGrAt7fnyDQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm-musleabihf": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.153.0.tgz", + "integrity": "sha512-k1MDve56Sml/O8f9EeeRecZtMdzAQZYtq8mqShtzyJ/LvLQ5KKXnyzlZxQYjoMsRJwzsdkWNHE2svNQxtpyBaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.153.0.tgz", + "integrity": "sha512-N8ZvBN7uhlqRgxiqwhgpF7KAaIj5qT/4cGMscxleipj90tbtYOO3rAqxiBuABCrvoosrLX4HbJ39gQX9verUiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-arm64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.153.0.tgz", + "integrity": "sha512-OIRZqffxi0yam6aMzIV0lAV5JsMeGs1cQRTwco0qzeAFTKkVaFhpUWXdw1j4BXDDTqbJ3PWi+oQaYzLcGNiaFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-ppc64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.153.0.tgz", + "integrity": "sha512-KLf7dDN7zU6MFhka9zUHmJw8fvNRSxxGgLDelZwKh0iuaRjUSibKFQ4ZQd0/Rk5ybZbJgqDNFH5xeVU4Cd6ZfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-riscv64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.153.0.tgz", + "integrity": "sha512-WByPPuG7ndXq0tb12P6eHzzolS9Q+ioBmUF99x1FfOiya5nkuc6bjjixq7ZgYezNiSxkw7UsfCgcBZwtNYajkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-riscv64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.153.0.tgz", + "integrity": "sha512-yXM0cc0mtvIH9keD/9oULanLhVXN8Rf8cvSO5OO2vVrGYs5b0DwkTStztvwn1JQc1eQxPru9iKpZSJq6R4kURA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-s390x-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.153.0.tgz", + "integrity": "sha512-fqwaM2IMmOI0L7DEUWxWtJzGPyvr9ciIOXhB1vp8gkQ97j6xUCddJrEeUJTOAyJyYvHeT0rf03rWciASK4FqUw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-x64-gnu": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.153.0.tgz", + "integrity": "sha512-q0yrehYszYqbJfBW0LeUeYvI+NUMwD5p/6skX05EHVQsOJCUTdYKI2wEN2b3nBRUVYxf030pFdOtXJgpZatf/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-linux-x64-musl": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-linux-x64-musl/-/binding-linux-x64-musl-0.153.0.tgz", + "integrity": "sha512-+e0dMTEE7azbGjnJSG97BIQ/CtHM2FbudIlaMmq0Dx5BMaeZaynvXfOg4X2uAYWHEIJva9pC+o6JByuIhUrMOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-openharmony-arm64": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-openharmony-arm64/-/binding-openharmony-arm64-0.153.0.tgz", + "integrity": "sha512-yQu5Ykk76Nu0g82B6q5nMgyuqgntdmv16LXQPBOVIl2CvwbSwx2jrigfTztNDHk708/hCjiPp6/J7AnNpxO/kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-win32-arm64-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.153.0.tgz", + "integrity": "sha512-6hXONV9UIT0wqfSOtuXlRZSZofHF8orlhyYCNwZJY+xuu/HjFI6D/+T6BbuTnCrhbutynMT5ZUIeVScqsELxDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-win32-ia32-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.153.0.tgz", + "integrity": "sha512-i0tleWz872L9t0SAYs6bcY2RLfN/3XE3i0h0UDuTCxMT7wYj22Nd3IFF+4CFZQgF9Eb/Jh92OFrXQHFlwfhA0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-transform/binding-win32-x64-msvc": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-transform/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.153.0.tgz", + "integrity": "sha512-DC1juC+WQbUmGf3qJ7x+w4K0U9SUAANizzgGU3J+Y0WrMNZZryXWq0zzHAKTfvFpY2fORgz+J1IXlZ/wf6mRGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-5.0.3.tgz", + "integrity": "sha512-N/VGY122AqBx25OB4EatqlVu8PBayvm9dQzpJ8kVq/pOvqAhXTE5MAmoSMzdB8klDU9wcgSEUliwgMNMtxPa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "5.0.3", + "@vitest/utils": "5.0.3", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-5.0.3.tgz", + "integrity": "sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz", + "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-5.0.3.tgz", + "integrity": "sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "5.0.3", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/e2e": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/e2e/-/e2e-0.21.2.tgz", + "integrity": "sha512-w3NNJLbu0wkUhGy44O3elTXhDNdwetrvEhVvqA2ZjUx+l+ccchIVADodrOXaP2FaB15Y/lnZMbQrOdfVOheJIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "4.0.26", + "@clack/prompts": "1.8.1", + "@jridgewell/remapping": "2.3.5", + "@modelcontextprotocol/server": "2.3.1", + "@oxc-project/runtime": "0.153.0", + "@vitest/expect": "5.0.3", + "commander": "15.0.0", + "fast-png": "8.0.0", + "get-tsconfig": "4.14.3", + "oxc-parser": "0.153.0", + "oxc-transform": "0.153.0", + "picocolors": "1.1.1", + "pixelmatch": "8.0.0", + "zod": "4.6.1" + }, + "bin": { + "e2e": "dist/cli/bin.js" + }, + "engines": { + "node": "^22.22.3 || >=24.8.0" + }, + "peerDependencies": { + "@ai-sdk/anthropic": "^4.0.0", + "@ai-sdk/google": "^4.0.0", + "@ai-sdk/openai": "^4.0.0", + "@ai-sdk/openai-compatible": "^3.0.0", + "@ai-sdk/xai": "^5.0.0", + "ai": "^7.0.0" + }, + "peerDependenciesMeta": { + "@ai-sdk/anthropic": { + "optional": true + }, + "@ai-sdk/google": { + "optional": true + }, + "@ai-sdk/openai": { + "optional": true + }, + "@ai-sdk/openai-compatible": { + "optional": true + }, + "@ai-sdk/xai": { + "optional": true + }, + "ai": { + "optional": true + } + } + }, + "node_modules/fast-png": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-8.0.0.tgz", + "integrity": "sha512-gCysNasJ8KEMgfdYIKd/wTDo6ENK1PWT0RJO7O+0pgmuHPw2O6tA1WvdxFRJoLf9V8yFYpG0FA1YgI8X97OhJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fflate": "^0.8.2", + "iobuffer": "^6.0.1" + } + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/iobuffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-6.0.1.tgz", + "integrity": "sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/oxc-parser": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.153.0.tgz", + "integrity": "sha512-cEnpyyV1OJRXv++SFNw0z8i+2o/cr88rVLaOYJafELDoHM5yHvCAbwGRLvxuany6SNLcbblSMUyZaGzH+quSYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.153.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.153.0", + "@oxc-parser/binding-android-arm64": "0.153.0", + "@oxc-parser/binding-darwin-arm64": "0.153.0", + "@oxc-parser/binding-darwin-x64": "0.153.0", + "@oxc-parser/binding-freebsd-x64": "0.153.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.153.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.153.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.153.0", + "@oxc-parser/binding-linux-arm64-musl": "0.153.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.153.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.153.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.153.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.153.0", + "@oxc-parser/binding-linux-x64-gnu": "0.153.0", + "@oxc-parser/binding-linux-x64-musl": "0.153.0", + "@oxc-parser/binding-openharmony-arm64": "0.153.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.153.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.153.0", + "@oxc-parser/binding-win32-x64-msvc": "0.153.0" + } + }, + "node_modules/oxc-transform": { + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/oxc-transform/-/oxc-transform-0.153.0.tgz", + "integrity": "sha512-QNLqPkK0IeFF8QWB5KwieTLOZAo6v2hEHrXtmgPEINznmNy/nRi/fce/rm51RPKHPBkrEsxhePwYjGKp6wvfHQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxc-transform/binding-android-arm-eabi": "0.153.0", + "@oxc-transform/binding-android-arm64": "0.153.0", + "@oxc-transform/binding-darwin-arm64": "0.153.0", + "@oxc-transform/binding-darwin-x64": "0.153.0", + "@oxc-transform/binding-freebsd-x64": "0.153.0", + "@oxc-transform/binding-linux-arm-gnueabihf": "0.153.0", + "@oxc-transform/binding-linux-arm-musleabihf": "0.153.0", + "@oxc-transform/binding-linux-arm64-gnu": "0.153.0", + "@oxc-transform/binding-linux-arm64-musl": "0.153.0", + "@oxc-transform/binding-linux-ppc64-gnu": "0.153.0", + "@oxc-transform/binding-linux-riscv64-gnu": "0.153.0", + "@oxc-transform/binding-linux-riscv64-musl": "0.153.0", + "@oxc-transform/binding-linux-s390x-gnu": "0.153.0", + "@oxc-transform/binding-linux-x64-gnu": "0.153.0", + "@oxc-transform/binding-linux-x64-musl": "0.153.0", + "@oxc-transform/binding-openharmony-arm64": "0.153.0", + "@oxc-transform/binding-win32-arm64-msvc": "0.153.0", + "@oxc-transform/binding-win32-ia32-msvc": "0.153.0", + "@oxc-transform/binding-win32-x64-msvc": "0.153.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pixelmatch": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-8.0.0.tgz", + "integrity": "sha512-cIqOLfh5k52us715qf3B3+kTOxZv+WDv77HN7xokLScYQGYQHi4m/ynoDIPpvF2LvCyUJGyvzY64i0QCXtzu1g==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/playwright-core": { + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.0.tgz", + "integrity": "sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyrainbow": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.1.tgz", + "integrity": "sha512-341aRWQsve0rvronKNTqZpjmzdbUDlFuzHaI/XLg/Ej82qffDJRRfBTCuv7+9q/rMjB6LSLyEBnW4InJeMtt/Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/tests/browser/package.json b/tests/browser/package.json new file mode 100644 index 0000000..05ab166 --- /dev/null +++ b/tests/browser/package.json @@ -0,0 +1 @@ +{"name":"moji-browser-verification","private":true,"type":"module","engines":{"node":">=24.8.0"},"scripts":{"test":"node run.mjs"},"devDependencies":{"@e2e-dev/web":"0.15.2","e2e":"0.21.2","yaml":"2.9.1"}} \ No newline at end of file diff --git a/tests/browser/run.mjs b/tests/browser/run.mjs new file mode 100644 index 0000000..5035cbf --- /dev/null +++ b/tests/browser/run.mjs @@ -0,0 +1,16 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { spawnSync } from "node:child_process"; +const require = createRequire(import.meta.url); +const [major, minor] = process.versions.node.split(".").map(Number); +if (major < 24 || (major === 24 && minor < 8)) { + console.error("Browser E2E requires Node >=24.8.0."); + process.exit(1); +} +const cli = join(dirname(require.resolve("e2e")), "cli", "bin.js"); +const result = spawnSync(process.execPath, [cli, "run", ...process.argv.slice(2)], { + stdio: "inherit", + env: { ...process.env, E2E_TELEMETRY_DISABLED: "1" }, +}); +if (result.error) throw result.error; +process.exit(result.status ?? 1); diff --git a/tests/browser/serve.mjs b/tests/browser/serve.mjs new file mode 100644 index 0000000..f3e0873 --- /dev/null +++ b/tests/browser/serve.mjs @@ -0,0 +1,9 @@ +import {spawn} from 'node:child_process'; +import {createRequire} from 'node:module'; +import {fileURLToPath} from 'node:url'; +const require=createRequire(import.meta.url); +const root=fileURLToPath(new URL('../../docs/',import.meta.url)); +const child=spawn(process.execPath,[require.resolve('next/dist/bin/next'),'start','--hostname','127.0.0.1','--port','4192'],{cwd:root,stdio:'inherit',env:{...process.env,NEXT_TELEMETRY_DISABLED:'1'}}); +for(const signal of ['SIGINT','SIGTERM'])process.on(signal,()=>child.kill(signal)); +child.once('error',error=>{console.error(error);process.exitCode=1;}); +child.once('exit',code=>{process.exitCode=code??0;}); diff --git a/tests/browser/tsconfig.json b/tests/browser/tsconfig.json new file mode 100644 index 0000000..2e646f2 --- /dev/null +++ b/tests/browser/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions":{"target":"ES2022","lib":["ES2022","DOM"],"module":"NodeNext","moduleResolution":"NodeNext","strict":true,"skipLibCheck":true,"noEmit":true,"types":["node"]},"include":["*.ts"]}