this repo has no description
32
fork

Configure Feed

Select the types of activity you want to include in your feed.

Migrate smoke harness to TypeScript and Bun

alice 5756f693 2b017a3a

+1304 -1551
+2
.gitignore
··· 1 1 node_modules/ 2 + dist/ 2 3 playwright-report/ 3 4 test-results/ 4 5 data/ 6 + .tmp/ 5 7 .DS_Store 6 8 config.json 7 9 /demo.gif
+2
.prettierignore
··· 1 1 .tmp/ 2 2 data/ 3 + dist/ 3 4 node_modules/ 5 + *.locket/
+8 -8
README.md
··· 16 16 ## Quick start 17 17 18 18 ```sh 19 - npm install 20 - npx playwright install chromium 19 + bun install 20 + bunx playwright install chromium 21 21 22 22 # generate a config, fill in your PDS URL and credentials 23 - node bin/atproto-smoke.mjs write-example --mode dual --output config.json 23 + bunx tsx bin/atproto-smoke.ts write-example --mode dual --output config.json 24 24 $EDITOR config.json 25 25 26 26 # validate and run 27 - node bin/atproto-smoke.mjs validate --mode dual --config config.json 28 - node bin/atproto-smoke.mjs run-dual --config config.json 27 + bunx tsx bin/atproto-smoke.ts validate --mode dual --config config.json 28 + node dist/bin/atproto-smoke.js run-dual --config config.json 29 29 ``` 30 30 31 31 That's it. Provide a `pdsUrl` and two account credentials, and the suite handles the rest. Run commands print per-step progress to `stderr` and write a JSON summary to `stdout` (`--json-only` for machine-readable output only). ··· 50 50 The suite ships with built-in adapters for different PDS implementations: 51 51 52 52 ```sh 53 - node bin/atproto-smoke.mjs list-adapters 53 + node dist/bin/atproto-smoke.js list-adapters 54 54 ``` 55 55 56 56 - **`bring-your-own`** — the default. Works with any PDS that has accounts you can log into. ··· 77 77 Example configs live in [examples/](./examples). See [docs/SAMPLE_OUTPUT.md](./docs/SAMPLE_OUTPUT.md) for representative CLI output and `summary.json` shape. 78 78 79 79 For the local `pdslab.net` smoke lab, the committed non-secret target inventory 80 - lives in [`src/lab/pdslab-targets.mjs`](./src/lab/pdslab-targets.mjs). If you 80 + lives in [`src/lab/pdslab-targets.ts`](./src/lab/pdslab-targets.ts). If you 81 81 also have the local credential ledger in `.tmp/smoke-accounts.local.json`, you 82 82 can generate runnable configs into `.tmp/generated/pdslab-configs/` with: 83 83 84 84 ```sh 85 - npm run write:pdslab-configs 85 + bun run write:pdslab-configs 86 86 ``` 87 87 88 88 That writes one config per runnable target plus an `inventory.json` summary,
-10
bin/atproto-smoke.mjs
··· 1 - #!/usr/bin/env node 2 - import { runCliFromArgv } from "../src/cli.mjs"; 3 - 4 - try { 5 - const exitCode = await runCliFromArgv(process.argv); 6 - process.exitCode = exitCode; 7 - } catch (error) { 8 - console.error(String(error?.message ?? error)); 9 - process.exitCode = 1; 10 - }
+11
bin/atproto-smoke.ts
··· 1 + #!/usr/bin/env node 2 + import { errorMessage } from "../src/browser/lib/runtime-utils.js"; 3 + import { runCliFromArgv } from "../src/cli.js"; 4 + 5 + try { 6 + const exitCode = await runCliFromArgv(process.argv); 7 + process.exit(exitCode); 8 + } catch (error) { 9 + console.error(errorMessage(error)); 10 + process.exit(1); 11 + }
+330
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "atproto-smoke", 7 + "dependencies": { 8 + "playwright": "^1.54.2", 9 + }, 10 + "devDependencies": { 11 + "@eslint/js": "^9.39.2", 12 + "@types/node": "^24.7.2", 13 + "@typescript/native-preview": "latest", 14 + "eslint": "^9.39.2", 15 + "globals": "^17.0.0", 16 + "prettier": "^3.8.0", 17 + "tsx": "^4.20.6", 18 + "typescript": "^5.9.3", 19 + "typescript-eslint": "^8.46.1", 20 + }, 21 + }, 22 + }, 23 + "packages": { 24 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], 25 + 26 + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], 27 + 28 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], 29 + 30 + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], 31 + 32 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], 33 + 34 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], 35 + 36 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], 37 + 38 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], 39 + 40 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], 41 + 42 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], 43 + 44 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], 45 + 46 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], 47 + 48 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], 49 + 50 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], 51 + 52 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], 53 + 54 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], 55 + 56 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], 57 + 58 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], 59 + 60 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], 61 + 62 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], 63 + 64 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], 65 + 66 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], 67 + 68 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], 69 + 70 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], 71 + 72 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], 73 + 74 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], 75 + 76 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], 77 + 78 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 79 + 80 + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], 81 + 82 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 83 + 84 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 85 + 86 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], 87 + 88 + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], 89 + 90 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 91 + 92 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 93 + 94 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 95 + 96 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 97 + 98 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 99 + 100 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 101 + 102 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 103 + 104 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 105 + 106 + "@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], 107 + 108 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/type-utils": "8.57.2", "@typescript-eslint/utils": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w=="], 109 + 110 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="], 111 + 112 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.2", "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw=="], 113 + 114 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], 115 + 116 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw=="], 117 + 118 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg=="], 119 + 120 + "@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], 121 + 122 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.2", "@typescript-eslint/tsconfig-utils": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA=="], 123 + 124 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg=="], 125 + 126 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], 127 + 128 + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260327.2", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260327.2", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260327.2", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260327.2", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260327.2", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260327.2", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260327.2", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260327.2" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-npU/LrswTK7gawemSkI2BufIgNgoOHA1OwwIC5EUh++oWLDuWZSvSAcH6mfn28NOt5A196zrHQd3SK7f5XCVAw=="], 129 + 130 + "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260327.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lEcUWwu2DLY0NjoB3x7Fivz63zd57D1fD6PD8ByQqa0/xO8+EC5GHr5YniJlHSpF05cn2sgl7SPS92qVs0Xhlw=="], 131 + 132 + "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260327.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-IvUv0dCaVNKbA9Oq/GEEhtBPF9PP3E5MfW4pla4NpDsZh+6/nL5p0WXvlocV0fe1rT9fqmCQfk3oH+XrN1I8Qw=="], 133 + 134 + "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260327.2", "", { "os": "linux", "cpu": "arm" }, "sha512-7ATSft1YojnRp/VG70i97CxFe+umhTHYA/jJwQBPrjdopAFa5IvFDr4kNajjy1uypbz/x6pfvCnTdOd1/lM3FQ=="], 135 + 136 + "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260327.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-k98Q20XAC8bM9L915GFIfjo/ii/sYIaEzIDH3l6MwhiJMDPucARQpfbReUdXl8N3TsdCNwk8xay0pNIK0DOkvg=="], 137 + 138 + "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260327.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XTe5M1sTwjlxHS1NaNci4vf2nR7bAEwPB70SbhbSTS9ka+75mTP7cv8jHbGhKXEPxDLURBPSyvionog4+5NXmA=="], 139 + 140 + "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260327.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-1F86Pmf1QcHv0IENBJJ7nWWVSWjR1nn4jokuSbWiPkKGD57rkJWiHY7xtpt4ql8ZG4bIWxdHonLaepBfjIkaug=="], 141 + 142 + "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260327.2", "", { "os": "win32", "cpu": "x64" }, "sha512-zMI8AIWRr98hOgTC5DCe3GD/MGHfusX/E/Dz64Xcf+re4EUVS/pXP5fAUb3dQr8ndi9mHbM0DEmNb4ctSxsxew=="], 143 + 144 + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], 145 + 146 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 147 + 148 + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], 149 + 150 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 151 + 152 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 153 + 154 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 155 + 156 + "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], 157 + 158 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 159 + 160 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 161 + 162 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 163 + 164 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 165 + 166 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 167 + 168 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 169 + 170 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 171 + 172 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 173 + 174 + "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], 175 + 176 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 177 + 178 + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], 179 + 180 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 181 + 182 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 183 + 184 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 185 + 186 + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], 187 + 188 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 189 + 190 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 191 + 192 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 193 + 194 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 195 + 196 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 197 + 198 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 199 + 200 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 201 + 202 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 203 + 204 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 205 + 206 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 207 + 208 + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], 209 + 210 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 211 + 212 + "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], 213 + 214 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 215 + 216 + "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], 217 + 218 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 219 + 220 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 221 + 222 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 223 + 224 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 225 + 226 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 227 + 228 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 229 + 230 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 231 + 232 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 233 + 234 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 235 + 236 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 237 + 238 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 239 + 240 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 241 + 242 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 243 + 244 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 245 + 246 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 247 + 248 + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], 249 + 250 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 251 + 252 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 253 + 254 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 255 + 256 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 257 + 258 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 259 + 260 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 261 + 262 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 263 + 264 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 265 + 266 + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], 267 + 268 + "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], 269 + 270 + "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], 271 + 272 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 273 + 274 + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], 275 + 276 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 277 + 278 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 279 + 280 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 281 + 282 + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 283 + 284 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 285 + 286 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 287 + 288 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 289 + 290 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 291 + 292 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 293 + 294 + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], 295 + 296 + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 297 + 298 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 299 + 300 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 301 + 302 + "typescript-eslint": ["typescript-eslint@8.57.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.2", "@typescript-eslint/parser": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A=="], 303 + 304 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 305 + 306 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 307 + 308 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 309 + 310 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 311 + 312 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 313 + 314 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 315 + 316 + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 317 + 318 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 319 + 320 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], 321 + 322 + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], 323 + 324 + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], 325 + 326 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], 327 + 328 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], 329 + } 330 + }
+1 -1
demo.tape
··· 24 24 Sleep 500ms 25 25 Show 26 26 27 - Type "node bin/atproto-smoke.mjs run-dual --config config.json" 27 + Type "node dist/bin/atproto-smoke.js run-dual --config config.json" 28 28 Sleep 500ms 29 29 Enter 30 30 Sleep 120s
+1 -1
docs/ADAPTERS.md
··· 67 67 68 68 ## Minimal Adapter Contract 69 69 70 - The current registry lives in `src/adapters/registry.mjs`. A built-in adapter 70 + The current registry lives in `src/adapters/registry.ts`. A built-in adapter 71 71 definition looks like this in practice: 72 72 73 73 ```js
+73 -23
eslint.config.js
··· 1 1 import eslint from "@eslint/js"; 2 + import tseslint from "typescript-eslint"; 2 3 import globals from "globals"; 3 4 4 - export default [ 5 - { 6 - ignores: [".tmp/**", "data/**", "node_modules/**"], 7 - }, 5 + export default tseslint.config( 8 6 eslint.configs.recommended, 7 + tseslint.configs.strictTypeChecked, 8 + tseslint.configs.stylisticTypeChecked, 9 9 { 10 - files: ["**/*.{js,mjs,cjs}"], 11 10 languageOptions: { 12 - ecmaVersion: "latest", 13 - sourceType: "module", 14 11 globals: { 15 12 ...globals.browser, 16 13 ...globals.node, 17 14 }, 15 + parserOptions: { 16 + projectService: true, 17 + tsconfigRootDir: import.meta.dirname, 18 + }, 18 19 }, 19 20 rules: { 20 - "consistent-return": "error", 21 - eqeqeq: ["error", "always"], 22 - "no-console": "warn", 23 - "no-implicit-coercion": "error", 24 - "no-shadow": "error", 25 - "no-unused-vars": [ 21 + "@typescript-eslint/no-explicit-any": "error", 22 + "@typescript-eslint/no-unsafe-assignment": "error", 23 + "@typescript-eslint/no-unsafe-call": "error", 24 + "@typescript-eslint/no-unsafe-member-access": "error", 25 + "@typescript-eslint/no-unsafe-return": "error", 26 + "@typescript-eslint/no-unsafe-argument": "error", 27 + "@typescript-eslint/no-non-null-assertion": "error", 28 + "@typescript-eslint/strict-boolean-expressions": "error", 29 + "@typescript-eslint/explicit-function-return-type": "error", 30 + "@typescript-eslint/explicit-module-boundary-types": "error", 31 + "@typescript-eslint/no-unused-vars": [ 26 32 "error", 27 33 { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, 28 34 ], 29 - "no-var": "error", 30 - "prefer-const": "error", 31 - "require-await": "error", 35 + "@typescript-eslint/no-floating-promises": "error", 36 + "@typescript-eslint/await-thenable": "error", 37 + "@typescript-eslint/no-misused-promises": "error", 38 + "@typescript-eslint/require-await": "error", 39 + "@typescript-eslint/consistent-type-imports": [ 40 + "error", 41 + { prefer: "type-imports" }, 42 + ], 43 + "@typescript-eslint/consistent-type-definitions": [ 44 + "error", 45 + "interface", 46 + ], 47 + "@typescript-eslint/naming-convention": [ 48 + "error", 49 + { 50 + selector: "typeLike", 51 + format: ["PascalCase"], 52 + }, 53 + { 54 + selector: "variable", 55 + format: ["camelCase", "UPPER_CASE", "PascalCase"], 56 + }, 57 + { 58 + selector: "function", 59 + format: ["camelCase", "PascalCase"], 60 + }, 61 + ], 62 + "no-console": "warn", 63 + eqeqeq: ["error", "always"], 64 + "no-return-await": "off", 65 + "@typescript-eslint/return-await": ["error", "always"], 66 + "@typescript-eslint/no-unnecessary-condition": [ 67 + "error", 68 + { allowConstantLoopConditions: true }, 69 + ], 32 70 }, 33 71 }, 34 72 { 35 - files: [ 36 - "**/__tests__/**/*.{js,mjs,cjs}", 37 - "**/*.test.{js,mjs,cjs}", 38 - "**/*.spec.{js,mjs,cjs}", 39 - ], 73 + files: ["**/__tests__/**/*.ts", "**/*.test.ts", "**/*.spec.ts"], 40 74 rules: { 41 - "require-await": "off", 75 + "@typescript-eslint/no-floating-promises": "off", 76 + "@typescript-eslint/no-non-null-assertion": "off", 77 + "@typescript-eslint/no-unnecessary-condition": "off", 78 + "@typescript-eslint/no-deprecated": "off", 79 + "@typescript-eslint/restrict-template-expressions": "off", 80 + "@typescript-eslint/naming-convention": "off", 81 + "no-case-declarations": "off", 42 82 }, 43 83 }, 44 - ]; 84 + { 85 + ignores: [ 86 + "**/dist/**", 87 + "**/data/**", 88 + "**/.tmp/**", 89 + "**/node_modules/**", 90 + "prettier.config.mjs", 91 + "**/*.js", 92 + ], 93 + }, 94 + );
-1192
package-lock.json
··· 1 - { 2 - "name": "atproto-smoke", 3 - "version": "0.2.0", 4 - "lockfileVersion": 3, 5 - "requires": true, 6 - "packages": { 7 - "": { 8 - "name": "atproto-smoke", 9 - "version": "0.2.0", 10 - "dependencies": { 11 - "playwright": "^1.54.2" 12 - }, 13 - "bin": { 14 - "atproto-smoke": "bin/atproto-smoke.mjs" 15 - }, 16 - "devDependencies": { 17 - "@eslint/js": "^9.39.2", 18 - "@types/node": "^24.7.2", 19 - "eslint": "^9.39.2", 20 - "globals": "^17.0.0", 21 - "prettier": "^3.8.0", 22 - "typescript": "^5.9.3" 23 - }, 24 - "engines": { 25 - "node": ">=20" 26 - } 27 - }, 28 - "node_modules/@eslint-community/eslint-utils": { 29 - "version": "4.9.1", 30 - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", 31 - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", 32 - "dev": true, 33 - "license": "MIT", 34 - "dependencies": { 35 - "eslint-visitor-keys": "^3.4.3" 36 - }, 37 - "engines": { 38 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 39 - }, 40 - "funding": { 41 - "url": "https://opencollective.com/eslint" 42 - }, 43 - "peerDependencies": { 44 - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 45 - } 46 - }, 47 - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 48 - "version": "3.4.3", 49 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 50 - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 51 - "dev": true, 52 - "license": "Apache-2.0", 53 - "engines": { 54 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 55 - }, 56 - "funding": { 57 - "url": "https://opencollective.com/eslint" 58 - } 59 - }, 60 - "node_modules/@eslint-community/regexpp": { 61 - "version": "4.12.2", 62 - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", 63 - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", 64 - "dev": true, 65 - "license": "MIT", 66 - "engines": { 67 - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 68 - } 69 - }, 70 - "node_modules/@eslint/config-array": { 71 - "version": "0.21.2", 72 - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", 73 - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", 74 - "dev": true, 75 - "license": "Apache-2.0", 76 - "dependencies": { 77 - "@eslint/object-schema": "^2.1.7", 78 - "debug": "^4.3.1", 79 - "minimatch": "^3.1.5" 80 - }, 81 - "engines": { 82 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 83 - } 84 - }, 85 - "node_modules/@eslint/config-helpers": { 86 - "version": "0.4.2", 87 - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", 88 - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", 89 - "dev": true, 90 - "license": "Apache-2.0", 91 - "dependencies": { 92 - "@eslint/core": "^0.17.0" 93 - }, 94 - "engines": { 95 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 96 - } 97 - }, 98 - "node_modules/@eslint/core": { 99 - "version": "0.17.0", 100 - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", 101 - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", 102 - "dev": true, 103 - "license": "Apache-2.0", 104 - "dependencies": { 105 - "@types/json-schema": "^7.0.15" 106 - }, 107 - "engines": { 108 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 109 - } 110 - }, 111 - "node_modules/@eslint/eslintrc": { 112 - "version": "3.3.5", 113 - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", 114 - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", 115 - "dev": true, 116 - "license": "MIT", 117 - "dependencies": { 118 - "ajv": "^6.14.0", 119 - "debug": "^4.3.2", 120 - "espree": "^10.0.1", 121 - "globals": "^14.0.0", 122 - "ignore": "^5.2.0", 123 - "import-fresh": "^3.2.1", 124 - "js-yaml": "^4.1.1", 125 - "minimatch": "^3.1.5", 126 - "strip-json-comments": "^3.1.1" 127 - }, 128 - "engines": { 129 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 130 - }, 131 - "funding": { 132 - "url": "https://opencollective.com/eslint" 133 - } 134 - }, 135 - "node_modules/@eslint/eslintrc/node_modules/globals": { 136 - "version": "14.0.0", 137 - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 138 - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 139 - "dev": true, 140 - "license": "MIT", 141 - "engines": { 142 - "node": ">=18" 143 - }, 144 - "funding": { 145 - "url": "https://github.com/sponsors/sindresorhus" 146 - } 147 - }, 148 - "node_modules/@eslint/js": { 149 - "version": "9.39.4", 150 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", 151 - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", 152 - "dev": true, 153 - "license": "MIT", 154 - "engines": { 155 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 156 - }, 157 - "funding": { 158 - "url": "https://eslint.org/donate" 159 - } 160 - }, 161 - "node_modules/@eslint/object-schema": { 162 - "version": "2.1.7", 163 - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", 164 - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", 165 - "dev": true, 166 - "license": "Apache-2.0", 167 - "engines": { 168 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 169 - } 170 - }, 171 - "node_modules/@eslint/plugin-kit": { 172 - "version": "0.4.1", 173 - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", 174 - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", 175 - "dev": true, 176 - "license": "Apache-2.0", 177 - "dependencies": { 178 - "@eslint/core": "^0.17.0", 179 - "levn": "^0.4.1" 180 - }, 181 - "engines": { 182 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 183 - } 184 - }, 185 - "node_modules/@humanfs/core": { 186 - "version": "0.19.1", 187 - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 188 - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 189 - "dev": true, 190 - "license": "Apache-2.0", 191 - "engines": { 192 - "node": ">=18.18.0" 193 - } 194 - }, 195 - "node_modules/@humanfs/node": { 196 - "version": "0.16.7", 197 - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 198 - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 199 - "dev": true, 200 - "license": "Apache-2.0", 201 - "dependencies": { 202 - "@humanfs/core": "^0.19.1", 203 - "@humanwhocodes/retry": "^0.4.0" 204 - }, 205 - "engines": { 206 - "node": ">=18.18.0" 207 - } 208 - }, 209 - "node_modules/@humanwhocodes/module-importer": { 210 - "version": "1.0.1", 211 - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 212 - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 213 - "dev": true, 214 - "license": "Apache-2.0", 215 - "engines": { 216 - "node": ">=12.22" 217 - }, 218 - "funding": { 219 - "type": "github", 220 - "url": "https://github.com/sponsors/nzakas" 221 - } 222 - }, 223 - "node_modules/@humanwhocodes/retry": { 224 - "version": "0.4.3", 225 - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 226 - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 227 - "dev": true, 228 - "license": "Apache-2.0", 229 - "engines": { 230 - "node": ">=18.18" 231 - }, 232 - "funding": { 233 - "type": "github", 234 - "url": "https://github.com/sponsors/nzakas" 235 - } 236 - }, 237 - "node_modules/@types/estree": { 238 - "version": "1.0.8", 239 - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 240 - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 241 - "dev": true, 242 - "license": "MIT" 243 - }, 244 - "node_modules/@types/json-schema": { 245 - "version": "7.0.15", 246 - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 247 - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 248 - "dev": true, 249 - "license": "MIT" 250 - }, 251 - "node_modules/@types/node": { 252 - "version": "24.12.0", 253 - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", 254 - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", 255 - "dev": true, 256 - "license": "MIT", 257 - "dependencies": { 258 - "undici-types": "~7.16.0" 259 - } 260 - }, 261 - "node_modules/acorn": { 262 - "version": "8.16.0", 263 - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", 264 - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", 265 - "dev": true, 266 - "license": "MIT", 267 - "bin": { 268 - "acorn": "bin/acorn" 269 - }, 270 - "engines": { 271 - "node": ">=0.4.0" 272 - } 273 - }, 274 - "node_modules/acorn-jsx": { 275 - "version": "5.3.2", 276 - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 277 - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 278 - "dev": true, 279 - "license": "MIT", 280 - "peerDependencies": { 281 - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 282 - } 283 - }, 284 - "node_modules/ajv": { 285 - "version": "6.14.0", 286 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", 287 - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", 288 - "dev": true, 289 - "license": "MIT", 290 - "dependencies": { 291 - "fast-deep-equal": "^3.1.1", 292 - "fast-json-stable-stringify": "^2.0.0", 293 - "json-schema-traverse": "^0.4.1", 294 - "uri-js": "^4.2.2" 295 - }, 296 - "funding": { 297 - "type": "github", 298 - "url": "https://github.com/sponsors/epoberezkin" 299 - } 300 - }, 301 - "node_modules/ansi-styles": { 302 - "version": "4.3.0", 303 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 304 - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 305 - "dev": true, 306 - "license": "MIT", 307 - "dependencies": { 308 - "color-convert": "^2.0.1" 309 - }, 310 - "engines": { 311 - "node": ">=8" 312 - }, 313 - "funding": { 314 - "url": "https://github.com/chalk/ansi-styles?sponsor=1" 315 - } 316 - }, 317 - "node_modules/argparse": { 318 - "version": "2.0.1", 319 - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 320 - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 321 - "dev": true, 322 - "license": "Python-2.0" 323 - }, 324 - "node_modules/balanced-match": { 325 - "version": "1.0.2", 326 - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 327 - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 328 - "dev": true, 329 - "license": "MIT" 330 - }, 331 - "node_modules/brace-expansion": { 332 - "version": "1.1.13", 333 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", 334 - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", 335 - "dev": true, 336 - "license": "MIT", 337 - "dependencies": { 338 - "balanced-match": "^1.0.0", 339 - "concat-map": "0.0.1" 340 - } 341 - }, 342 - "node_modules/callsites": { 343 - "version": "3.1.0", 344 - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 345 - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 346 - "dev": true, 347 - "license": "MIT", 348 - "engines": { 349 - "node": ">=6" 350 - } 351 - }, 352 - "node_modules/chalk": { 353 - "version": "4.1.2", 354 - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 355 - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 356 - "dev": true, 357 - "license": "MIT", 358 - "dependencies": { 359 - "ansi-styles": "^4.1.0", 360 - "supports-color": "^7.1.0" 361 - }, 362 - "engines": { 363 - "node": ">=10" 364 - }, 365 - "funding": { 366 - "url": "https://github.com/chalk/chalk?sponsor=1" 367 - } 368 - }, 369 - "node_modules/color-convert": { 370 - "version": "2.0.1", 371 - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 372 - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 373 - "dev": true, 374 - "license": "MIT", 375 - "dependencies": { 376 - "color-name": "~1.1.4" 377 - }, 378 - "engines": { 379 - "node": ">=7.0.0" 380 - } 381 - }, 382 - "node_modules/color-name": { 383 - "version": "1.1.4", 384 - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 385 - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 386 - "dev": true, 387 - "license": "MIT" 388 - }, 389 - "node_modules/concat-map": { 390 - "version": "0.0.1", 391 - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 392 - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 393 - "dev": true, 394 - "license": "MIT" 395 - }, 396 - "node_modules/cross-spawn": { 397 - "version": "7.0.6", 398 - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 399 - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 400 - "dev": true, 401 - "license": "MIT", 402 - "dependencies": { 403 - "path-key": "^3.1.0", 404 - "shebang-command": "^2.0.0", 405 - "which": "^2.0.1" 406 - }, 407 - "engines": { 408 - "node": ">= 8" 409 - } 410 - }, 411 - "node_modules/debug": { 412 - "version": "4.4.3", 413 - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 414 - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 415 - "dev": true, 416 - "license": "MIT", 417 - "dependencies": { 418 - "ms": "^2.1.3" 419 - }, 420 - "engines": { 421 - "node": ">=6.0" 422 - }, 423 - "peerDependenciesMeta": { 424 - "supports-color": { 425 - "optional": true 426 - } 427 - } 428 - }, 429 - "node_modules/deep-is": { 430 - "version": "0.1.4", 431 - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 432 - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 433 - "dev": true, 434 - "license": "MIT" 435 - }, 436 - "node_modules/escape-string-regexp": { 437 - "version": "4.0.0", 438 - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 439 - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 440 - "dev": true, 441 - "license": "MIT", 442 - "engines": { 443 - "node": ">=10" 444 - }, 445 - "funding": { 446 - "url": "https://github.com/sponsors/sindresorhus" 447 - } 448 - }, 449 - "node_modules/eslint": { 450 - "version": "9.39.4", 451 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", 452 - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", 453 - "dev": true, 454 - "license": "MIT", 455 - "dependencies": { 456 - "@eslint-community/eslint-utils": "^4.8.0", 457 - "@eslint-community/regexpp": "^4.12.1", 458 - "@eslint/config-array": "^0.21.2", 459 - "@eslint/config-helpers": "^0.4.2", 460 - "@eslint/core": "^0.17.0", 461 - "@eslint/eslintrc": "^3.3.5", 462 - "@eslint/js": "9.39.4", 463 - "@eslint/plugin-kit": "^0.4.1", 464 - "@humanfs/node": "^0.16.6", 465 - "@humanwhocodes/module-importer": "^1.0.1", 466 - "@humanwhocodes/retry": "^0.4.2", 467 - "@types/estree": "^1.0.6", 468 - "ajv": "^6.14.0", 469 - "chalk": "^4.0.0", 470 - "cross-spawn": "^7.0.6", 471 - "debug": "^4.3.2", 472 - "escape-string-regexp": "^4.0.0", 473 - "eslint-scope": "^8.4.0", 474 - "eslint-visitor-keys": "^4.2.1", 475 - "espree": "^10.4.0", 476 - "esquery": "^1.5.0", 477 - "esutils": "^2.0.2", 478 - "fast-deep-equal": "^3.1.3", 479 - "file-entry-cache": "^8.0.0", 480 - "find-up": "^5.0.0", 481 - "glob-parent": "^6.0.2", 482 - "ignore": "^5.2.0", 483 - "imurmurhash": "^0.1.4", 484 - "is-glob": "^4.0.0", 485 - "json-stable-stringify-without-jsonify": "^1.0.1", 486 - "lodash.merge": "^4.6.2", 487 - "minimatch": "^3.1.5", 488 - "natural-compare": "^1.4.0", 489 - "optionator": "^0.9.3" 490 - }, 491 - "bin": { 492 - "eslint": "bin/eslint.js" 493 - }, 494 - "engines": { 495 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 496 - }, 497 - "funding": { 498 - "url": "https://eslint.org/donate" 499 - }, 500 - "peerDependencies": { 501 - "jiti": "*" 502 - }, 503 - "peerDependenciesMeta": { 504 - "jiti": { 505 - "optional": true 506 - } 507 - } 508 - }, 509 - "node_modules/eslint-scope": { 510 - "version": "8.4.0", 511 - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 512 - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 513 - "dev": true, 514 - "license": "BSD-2-Clause", 515 - "dependencies": { 516 - "esrecurse": "^4.3.0", 517 - "estraverse": "^5.2.0" 518 - }, 519 - "engines": { 520 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 521 - }, 522 - "funding": { 523 - "url": "https://opencollective.com/eslint" 524 - } 525 - }, 526 - "node_modules/eslint-visitor-keys": { 527 - "version": "4.2.1", 528 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 529 - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 530 - "dev": true, 531 - "license": "Apache-2.0", 532 - "engines": { 533 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 534 - }, 535 - "funding": { 536 - "url": "https://opencollective.com/eslint" 537 - } 538 - }, 539 - "node_modules/espree": { 540 - "version": "10.4.0", 541 - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 542 - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 543 - "dev": true, 544 - "license": "BSD-2-Clause", 545 - "dependencies": { 546 - "acorn": "^8.15.0", 547 - "acorn-jsx": "^5.3.2", 548 - "eslint-visitor-keys": "^4.2.1" 549 - }, 550 - "engines": { 551 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 552 - }, 553 - "funding": { 554 - "url": "https://opencollective.com/eslint" 555 - } 556 - }, 557 - "node_modules/esquery": { 558 - "version": "1.7.0", 559 - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", 560 - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", 561 - "dev": true, 562 - "license": "BSD-3-Clause", 563 - "dependencies": { 564 - "estraverse": "^5.1.0" 565 - }, 566 - "engines": { 567 - "node": ">=0.10" 568 - } 569 - }, 570 - "node_modules/esrecurse": { 571 - "version": "4.3.0", 572 - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 573 - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 574 - "dev": true, 575 - "license": "BSD-2-Clause", 576 - "dependencies": { 577 - "estraverse": "^5.2.0" 578 - }, 579 - "engines": { 580 - "node": ">=4.0" 581 - } 582 - }, 583 - "node_modules/estraverse": { 584 - "version": "5.3.0", 585 - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 586 - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 587 - "dev": true, 588 - "license": "BSD-2-Clause", 589 - "engines": { 590 - "node": ">=4.0" 591 - } 592 - }, 593 - "node_modules/esutils": { 594 - "version": "2.0.3", 595 - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 596 - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 597 - "dev": true, 598 - "license": "BSD-2-Clause", 599 - "engines": { 600 - "node": ">=0.10.0" 601 - } 602 - }, 603 - "node_modules/fast-deep-equal": { 604 - "version": "3.1.3", 605 - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 606 - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 607 - "dev": true, 608 - "license": "MIT" 609 - }, 610 - "node_modules/fast-json-stable-stringify": { 611 - "version": "2.1.0", 612 - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 613 - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 614 - "dev": true, 615 - "license": "MIT" 616 - }, 617 - "node_modules/fast-levenshtein": { 618 - "version": "2.0.6", 619 - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 620 - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 621 - "dev": true, 622 - "license": "MIT" 623 - }, 624 - "node_modules/file-entry-cache": { 625 - "version": "8.0.0", 626 - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 627 - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 628 - "dev": true, 629 - "license": "MIT", 630 - "dependencies": { 631 - "flat-cache": "^4.0.0" 632 - }, 633 - "engines": { 634 - "node": ">=16.0.0" 635 - } 636 - }, 637 - "node_modules/find-up": { 638 - "version": "5.0.0", 639 - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 640 - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 641 - "dev": true, 642 - "license": "MIT", 643 - "dependencies": { 644 - "locate-path": "^6.0.0", 645 - "path-exists": "^4.0.0" 646 - }, 647 - "engines": { 648 - "node": ">=10" 649 - }, 650 - "funding": { 651 - "url": "https://github.com/sponsors/sindresorhus" 652 - } 653 - }, 654 - "node_modules/flat-cache": { 655 - "version": "4.0.1", 656 - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 657 - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 658 - "dev": true, 659 - "license": "MIT", 660 - "dependencies": { 661 - "flatted": "^3.2.9", 662 - "keyv": "^4.5.4" 663 - }, 664 - "engines": { 665 - "node": ">=16" 666 - } 667 - }, 668 - "node_modules/flatted": { 669 - "version": "3.4.2", 670 - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", 671 - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", 672 - "dev": true, 673 - "license": "ISC" 674 - }, 675 - "node_modules/fsevents": { 676 - "version": "2.3.2", 677 - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 678 - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 679 - "hasInstallScript": true, 680 - "license": "MIT", 681 - "optional": true, 682 - "os": [ 683 - "darwin" 684 - ], 685 - "engines": { 686 - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 687 - } 688 - }, 689 - "node_modules/glob-parent": { 690 - "version": "6.0.2", 691 - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 692 - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 693 - "dev": true, 694 - "license": "ISC", 695 - "dependencies": { 696 - "is-glob": "^4.0.3" 697 - }, 698 - "engines": { 699 - "node": ">=10.13.0" 700 - } 701 - }, 702 - "node_modules/globals": { 703 - "version": "17.4.0", 704 - "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", 705 - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", 706 - "dev": true, 707 - "license": "MIT", 708 - "engines": { 709 - "node": ">=18" 710 - }, 711 - "funding": { 712 - "url": "https://github.com/sponsors/sindresorhus" 713 - } 714 - }, 715 - "node_modules/has-flag": { 716 - "version": "4.0.0", 717 - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 718 - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 719 - "dev": true, 720 - "license": "MIT", 721 - "engines": { 722 - "node": ">=8" 723 - } 724 - }, 725 - "node_modules/ignore": { 726 - "version": "5.3.2", 727 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 728 - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 729 - "dev": true, 730 - "license": "MIT", 731 - "engines": { 732 - "node": ">= 4" 733 - } 734 - }, 735 - "node_modules/import-fresh": { 736 - "version": "3.3.1", 737 - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 738 - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 739 - "dev": true, 740 - "license": "MIT", 741 - "dependencies": { 742 - "parent-module": "^1.0.0", 743 - "resolve-from": "^4.0.0" 744 - }, 745 - "engines": { 746 - "node": ">=6" 747 - }, 748 - "funding": { 749 - "url": "https://github.com/sponsors/sindresorhus" 750 - } 751 - }, 752 - "node_modules/imurmurhash": { 753 - "version": "0.1.4", 754 - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 755 - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 756 - "dev": true, 757 - "license": "MIT", 758 - "engines": { 759 - "node": ">=0.8.19" 760 - } 761 - }, 762 - "node_modules/is-extglob": { 763 - "version": "2.1.1", 764 - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 765 - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 766 - "dev": true, 767 - "license": "MIT", 768 - "engines": { 769 - "node": ">=0.10.0" 770 - } 771 - }, 772 - "node_modules/is-glob": { 773 - "version": "4.0.3", 774 - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 775 - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 776 - "dev": true, 777 - "license": "MIT", 778 - "dependencies": { 779 - "is-extglob": "^2.1.1" 780 - }, 781 - "engines": { 782 - "node": ">=0.10.0" 783 - } 784 - }, 785 - "node_modules/isexe": { 786 - "version": "2.0.0", 787 - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 788 - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 789 - "dev": true, 790 - "license": "ISC" 791 - }, 792 - "node_modules/js-yaml": { 793 - "version": "4.1.1", 794 - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", 795 - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", 796 - "dev": true, 797 - "license": "MIT", 798 - "dependencies": { 799 - "argparse": "^2.0.1" 800 - }, 801 - "bin": { 802 - "js-yaml": "bin/js-yaml.js" 803 - } 804 - }, 805 - "node_modules/json-buffer": { 806 - "version": "3.0.1", 807 - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 808 - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 809 - "dev": true, 810 - "license": "MIT" 811 - }, 812 - "node_modules/json-schema-traverse": { 813 - "version": "0.4.1", 814 - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 815 - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 816 - "dev": true, 817 - "license": "MIT" 818 - }, 819 - "node_modules/json-stable-stringify-without-jsonify": { 820 - "version": "1.0.1", 821 - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 822 - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 823 - "dev": true, 824 - "license": "MIT" 825 - }, 826 - "node_modules/keyv": { 827 - "version": "4.5.4", 828 - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 829 - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 830 - "dev": true, 831 - "license": "MIT", 832 - "dependencies": { 833 - "json-buffer": "3.0.1" 834 - } 835 - }, 836 - "node_modules/levn": { 837 - "version": "0.4.1", 838 - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 839 - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 840 - "dev": true, 841 - "license": "MIT", 842 - "dependencies": { 843 - "prelude-ls": "^1.2.1", 844 - "type-check": "~0.4.0" 845 - }, 846 - "engines": { 847 - "node": ">= 0.8.0" 848 - } 849 - }, 850 - "node_modules/locate-path": { 851 - "version": "6.0.0", 852 - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 853 - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 854 - "dev": true, 855 - "license": "MIT", 856 - "dependencies": { 857 - "p-locate": "^5.0.0" 858 - }, 859 - "engines": { 860 - "node": ">=10" 861 - }, 862 - "funding": { 863 - "url": "https://github.com/sponsors/sindresorhus" 864 - } 865 - }, 866 - "node_modules/lodash.merge": { 867 - "version": "4.6.2", 868 - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 869 - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 870 - "dev": true, 871 - "license": "MIT" 872 - }, 873 - "node_modules/minimatch": { 874 - "version": "3.1.5", 875 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", 876 - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", 877 - "dev": true, 878 - "license": "ISC", 879 - "dependencies": { 880 - "brace-expansion": "^1.1.7" 881 - }, 882 - "engines": { 883 - "node": "*" 884 - } 885 - }, 886 - "node_modules/ms": { 887 - "version": "2.1.3", 888 - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 889 - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 890 - "dev": true, 891 - "license": "MIT" 892 - }, 893 - "node_modules/natural-compare": { 894 - "version": "1.4.0", 895 - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 896 - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 897 - "dev": true, 898 - "license": "MIT" 899 - }, 900 - "node_modules/optionator": { 901 - "version": "0.9.4", 902 - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 903 - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 904 - "dev": true, 905 - "license": "MIT", 906 - "dependencies": { 907 - "deep-is": "^0.1.3", 908 - "fast-levenshtein": "^2.0.6", 909 - "levn": "^0.4.1", 910 - "prelude-ls": "^1.2.1", 911 - "type-check": "^0.4.0", 912 - "word-wrap": "^1.2.5" 913 - }, 914 - "engines": { 915 - "node": ">= 0.8.0" 916 - } 917 - }, 918 - "node_modules/p-limit": { 919 - "version": "3.1.0", 920 - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 921 - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 922 - "dev": true, 923 - "license": "MIT", 924 - "dependencies": { 925 - "yocto-queue": "^0.1.0" 926 - }, 927 - "engines": { 928 - "node": ">=10" 929 - }, 930 - "funding": { 931 - "url": "https://github.com/sponsors/sindresorhus" 932 - } 933 - }, 934 - "node_modules/p-locate": { 935 - "version": "5.0.0", 936 - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 937 - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 938 - "dev": true, 939 - "license": "MIT", 940 - "dependencies": { 941 - "p-limit": "^3.0.2" 942 - }, 943 - "engines": { 944 - "node": ">=10" 945 - }, 946 - "funding": { 947 - "url": "https://github.com/sponsors/sindresorhus" 948 - } 949 - }, 950 - "node_modules/parent-module": { 951 - "version": "1.0.1", 952 - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 953 - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 954 - "dev": true, 955 - "license": "MIT", 956 - "dependencies": { 957 - "callsites": "^3.0.0" 958 - }, 959 - "engines": { 960 - "node": ">=6" 961 - } 962 - }, 963 - "node_modules/path-exists": { 964 - "version": "4.0.0", 965 - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 966 - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 967 - "dev": true, 968 - "license": "MIT", 969 - "engines": { 970 - "node": ">=8" 971 - } 972 - }, 973 - "node_modules/path-key": { 974 - "version": "3.1.1", 975 - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 976 - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 977 - "dev": true, 978 - "license": "MIT", 979 - "engines": { 980 - "node": ">=8" 981 - } 982 - }, 983 - "node_modules/playwright": { 984 - "version": "1.58.2", 985 - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", 986 - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", 987 - "license": "Apache-2.0", 988 - "dependencies": { 989 - "playwright-core": "1.58.2" 990 - }, 991 - "bin": { 992 - "playwright": "cli.js" 993 - }, 994 - "engines": { 995 - "node": ">=18" 996 - }, 997 - "optionalDependencies": { 998 - "fsevents": "2.3.2" 999 - } 1000 - }, 1001 - "node_modules/playwright-core": { 1002 - "version": "1.58.2", 1003 - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", 1004 - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", 1005 - "license": "Apache-2.0", 1006 - "bin": { 1007 - "playwright-core": "cli.js" 1008 - }, 1009 - "engines": { 1010 - "node": ">=18" 1011 - } 1012 - }, 1013 - "node_modules/prelude-ls": { 1014 - "version": "1.2.1", 1015 - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1016 - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1017 - "dev": true, 1018 - "license": "MIT", 1019 - "engines": { 1020 - "node": ">= 0.8.0" 1021 - } 1022 - }, 1023 - "node_modules/prettier": { 1024 - "version": "3.8.1", 1025 - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", 1026 - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", 1027 - "dev": true, 1028 - "license": "MIT", 1029 - "bin": { 1030 - "prettier": "bin/prettier.cjs" 1031 - }, 1032 - "engines": { 1033 - "node": ">=14" 1034 - }, 1035 - "funding": { 1036 - "url": "https://github.com/prettier/prettier?sponsor=1" 1037 - } 1038 - }, 1039 - "node_modules/punycode": { 1040 - "version": "2.3.1", 1041 - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1042 - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1043 - "dev": true, 1044 - "license": "MIT", 1045 - "engines": { 1046 - "node": ">=6" 1047 - } 1048 - }, 1049 - "node_modules/resolve-from": { 1050 - "version": "4.0.0", 1051 - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1052 - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1053 - "dev": true, 1054 - "license": "MIT", 1055 - "engines": { 1056 - "node": ">=4" 1057 - } 1058 - }, 1059 - "node_modules/shebang-command": { 1060 - "version": "2.0.0", 1061 - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1062 - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1063 - "dev": true, 1064 - "license": "MIT", 1065 - "dependencies": { 1066 - "shebang-regex": "^3.0.0" 1067 - }, 1068 - "engines": { 1069 - "node": ">=8" 1070 - } 1071 - }, 1072 - "node_modules/shebang-regex": { 1073 - "version": "3.0.0", 1074 - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1075 - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1076 - "dev": true, 1077 - "license": "MIT", 1078 - "engines": { 1079 - "node": ">=8" 1080 - } 1081 - }, 1082 - "node_modules/strip-json-comments": { 1083 - "version": "3.1.1", 1084 - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1085 - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1086 - "dev": true, 1087 - "license": "MIT", 1088 - "engines": { 1089 - "node": ">=8" 1090 - }, 1091 - "funding": { 1092 - "url": "https://github.com/sponsors/sindresorhus" 1093 - } 1094 - }, 1095 - "node_modules/supports-color": { 1096 - "version": "7.2.0", 1097 - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1098 - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1099 - "dev": true, 1100 - "license": "MIT", 1101 - "dependencies": { 1102 - "has-flag": "^4.0.0" 1103 - }, 1104 - "engines": { 1105 - "node": ">=8" 1106 - } 1107 - }, 1108 - "node_modules/type-check": { 1109 - "version": "0.4.0", 1110 - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1111 - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1112 - "dev": true, 1113 - "license": "MIT", 1114 - "dependencies": { 1115 - "prelude-ls": "^1.2.1" 1116 - }, 1117 - "engines": { 1118 - "node": ">= 0.8.0" 1119 - } 1120 - }, 1121 - "node_modules/typescript": { 1122 - "version": "5.9.3", 1123 - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 1124 - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 1125 - "dev": true, 1126 - "license": "Apache-2.0", 1127 - "bin": { 1128 - "tsc": "bin/tsc", 1129 - "tsserver": "bin/tsserver" 1130 - }, 1131 - "engines": { 1132 - "node": ">=14.17" 1133 - } 1134 - }, 1135 - "node_modules/undici-types": { 1136 - "version": "7.16.0", 1137 - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 1138 - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 1139 - "dev": true, 1140 - "license": "MIT" 1141 - }, 1142 - "node_modules/uri-js": { 1143 - "version": "4.4.1", 1144 - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1145 - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1146 - "dev": true, 1147 - "license": "BSD-2-Clause", 1148 - "dependencies": { 1149 - "punycode": "^2.1.0" 1150 - } 1151 - }, 1152 - "node_modules/which": { 1153 - "version": "2.0.2", 1154 - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1155 - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1156 - "dev": true, 1157 - "license": "ISC", 1158 - "dependencies": { 1159 - "isexe": "^2.0.0" 1160 - }, 1161 - "bin": { 1162 - "node-which": "bin/node-which" 1163 - }, 1164 - "engines": { 1165 - "node": ">= 8" 1166 - } 1167 - }, 1168 - "node_modules/word-wrap": { 1169 - "version": "1.2.5", 1170 - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 1171 - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 1172 - "dev": true, 1173 - "license": "MIT", 1174 - "engines": { 1175 - "node": ">=0.10.0" 1176 - } 1177 - }, 1178 - "node_modules/yocto-queue": { 1179 - "version": "0.1.0", 1180 - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1181 - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1182 - "dev": true, 1183 - "license": "MIT", 1184 - "engines": { 1185 - "node": ">=10" 1186 - }, 1187 - "funding": { 1188 - "url": "https://github.com/sponsors/sindresorhus" 1189 - } 1190 - } 1191 - } 1192 - }
+26 -20
package.json
··· 2 2 "name": "atproto-smoke", 3 3 "version": "0.2.0", 4 4 "private": true, 5 + "packageManager": "bun@1.3.11", 5 6 "description": "ATProto PDS compatibility smoke and end-to-end browser checks", 6 7 "type": "module", 7 8 "scripts": { 8 - "help": "node bin/atproto-smoke.mjs --help", 9 - "list-adapters": "node bin/atproto-smoke.mjs list-adapters", 9 + "build": "tsgo --project tsconfig.json", 10 + "clean": "rm -rf dist", 11 + "help": "tsx bin/atproto-smoke.ts --help", 12 + "list-adapters": "tsx bin/atproto-smoke.ts list-adapters", 10 13 "lint": "eslint .", 11 - "typecheck": "tsc --project tsconfig.json", 14 + "typecheck": "tsgo --project tsconfig.json --noEmit", 12 15 "format": "prettier --write .", 13 16 "format:check": "prettier --check .", 14 - "print-example:dual": "node bin/atproto-smoke.mjs print-example --mode dual", 15 - "write:pdslab-configs": "node scripts/write-pdslab-configs.mjs", 16 - "validate:example:single": "node bin/atproto-smoke.mjs validate --mode single --config examples/bring-your-own-single.json", 17 - "validate:example:dual": "node bin/atproto-smoke.mjs validate --mode dual --config examples/bring-your-own-dual.json", 18 - "validate:example:perlsky": "node bin/atproto-smoke.mjs validate --mode dual --adapter perlsky --config examples/perlsky-dual.json", 19 - "validate:example:tranquil-pds": "node bin/atproto-smoke.mjs validate --mode dual --adapter tranquil-pds --config examples/tranquil-pds-dual.json" 17 + "print-example:dual": "tsx bin/atproto-smoke.ts print-example --mode dual", 18 + "write:pdslab-configs": "tsx scripts/write-pdslab-configs.ts", 19 + "validate:example:single": "tsx bin/atproto-smoke.ts validate --mode single --config examples/bring-your-own-single.json", 20 + "validate:example:dual": "tsx bin/atproto-smoke.ts validate --mode dual --config examples/bring-your-own-dual.json", 21 + "validate:example:perlsky": "tsx bin/atproto-smoke.ts validate --mode dual --adapter perlsky --config examples/perlsky-dual.json", 22 + "validate:example:tranquil-pds": "tsx bin/atproto-smoke.ts validate --mode dual --adapter tranquil-pds --config examples/tranquil-pds-dual.json" 20 23 }, 21 24 "devDependencies": { 22 25 "@eslint/js": "^9.39.2", 26 + "@typescript/native-preview": "latest", 23 27 "@types/node": "^24.7.2", 24 28 "eslint": "^9.39.2", 25 29 "globals": "^17.0.0", 26 30 "prettier": "^3.8.0", 31 + "tsx": "^4.20.6", 32 + "typescript-eslint": "^8.46.1", 27 33 "typescript": "^5.9.3" 28 34 }, 29 35 "engines": { 30 36 "node": ">=20" 31 37 }, 32 38 "bin": { 33 - "atproto-smoke": "./bin/atproto-smoke.mjs" 39 + "atproto-smoke": "./dist/bin/atproto-smoke.js" 34 40 }, 35 41 "dependencies": { 36 42 "playwright": "^1.54.2" ··· 41 47 }, 42 48 "homepage": "https://github.com/aliceisjustplaying/atproto-smoke", 43 49 "exports": { 44 - ".": "./src/index.mjs", 45 - "./config": "./src/config.mjs", 46 - "./adapters/bring-your-own": "./src/adapters/bring-your-own.mjs", 47 - "./adapters/perlsky": "./src/adapters/perlsky.mjs", 48 - "./adapters/tranquil-pds": "./src/adapters/tranquil-pds.mjs", 49 - "./adapters/registry": "./src/adapters/registry.mjs", 50 - "./lab/pdslab-targets": "./src/lab/pdslab-targets.mjs", 51 - "./browser/run-single": "./src/browser/run-single.mjs", 52 - "./browser/run-dual": "./src/browser/run-dual.mjs", 53 - "./cli": "./src/cli.mjs" 50 + ".": "./dist/src/index.js", 51 + "./config": "./dist/src/config.js", 52 + "./adapters/bring-your-own": "./dist/src/adapters/bring-your-own.js", 53 + "./adapters/perlsky": "./dist/src/adapters/perlsky.js", 54 + "./adapters/tranquil-pds": "./dist/src/adapters/tranquil-pds.js", 55 + "./adapters/registry": "./dist/src/adapters/registry.js", 56 + "./lab/pdslab-targets": "./dist/src/lab/pdslab-targets.js", 57 + "./browser/run-single": "./dist/src/browser/run-single.js", 58 + "./browser/run-dual": "./dist/src/browser/run-dual.js", 59 + "./cli": "./dist/src/cli.js" 54 60 } 55 61 }
+111 -29
scripts/write-pdslab-configs.mjs scripts/write-pdslab-configs.ts
··· 3 3 import fs from "node:fs/promises"; 4 4 import path from "node:path"; 5 5 import { fileURLToPath } from "node:url"; 6 - import { PDSLAB_TARGETS } from "../src/lab/pdslab-targets.mjs"; 6 + import { PDSLAB_TARGETS } from "../src/lab/pdslab-targets.js"; 7 + import type { AccountConfig, FlexibleRecord } from "../src/types.js"; 8 + import { errorMessage } from "../src/browser/lib/runtime-utils.js"; 9 + 10 + interface PlanTarget { 11 + id: string; 12 + mode: string; 13 + runnerStatus: string; 14 + config: FlexibleRecord; 15 + } 16 + 17 + interface SkippedTarget { 18 + id: string; 19 + mode: string; 20 + runnerStatus: string; 21 + notes: unknown; 22 + } 23 + 24 + interface Plan { 25 + generatedAt: string; 26 + domain: unknown; 27 + runnableTargets: PlanTarget[]; 28 + skippedTargets: SkippedTarget[]; 29 + } 7 30 8 31 const repoRoot = path.resolve( 9 32 path.dirname(fileURLToPath(import.meta.url)), 10 33 "..", 11 34 ); 12 35 13 - const parseArgs = (argv) => { 14 - const result = { 36 + const parseArgs = ( 37 + argv: string[], 38 + ): { ledgerPath: string; outputDir: string; help?: boolean } => { 39 + const result: { ledgerPath: string; outputDir: string; help?: boolean } = { 15 40 ledgerPath: path.join(repoRoot, ".tmp", "smoke-accounts.local.json"), 16 41 outputDir: path.join(repoRoot, ".tmp", "generated", "pdslab-configs"), 17 42 }; ··· 37 62 }; 38 63 39 64 const usage = `Usage: 40 - node scripts/write-pdslab-configs.mjs [--ledger .tmp/smoke-accounts.local.json] [--output-dir .tmp/generated/pdslab-configs] 65 + bun run write:pdslab-configs [--ledger .tmp/smoke-accounts.local.json] [--output-dir .tmp/generated/pdslab-configs] 41 66 `; 42 67 43 - const readJson = async (filePath) => { 68 + const readJson = async (filePath: string): Promise<FlexibleRecord> => { 44 69 return JSON.parse(await fs.readFile(filePath, "utf8")); 45 70 }; 46 71 47 - const ensureTarget = (ledger, targetId) => { 72 + const ensureTarget = ( 73 + ledger: FlexibleRecord, 74 + targetId: string, 75 + ): FlexibleRecord => { 48 76 const target = ledger.targets?.[targetId]; 49 77 if (!target) { 50 78 throw new Error(`ledger target missing: ${targetId}`); ··· 52 80 return target; 53 81 }; 54 82 55 - const createAccountDefaults = ({ spec, role }) => { 83 + const createAccountDefaults = ({ 84 + spec, 85 + role, 86 + }: { 87 + spec: FlexibleRecord; 88 + role: string; 89 + }): FlexibleRecord => { 56 90 const prefix = `pdslab ${spec.id} ${role}`; 57 91 return { 58 92 postText: `${prefix} root post`, ··· 64 98 }; 65 99 }; 66 100 67 - const createAccount = ({ account, loginIdentifierKey, spec, role }) => { 101 + const createAccount = ({ 102 + account, 103 + loginIdentifierKey, 104 + spec, 105 + role, 106 + }: { 107 + account: FlexibleRecord; 108 + loginIdentifierKey?: string; 109 + spec: FlexibleRecord; 110 + role: string; 111 + }): FlexibleRecord => { 68 112 if (!account) { 69 113 throw new Error("account details are required"); 70 114 } 71 115 72 - const normalized = { 116 + const normalized: FlexibleRecord = { 73 117 ...createAccountDefaults({ spec, role }), 74 118 handle: account.handle, 75 119 password: account.password, ··· 101 145 return normalized; 102 146 }; 103 147 104 - const createSingleConfig = ({ spec, ledgerTarget }) => { 148 + const createSingleConfig = ({ 149 + spec, 150 + ledgerTarget, 151 + }: { 152 + spec: FlexibleRecord; 153 + ledgerTarget: FlexibleRecord; 154 + }): FlexibleRecord => { 105 155 const accountSource = spec.currentDeploymentKey 106 - ? ledgerTarget[spec.currentDeploymentKey] 107 - : ledgerTarget.accounts?.[spec.ledgerAccount]; 156 + ? ledgerTarget[String(spec.currentDeploymentKey)] 157 + : ledgerTarget.accounts?.[String(spec.ledgerAccount)]; 108 158 109 159 if (!accountSource) { 110 160 throw new Error( ··· 126 176 account: accountSource, 127 177 loginIdentifierKey: spec.loginIdentifierKey, 128 178 spec, 129 - role: spec.ledgerAccount || "single", 179 + role: 180 + typeof spec.ledgerAccount === "string" ? spec.ledgerAccount : "single", 130 181 }), 131 182 }; 132 183 }; 133 184 134 - const createDualConfig = ({ spec, ledgerTarget }) => { 185 + const createDualConfig = ({ 186 + spec, 187 + ledgerTarget, 188 + }: { 189 + spec: FlexibleRecord; 190 + ledgerTarget: FlexibleRecord; 191 + }): FlexibleRecord => { 135 192 const primary = ledgerTarget.accounts?.["smoke-a"]; 136 193 const secondary = ledgerTarget.accounts?.["smoke-b"]; 137 194 if (!primary || !secondary) { ··· 157 214 spec, 158 215 primaryLedgerTarget, 159 216 secondaryLedgerTarget, 160 - }) => { 217 + }: { 218 + spec: FlexibleRecord; 219 + primaryLedgerTarget: FlexibleRecord; 220 + secondaryLedgerTarget: FlexibleRecord; 221 + }): FlexibleRecord => { 161 222 const primarySource = spec.primaryCurrentDeploymentKey 162 - ? primaryLedgerTarget[spec.primaryCurrentDeploymentKey] 163 - : primaryLedgerTarget.accounts?.[spec.primaryLedgerAccount]; 223 + ? primaryLedgerTarget[String(spec.primaryCurrentDeploymentKey)] 224 + : primaryLedgerTarget.accounts?.[String(spec.primaryLedgerAccount)]; 164 225 const secondarySource = spec.secondaryCurrentDeploymentKey 165 - ? secondaryLedgerTarget[spec.secondaryCurrentDeploymentKey] 166 - : secondaryLedgerTarget.accounts?.[spec.secondaryLedgerAccount]; 226 + ? secondaryLedgerTarget[String(spec.secondaryCurrentDeploymentKey)] 227 + : secondaryLedgerTarget.accounts?.[String(spec.secondaryLedgerAccount)]; 167 228 168 229 if (!primarySource || !secondarySource) { 169 230 throw new Error( ··· 200 261 }; 201 262 }; 202 263 203 - const createPlan = (ledger) => { 204 - const targets = []; 205 - const skipped = []; 264 + const createPlan = (ledger: FlexibleRecord): Plan => { 265 + const targets: PlanTarget[] = []; 266 + const skipped: SkippedTarget[] = []; 206 267 207 268 for (const spec of PDSLAB_TARGETS) { 208 269 const needsLedgerTarget = Boolean(spec.ledgerTarget); 209 270 const ledgerTarget = needsLedgerTarget 210 - ? ensureTarget(ledger, spec.ledgerTarget) 271 + ? ensureTarget(ledger, String(spec.ledgerTarget)) 211 272 : null; 212 273 const primaryLedgerTarget = spec.primaryLedgerTarget 213 - ? ensureTarget(ledger, spec.primaryLedgerTarget) 274 + ? ensureTarget(ledger, String(spec.primaryLedgerTarget)) 214 275 : null; 215 276 const secondaryLedgerTarget = spec.secondaryLedgerTarget 216 - ? ensureTarget(ledger, spec.secondaryLedgerTarget) 277 + ? ensureTarget(ledger, String(spec.secondaryLedgerTarget)) 217 278 : null; 218 279 219 280 if ( ··· 229 290 continue; 230 291 } 231 292 232 - let config; 293 + let config: FlexibleRecord; 233 294 if ( 234 295 spec.mode === "dual" && 235 296 spec.primaryLedgerTarget && 236 297 spec.secondaryLedgerTarget 237 298 ) { 299 + if (!primaryLedgerTarget || !secondaryLedgerTarget) { 300 + throw new Error( 301 + `cross-PDS target ${String(spec.id)} is missing its ledger targets`, 302 + ); 303 + } 238 304 config = createCrossPdsDualConfig({ 239 305 spec, 240 306 primaryLedgerTarget, 241 307 secondaryLedgerTarget, 242 308 }); 243 309 } else if (spec.mode === "dual") { 310 + if (!ledgerTarget) { 311 + throw new Error( 312 + `dual target ${String(spec.id)} is missing its ledger target`, 313 + ); 314 + } 244 315 config = createDualConfig({ spec, ledgerTarget }); 245 316 } else { 317 + if (!ledgerTarget) { 318 + throw new Error( 319 + `single target ${String(spec.id)} is missing its ledger target`, 320 + ); 321 + } 246 322 config = createSingleConfig({ spec, ledgerTarget }); 247 323 } 248 324 ··· 262 338 }; 263 339 }; 264 340 265 - const writePlan = async ({ plan, outputDir }) => { 341 + const writePlan = async ({ 342 + plan, 343 + outputDir, 344 + }: { 345 + plan: Plan; 346 + outputDir: string; 347 + }): Promise<void> => { 266 348 await fs.mkdir(outputDir, { recursive: true }); 267 349 268 350 const inventory = { ··· 302 384 } 303 385 }; 304 386 305 - const main = async (argv = process.argv) => { 387 + const main = async (argv = process.argv): Promise<number> => { 306 388 const args = parseArgs(argv); 307 389 if (args.help) { 308 390 console.log(usage); ··· 335 417 }; 336 418 337 419 const exitCode = await main(process.argv).catch((error) => { 338 - console.error(String(error?.message ?? error)); 420 + console.error(errorMessage(error)); 339 421 return 1; 340 422 }); 341 423
+36 -6
src/adapters/adapter-builder.mjs src/adapters/adapter-builder.ts
··· 2 2 createAccountConfig, 3 3 createDualRunConfig, 4 4 createSingleRunConfig, 5 - } from "../config.mjs"; 5 + } from "../config.js"; 6 + import type { 7 + AccountConfig, 8 + Adapter, 9 + ExampleBaseConfig, 10 + FlexibleRecord, 11 + } from "../types.js"; 6 12 7 13 export const createRoleBasedAdapter = ({ 8 14 name, ··· 14 20 primaryCleanupPrefixes, 15 21 secondaryCleanupPrefixes, 16 22 dualSuiteDefaults = {}, 17 - }) => { 18 - const createAccount = ({ role = "primary", ...account } = {}) => { 23 + }: { 24 + name: string; 25 + description: string; 26 + accountStrategy: string; 27 + notes: string[]; 28 + exampleBase: ExampleBaseConfig; 29 + roleDefaults: (role: string) => FlexibleRecord; 30 + primaryCleanupPrefixes: readonly string[]; 31 + secondaryCleanupPrefixes: readonly string[]; 32 + dualSuiteDefaults?: FlexibleRecord; 33 + }): { createAccount: (raw?: FlexibleRecord) => AccountConfig; adapter: Adapter } => { 34 + const createAccount = ({ 35 + role = "primary", 36 + ...account 37 + }: FlexibleRecord = {}): AccountConfig => { 19 38 const cleanupPostPrefixes = 20 39 role === "secondary" ? secondaryCleanupPrefixes : primaryCleanupPrefixes; 21 40 ··· 26 45 }); 27 46 }; 28 47 29 - const createExampleConfig = ({ mode }) => { 48 + const createExampleConfig = ({ 49 + mode, 50 + }: { 51 + mode: "single" | "dual"; 52 + }): FlexibleRecord => { 30 53 const { primaryHandle, secondaryHandle, ...configBase } = exampleBase; 31 54 const base = { 32 55 ...configBase, ··· 57 80 }; 58 81 }; 59 82 60 - const createSingleConfig = ({ account, ...rest } = {}) => { 83 + const createSingleConfig = ({ 84 + account, 85 + ...rest 86 + }: FlexibleRecord = {}) => { 61 87 return createSingleRunConfig({ 62 88 ...rest, 63 89 adapter: name, ··· 68 94 }); 69 95 }; 70 96 71 - const createDualConfig = ({ primary, secondary, ...rest } = {}) => { 97 + const createDualConfig = ({ 98 + primary, 99 + secondary, 100 + ...rest 101 + }: FlexibleRecord = {}) => { 72 102 return createDualRunConfig({ 73 103 ...dualSuiteDefaults, 74 104 ...rest,
+17 -5
src/adapters/bring-your-own.mjs src/adapters/bring-your-own.ts
··· 2 2 createAccountConfig, 3 3 createDualRunConfig, 4 4 createSingleRunConfig, 5 - } from "../config.mjs"; 5 + } from "../config.js"; 6 + import type { Adapter, FlexibleRecord } from "../types.js"; 6 7 7 - const createBringYourOwnExampleConfig = ({ mode }) => { 8 + const createBringYourOwnExampleConfig = ({ 9 + mode, 10 + }: { 11 + mode: "single" | "dual"; 12 + }): FlexibleRecord => { 8 13 const base = { 9 14 pdsUrl: "https://your-pds.example", 10 15 artifactsDir: `data/browser-smoke/bring-your-own-${mode}`, ··· 36 41 }; 37 42 }; 38 43 39 - const createBringYourOwnSingleConfig = ({ account, ...rest } = {}) => { 44 + const createBringYourOwnSingleConfig = ({ 45 + account, 46 + ...rest 47 + }: FlexibleRecord = {}) => { 40 48 return createSingleRunConfig({ 41 49 ...rest, 42 50 account: createAccountConfig(account), 43 51 }); 44 52 }; 45 53 46 - const createBringYourOwnDualConfig = ({ primary, secondary, ...rest } = {}) => { 54 + const createBringYourOwnDualConfig = ({ 55 + primary, 56 + secondary, 57 + ...rest 58 + }: FlexibleRecord = {}) => { 47 59 return createDualRunConfig({ 48 60 ...rest, 49 61 primary: createAccountConfig(primary), ··· 51 63 }); 52 64 }; 53 65 54 - export const BRING_YOUR_OWN_ADAPTER = Object.freeze({ 66 + export const BRING_YOUR_OWN_ADAPTER: Adapter = Object.freeze({ 55 67 name: "bring-your-own", 56 68 description: "Use existing accounts on any PDS with minimal configuration.", 57 69 accountStrategy: "existing-accounts",
+1 -1
src/adapters/perlsky.mjs src/adapters/perlsky.ts
··· 1 - import { createRoleBasedAdapter } from "./adapter-builder.mjs"; 1 + import { createRoleBasedAdapter } from "./adapter-builder.js"; 2 2 3 3 export const PERLSKY_PRIMARY_CLEANUP_PREFIXES = Object.freeze([ 4 4 "perlsky browser smoke ",
+8 -7
src/adapters/registry.mjs src/adapters/registry.ts
··· 1 - import { BRING_YOUR_OWN_ADAPTER } from "./bring-your-own.mjs"; 2 - import { PERLSKY_ADAPTER } from "./perlsky.mjs"; 3 - import { TRANQUIL_PDS_ADAPTER } from "./tranquil-pds.mjs"; 1 + import { BRING_YOUR_OWN_ADAPTER } from "./bring-your-own.js"; 2 + import { PERLSKY_ADAPTER } from "./perlsky.js"; 3 + import { TRANQUIL_PDS_ADAPTER } from "./tranquil-pds.js"; 4 + import type { Adapter } from "../types.js"; 4 5 5 6 /** 6 7 * Adapter definitions normalize raw user config into smoke-suite config. ··· 13 14 * They do not provision accounts, create invites, or run lifecycle hooks. 14 15 * Those higher-level workflows belong in per-PDS tooling around the suite. 15 16 */ 16 - export const ADAPTERS = Object.freeze({ 17 + export const ADAPTERS: Readonly<Record<string, Adapter>> = Object.freeze({ 17 18 [BRING_YOUR_OWN_ADAPTER.name]: BRING_YOUR_OWN_ADAPTER, 18 19 [PERLSKY_ADAPTER.name]: PERLSKY_ADAPTER, 19 20 [TRANQUIL_PDS_ADAPTER.name]: TRANQUIL_PDS_ADAPTER, 20 21 }); 21 22 22 - export const ADAPTER_NAMES = Object.freeze(Object.keys(ADAPTERS)); 23 + export const ADAPTER_NAMES: readonly string[] = Object.freeze(Object.keys(ADAPTERS)); 23 24 24 - export const getAdapter = (name = "bring-your-own") => { 25 + export const getAdapter = (name = "bring-your-own"): Adapter => { 25 26 const adapter = ADAPTERS[name]; 26 27 if (!adapter) { 27 28 throw new Error(`unsupported adapter: ${name}`); ··· 29 30 return adapter; 30 31 }; 31 32 32 - export const listAdapters = () => { 33 + export const listAdapters = (): Adapter[] => { 33 34 return ADAPTER_NAMES.map((name) => ADAPTERS[name]); 34 35 };
+1 -1
src/adapters/tranquil-pds.mjs src/adapters/tranquil-pds.ts
··· 1 - import { createRoleBasedAdapter } from "./adapter-builder.mjs"; 1 + import { createRoleBasedAdapter } from "./adapter-builder.js"; 2 2 3 3 export const TRANQUIL_PDS_PRIMARY_CLEANUP_PREFIXES = Object.freeze([ 4 4 "tranquil browser smoke ",
+3 -3
src/browser/lib/dual-actions.mjs src/browser/lib/dual-actions.ts
··· 1 - import { createDualFeedActions } from "./dual-actions/feed.mjs"; 2 - import { createDualModerationActions } from "./dual-actions/moderation.mjs"; 3 - import { createDualProfileActions } from "./dual-actions/profile.mjs"; 1 + import { createDualFeedActions } from "./dual-actions/feed.js"; 2 + import { createDualModerationActions } from "./dual-actions/moderation.js"; 3 + import { createDualProfileActions } from "./dual-actions/profile.js"; 4 4 5 5 export const createDualActions = (options) => { 6 6 return {
+3 -3
src/browser/lib/dual-actions/feed.mjs src/browser/lib/dual-actions/feed.ts
··· 1 - import { createPageAuthActions } from "../page-auth-actions.mjs"; 2 - import { createPageFeedActions } from "../page-feed-actions.mjs"; 3 - import { dismissBlockingOverlays } from "../runtime-utils.mjs"; 1 + import { createPageAuthActions } from "../page-auth-actions.js"; 2 + import { createPageFeedActions } from "../page-feed-actions.js"; 3 + import { dismissBlockingOverlays } from "../runtime-utils.js"; 4 4 5 5 export const createDualFeedActions = ({ 6 6 config,
src/browser/lib/dual-actions/moderation.mjs src/browser/lib/dual-actions/moderation.ts
+18 -12
src/browser/lib/dual-actions/profile.mjs src/browser/lib/dual-actions/profile.ts
··· 4 4 loginToBlueskyApp, 5 5 normalizeText, 6 6 pollJsonUntil, 7 - } from "../runtime-utils.mjs"; 8 - import { createPageAuthActions } from "../page-auth-actions.mjs"; 9 - import { createPageFeedActions } from "../page-feed-actions.mjs"; 10 - import { createPageProfileEditActions } from "../page-profile-edit-actions.mjs"; 7 + } from "../runtime-utils.js"; 8 + import type { FlexibleRecord } from "../../../types.js"; 9 + import { createPageAuthActions } from "../page-auth-actions.js"; 10 + import { createPageFeedActions } from "../page-feed-actions.js"; 11 + import { createPageProfileEditActions } from "../page-profile-edit-actions.js"; 11 12 12 13 export const createDualProfileActions = ({ 13 14 appBaseUrl, ··· 302 303 name: `public profile edit indexing for ${account.handle}`, 303 304 buildUrl: () => 304 305 `${config.publicApiUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(account.handle)}`, 305 - predicate: ({ ok, json }) => 306 - ok && 307 - json?.description === account.profileNote && 308 - typeof json?.avatar === "string" && 309 - json.avatar.length > 0, 306 + predicate: ({ ok, json }) => { 307 + const publicProfile = json as FlexibleRecord; 308 + return ( 309 + ok && 310 + publicProfile?.description === account.profileNote && 311 + typeof publicProfile?.avatar === "string" && 312 + publicProfile.avatar.length > 0 313 + ); 314 + }, 310 315 timeoutMs: publicCheckTimeoutMs, 311 316 fetchJson, 312 317 }); 313 - const avatarResult = await fetchStatus(result.json.avatar); 318 + const publicProfile = result.json as FlexibleRecord; 319 + const avatarResult = await fetchStatus(String(publicProfile.avatar)); 314 320 if (!avatarResult.ok) { 315 321 throw new Error( 316 322 `public avatar URL returned ${avatarResult.status} for ${account.handle}`, 317 323 ); 318 324 } 319 325 return { 320 - avatar: result.json.avatar, 326 + avatar: publicProfile.avatar, 321 327 avatarStatus: avatarResult.status, 322 - description: result.json.description, 328 + description: publicProfile.description, 323 329 }; 324 330 }; 325 331
+118 -51
src/browser/lib/dual-api.mjs src/browser/lib/dual-api.ts
··· 2 2 fetchJsonWithTimeout, 3 3 fetchStatusWithTimeout, 4 4 sleep, 5 - } from "./runtime-utils.mjs"; 6 - import { derivePdsHost } from "../../config.mjs"; 5 + } from "./runtime-utils.js"; 6 + import { derivePdsHost } from "../../config.js"; 7 + import type { 8 + AccountConfig, 9 + FetchJsonResult, 10 + FetchStatusResult, 11 + FlexibleRecord, 12 + RepoRecord, 13 + XrpcJsonOptions, 14 + } from "../../types.js"; 7 15 8 - export const createDualApiHelpers = ({ config }) => { 9 - const fetchJson = (url, options = {}) => fetchJsonWithTimeout(url, options); 16 + export const createDualApiHelpers = ({ 17 + config, 18 + }: { 19 + config: { pdsUrl: string }; 20 + }) => { 21 + const fetchJson = ( 22 + url: string, 23 + options: FlexibleRecord = {}, 24 + ): Promise<FetchJsonResult> => fetchJsonWithTimeout(url, options); 10 25 11 - const fetchStatus = (url, options = {}) => 26 + const fetchStatus = ( 27 + url: string, 28 + options: FlexibleRecord = {}, 29 + ): Promise<FetchStatusResult> => 12 30 fetchStatusWithTimeout(url, options); 13 31 14 - const collectionFromUri = (uri) => { 32 + const collectionFromUri = (uri: string | undefined): string | undefined => { 15 33 // Example: at://did:plc:123/app.bsky.feed.post/3kabc -> app.bsky.feed.post 16 34 if (typeof uri !== "string") { 17 35 return undefined; ··· 20 38 return parts.length >= 4 ? parts[3] : undefined; 21 39 }; 22 40 23 - const normalizeRepoRecord = (record) => { 41 + const normalizeRepoRecord = (record: RepoRecord): RepoRecord => { 24 42 const innerValue = record?.value?.value; 25 43 const innerType = innerValue?.$type; 26 44 const expectedCollection = collectionFromUri(record?.uri); ··· 43 61 }; 44 62 45 63 const xrpcJson = async ( 46 - nsid, 47 - { method = "GET", token, params, body, timeoutMs, pdsUrl } = {}, 48 - ) => { 64 + nsid: string, 65 + options: XrpcJsonOptions = {}, 66 + ): Promise<FetchJsonResult> => { 67 + const { 68 + method = "GET", 69 + token, 70 + params, 71 + body, 72 + timeoutMs, 73 + pdsUrl, 74 + } = options; 75 + const typedParams = params as Record<string, string> | undefined; 49 76 const basePdsUrl = pdsUrl || config.pdsUrl; 50 77 const url = new URL(`${basePdsUrl}/xrpc/${nsid}`); 51 - if (params) { 52 - for (const [key, value] of Object.entries(params)) { 78 + if (typedParams) { 79 + for (const [key, value] of Object.entries(typedParams)) { 53 80 url.searchParams.set(key, value); 54 81 } 55 82 } 56 - const headers = { accept: "application/json" }; 83 + const headers: Record<string, string> = { accept: "application/json" }; 57 84 if (token) { 58 85 headers.authorization = `Bearer ${token}`; 59 86 } 60 87 if (body !== undefined) { 61 88 headers["content-type"] = "application/json"; 62 89 } 63 - const run = (extraHeaders = {}) => 90 + const run = (extraHeaders: Record<string, string> = {}) => 64 91 fetchJson(url.toString(), { 65 92 method, 66 93 headers: { ··· 81 108 return result; 82 109 }; 83 110 84 - const listOwnRecords = async (account, collection, limit = 100) => { 111 + const listOwnRecords = async ( 112 + account: AccountConfig, 113 + collection: string, 114 + limit = 100, 115 + ): Promise<RepoRecord[]> => { 116 + const repo = account.did ?? account.handle; 85 117 const result = await xrpcJson("com.atproto.repo.listRecords", { 86 118 token: account.accessJwt, 87 119 pdsUrl: account.pdsUrl, 88 120 params: { 89 - repo: account.did, 121 + repo, 90 122 collection, 91 123 limit: String(limit), 92 124 }, ··· 96 128 `listRecords failed for ${account.handle} collection ${collection}: ${result.status} ${result.text}`, 97 129 ); 98 130 } 99 - return (result.json?.records || []).map(normalizeRepoRecord); 131 + const records = ((result.json as FlexibleRecord)?.records || []) as RepoRecord[]; 132 + return records.map(normalizeRepoRecord); 100 133 }; 101 134 102 - const recordRkey = (recordOrUri) => { 135 + const recordRkey = (recordOrUri: RepoRecord | string): string | undefined => { 103 136 const uri = 104 137 typeof recordOrUri === "string" ? recordOrUri : recordOrUri?.uri; 105 138 return uri?.split("/").pop(); 106 139 }; 107 140 108 - const deleteOwnRecord = async (account, collection, record) => { 141 + const deleteOwnRecord = async ( 142 + account: AccountConfig, 143 + collection: string, 144 + record: RepoRecord | string, 145 + ): Promise<{ rkey: string }> => { 109 146 const rkey = recordRkey(record); 110 147 if (!rkey) { 111 148 throw new Error( ··· 131 168 }; 132 169 133 170 const purgeOwnRecords = async ( 134 - account, 135 - collection, 136 - predicate, 171 + account: AccountConfig, 172 + collection: string, 173 + predicate: (record: RepoRecord) => boolean, 137 174 limit = 100, 138 - ) => { 175 + ): Promise<number> => { 139 176 const records = await listOwnRecords(account, collection, limit); 140 177 const doomed = records.filter(predicate); 141 178 for (const record of doomed) { ··· 146 183 }; 147 184 148 185 const waitForOwnRecord = async ( 149 - account, 150 - collection, 151 - predicate, 186 + account: AccountConfig, 187 + collection: string, 188 + predicate: (record: RepoRecord) => boolean, 152 189 timeoutMs = 60000, 153 - ) => { 190 + ): Promise<RepoRecord> => { 154 191 const started = Date.now(); 155 192 while (Date.now() - started < timeoutMs) { 156 193 const records = await listOwnRecords(account, collection); ··· 165 202 ); 166 203 }; 167 204 168 - const waitForOwnPostRecord = (account, text, timeoutMs = 60000) => { 205 + const waitForOwnPostRecord = ( 206 + account: AccountConfig, 207 + text: string, 208 + timeoutMs = 60000, 209 + ): Promise<RepoRecord> => { 169 210 return waitForOwnRecord( 170 211 account, 171 212 "app.bsky.feed.post", ··· 174 215 ); 175 216 }; 176 217 177 - const waitForFollowRecord = (account, subjectDid, timeoutMs = 60000) => 218 + const waitForFollowRecord = ( 219 + account: AccountConfig, 220 + subjectDid: string, 221 + timeoutMs = 60000, 222 + ): Promise<RepoRecord> => 178 223 waitForOwnRecord( 179 224 account, 180 225 "app.bsky.graph.follow", ··· 183 228 ); 184 229 185 230 const waitForNoOwnRecord = async ( 186 - account, 187 - collection, 188 - predicate, 231 + account: AccountConfig, 232 + collection: string, 233 + predicate: (record: RepoRecord) => boolean, 189 234 timeoutMs = 60000, 190 - ) => { 235 + ): Promise<true> => { 191 236 const started = Date.now(); 192 237 while (Date.now() - started < timeoutMs) { 193 238 const records = await listOwnRecords(account, collection); ··· 201 246 ); 202 247 }; 203 248 204 - const waitForOwnListRecord = (account, name, timeoutMs = 60000) => 249 + const waitForOwnListRecord = ( 250 + account: AccountConfig, 251 + name: string, 252 + timeoutMs = 60000, 253 + ): Promise<RepoRecord> => 205 254 waitForOwnRecord( 206 255 account, 207 256 "app.bsky.graph.list", ··· 210 259 ); 211 260 212 261 const waitForOwnListItemRecord = ( 213 - account, 214 - listUri, 215 - subjectDid, 262 + account: AccountConfig, 263 + listUri: string, 264 + subjectDid: string, 216 265 timeoutMs = 60000, 217 - ) => 266 + ): Promise<RepoRecord> => 218 267 waitForOwnRecord( 219 268 account, 220 269 "app.bsky.graph.listitem", ··· 224 273 timeoutMs, 225 274 ); 226 275 227 - const createSession = async (account) => { 276 + const createSession = async ( 277 + account: AccountConfig, 278 + ): Promise<FlexibleRecord> => { 228 279 const identifier = account.loginIdentifier || account.handle; 229 280 const result = await xrpcJson("com.atproto.server.createSession", { 230 281 method: "POST", ··· 239 290 `createSession failed for ${identifier}: ${result.status} ${result.text}`, 240 291 ); 241 292 } 242 - return result.json; 293 + return (result.json ?? {}) as FlexibleRecord; 243 294 }; 244 295 245 296 const pollNotifications = async ({ ··· 248 299 reasons, 249 300 minIndexedAt, 250 301 timeoutMs = 180000, 251 - }) => { 302 + }: { 303 + account: AccountConfig; 304 + authorHandle: string; 305 + reasons: string[]; 306 + minIndexedAt: number; 307 + timeoutMs?: number; 308 + }): Promise<{ notifications: FlexibleRecord[]; allNotifications: FlexibleRecord[] }> => { 252 309 const started = Date.now(); 253 310 let last; 254 311 while (Date.now() - started < timeoutMs) { ··· 286 343 ); 287 344 }; 288 345 289 - const accountFromConfig = (entry) => ({ 346 + const accountFromConfig = (entry: AccountConfig): AccountConfig => ({ 290 347 ...entry, 291 - pdsUrl: entry.pdsUrl || config.pdsUrl, 292 - pdsHost: entry.pdsHost || derivePdsHost(entry.pdsUrl || config.pdsUrl), 293 - loginIdentifier: entry.loginIdentifier || entry.handle, 294 - mediaPostText: entry.mediaPostText || `${entry.postText} image`, 348 + pdsUrl: entry.pdsUrl ?? config.pdsUrl, 349 + pdsHost: entry.pdsHost ?? derivePdsHost(entry.pdsUrl ?? config.pdsUrl), 350 + loginIdentifier: entry.loginIdentifier ?? entry.handle, 351 + mediaPostText: entry.mediaPostText ?? `${entry.postText} image`, 295 352 shortHandle: entry.handle.replace(/^@/, ""), 296 353 }); 297 354 298 - const prepareAccounts = ({ primaryConfig, secondaryConfig, startedAt }) => { 355 + const prepareAccounts = ({ 356 + primaryConfig, 357 + secondaryConfig, 358 + startedAt, 359 + }: { 360 + primaryConfig: AccountConfig; 361 + secondaryConfig: AccountConfig; 362 + startedAt: string; 363 + }): { primary: AccountConfig; secondary: AccountConfig } => { 299 364 const runToken = startedAt.replace(/\D/g, "").slice(0, 14); 300 365 const primary = accountFromConfig({ 301 366 ...primaryConfig, ··· 312 377 return { primary, secondary }; 313 378 }; 314 379 315 - const stalePostPrefixesFor = (account) => { 380 + const stalePostPrefixesFor = (account: AccountConfig): string[] => { 316 381 if ( 317 382 Array.isArray(account.cleanupPostPrefixes) && 318 383 account.cleanupPostPrefixes.length 319 384 ) { 320 385 return account.cleanupPostPrefixes; 321 386 } 322 - return [account.postText].filter( 323 - (value) => typeof value === "string" && value.length > 0, 387 + return [account.postText].filter((value): value is string => 388 + typeof value === "string" && value.length > 0, 324 389 ); 325 390 }; 326 391 327 392 const staleListPrefixes = ["Smoke List ", "Updated Smoke List "]; 328 393 329 - const cleanupStaleSmokeArtifacts = async (account) => { 394 + const cleanupStaleSmokeArtifacts = async ( 395 + account: AccountConfig, 396 + ): Promise<{ deletedPosts: number; deletedListItems: number; deletedLists: number }> => { 330 397 const postPrefixes = stalePostPrefixesFor(account); 331 398 const deletedPosts = await purgeOwnRecords( 332 399 account,
+16 -8
src/browser/lib/dual-browser.mjs src/browser/lib/dual-browser.ts
··· 1 1 import path from "node:path"; 2 - import { chromium } from "./playwright-runtime.mjs"; 2 + import type { Page } from "playwright"; 3 + import { chromium } from "./playwright-runtime.js"; 3 4 import { 4 5 attachPageLogging, 5 6 buttonText, ··· 9 10 finalizeSummary, 10 11 launchBrowserWithFallback, 11 12 normalizeText, 12 - } from "./runtime-utils.mjs"; 13 + } from "./runtime-utils.js"; 13 14 import { 14 15 isIgnoredConsoleEntry, 15 16 isIgnoredHttpFailureEntry, 16 17 isIgnoredRequestFailureEntry, 17 - } from "./failure-rules.mjs"; 18 + } from "./failure-rules.js"; 19 + import type { Summary } from "../../types.js"; 18 20 19 21 export const setupDualBrowser = async ({ config, summary }) => { 20 22 const browser = await launchBrowserWithFallback({ ··· 58 60 summary, 59 61 primaryPage, 60 62 secondaryPage, 63 + }: { 64 + config: { artifactsDir: string; progress?: boolean; stepTimeoutMs?: number }; 65 + summary: Summary; 66 + primaryPage: Page; 67 + secondaryPage: Page; 61 68 }) => { 62 69 const stepTimeoutMs = Number(config.stepTimeoutMs || 120000); 63 70 const progressEnabled = config.progress !== false; 64 - const pageFor = (name) => (name === "primary" ? primaryPage : secondaryPage); 71 + const pageFor = (name: string): Page => 72 + name === "primary" ? primaryPage : secondaryPage; 65 73 66 74 const emitProgress = createProgressEmitter({ enabled: progressEnabled }); 67 75 68 - const screenshot = async (pageName, name) => { 76 + const screenshot = async (pageName: string, name: string): Promise<string> => { 69 77 const page = pageFor(pageName); 70 78 const file = path.join(config.artifactsDir, `${name}-${pageName}.png`); 71 79 await page.screenshot({ path: file, fullPage: true }); ··· 77 85 emitProgress, 78 86 defaultTimeoutMs: stepTimeoutMs, 79 87 captureArtifacts: async ({ name, pageNames, failed }) => { 80 - const screenshots = {}; 81 - for (const pageName of pageNames) { 88 + const screenshots: Record<string, string | undefined> = {}; 89 + for (const pageName of pageNames ?? []) { 82 90 screenshots[pageName] = await screenshot( 83 91 pageName, 84 92 failed ? `${name}-error` : name, ··· 88 96 }, 89 97 }); 90 98 91 - const wait = async (page, ms) => { 99 + const wait = async (page: Page, ms: number): Promise<void> => { 92 100 await page.waitForTimeout(ms); 93 101 }; 94 102
+1 -1
src/browser/lib/dual-scenario.mjs src/browser/lib/dual-scenario.ts
··· 3 3 runDualPrimaryWavePhase, 4 4 runDualSecondaryWaveAndSettingsPhase, 5 5 runDualSetupPhase, 6 - } from "./dual-scenario/phases.mjs"; 6 + } from "./dual-scenario/phases.js"; 7 7 8 8 export const runDualScenario = async (ctx) => { 9 9 await runDualSetupPhase(ctx);
src/browser/lib/dual-scenario/phases.mjs src/browser/lib/dual-scenario/phases.ts
+12
src/browser/lib/failure-rules.mjs src/browser/lib/failure-rules.ts
··· 17 17 url: /(?:video\.bsky\.app\/watch|video\.cdn\.bsky\.app\/hls)\/.*\/(?:(?:playlist|video)\.m3u8|.*\.ts|.*\.vtt)/i, 18 18 error: /ERR_ABORTED/i, 19 19 }, 20 + { 21 + url: /video\.bsky\.app\/watch\/.*\/thumbnail\.jpg/i, 22 + error: /ERR_ABORTED/i, 23 + }, 20 24 { url: /\/xrpc\/chat\.bsky\.convo\.getLog/i, error: /ERR_ABORTED/i }, 21 25 { 22 26 url: /\/xrpc\/app\.bsky\.graph\.(?:muteActor|unmuteActor)/i, ··· 29 33 { url: /\/xrpc\/app\.bsky\.feed\.getAuthorFeed/i, error: /ERR_ABORTED/i }, 30 34 { 31 35 url: /\/xrpc\/app\.bsky\.graph\.getSuggestedFollowsByActor/i, 36 + error: /ERR_ABORTED/i, 37 + }, 38 + { 39 + url: /\/xrpc\/app\.bsky\.feed\.getPosts/i, 40 + error: /ERR_ABORTED/i, 41 + }, 42 + { 43 + url: /\/xrpc\/app\.bsky\.unspecced\.getPostThreadV2/i, 32 44 error: /ERR_ABORTED/i, 33 45 }, 34 46 {
+3 -1
src/browser/lib/lists.mjs src/browser/lib/lists.ts
··· 56 56 const btn = document.querySelector( 57 57 '[data-testid="editProfileSaveBtn"]', 58 58 ); 59 + if (!(btn instanceof HTMLElement)) { 60 + return false; 61 + } 59 62 return ( 60 - Boolean(btn) && 61 63 !btn.hasAttribute("disabled") && 62 64 btn.getAttribute("aria-disabled") !== "true" 63 65 );
src/browser/lib/page-auth-actions.mjs src/browser/lib/page-auth-actions.ts
src/browser/lib/page-feed-actions.mjs src/browser/lib/page-feed-actions.ts
+3 -1
src/browser/lib/page-profile-edit-actions.mjs src/browser/lib/page-profile-edit-actions.ts
··· 97 97 const btn = document.querySelector( 98 98 '[data-testid="editProfileSaveBtn"]', 99 99 ); 100 + if (!(btn instanceof HTMLElement)) { 101 + return false; 102 + } 100 103 return ( 101 - Boolean(btn) && 102 104 !btn.hasAttribute("disabled") && 103 105 btn.getAttribute("aria-disabled") !== "true" 104 106 );
-20
src/browser/lib/playwright-runtime.mjs
··· 1 - let playwright; 2 - 3 - try { 4 - playwright = await import("playwright"); 5 - } catch (primaryError) { 6 - try { 7 - playwright = 8 - await import("../../../../tools/browser-automation/node_modules/playwright/index.mjs"); 9 - } catch { 10 - throw new Error( 11 - [ 12 - "Unable to load Playwright.", 13 - "Install dependencies with `npm install` and then install a browser with `npx playwright install chromium`.", 14 - `Original error: ${String(primaryError?.message ?? primaryError)}`, 15 - ].join(" "), 16 - ); 17 - } 18 - } 19 - 20 - export const { chromium } = playwright;
+23
src/browser/lib/playwright-runtime.ts
··· 1 + import { errorMessage } from "./runtime-utils.js"; 2 + 3 + let playwright: typeof import("playwright"); 4 + const fallbackPlaywrightPath = 5 + "../../../../tools/browser-automation/node_modules/playwright/index.js"; 6 + 7 + try { 8 + playwright = await import("playwright"); 9 + } catch (primaryError) { 10 + try { 11 + playwright = await import(fallbackPlaywrightPath); 12 + } catch { 13 + throw new Error( 14 + [ 15 + "Unable to load Playwright.", 16 + "Install dependencies with `bun install` and then install a browser with `bunx playwright install chromium`.", 17 + `Original error: ${errorMessage(primaryError)}`, 18 + ].join(" "), 19 + ); 20 + } 21 + } 22 + 23 + export const { chromium } = playwright;
+128 -39
src/browser/lib/runtime-utils.mjs src/browser/lib/runtime-utils.ts
··· 1 1 import fs from "node:fs/promises"; 2 + import type { Browser, Locator, Page } from "playwright"; 3 + import type { 4 + FetchJsonResult, 5 + FetchStatusResult, 6 + FlexibleRecord, 7 + Summary, 8 + } from "../../types.js"; 2 9 3 10 const SYSTEM_GOOGLE_CHROME = 4 11 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; 5 12 13 + export const errorMessage = (error: unknown): string => 14 + error instanceof Error ? error.message : String(error); 15 + 6 16 export const AVATAR_PNG_BASE64 = 7 17 "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAV0lEQVR4nO3PQQ0AIBDAMMC/58MCP7KkVbDX1pk5A6gWUC2gWkC1gGoB1QKqBVQLqBZQLaBaQLWAagHVAqoFVAuoFlAtoFpAtYBqAdUCqgVUC6gWUC2gWkD1B4a2AX/y3CvgAAAAAElFTkSuQmCC"; 8 18 9 - export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 19 + export const sleep = (ms: number): Promise<void> => 20 + new Promise((resolve) => setTimeout(resolve, ms)); 10 21 11 - export const normalizeText = (text) => (text || "").replace(/\s+/g, " ").trim(); 22 + export const normalizeText = (text: string | null | undefined): string => 23 + (text || "").replace(/\s+/g, " ").trim(); 12 24 13 - export const createBaseSummary = (fields = {}) => ({ 25 + export const createBaseSummary = (fields: FlexibleRecord = {}): Summary => ({ 14 26 startedAt: new Date().toISOString(), 15 27 steps: [], 16 28 console: [], ··· 22 34 ...fields, 23 35 }); 24 36 25 - export const recordStep = (summary, name, status, extra = {}) => { 37 + export const recordStep = ( 38 + summary: Summary, 39 + name: string, 40 + status: string, 41 + extra: FlexibleRecord = {}, 42 + ): void => { 26 43 summary.steps.push({ 27 44 name, 28 45 status, ··· 36 53 emitProgress, 37 54 captureArtifacts, 38 55 defaultTimeoutMs, 56 + }: { 57 + summary: Summary; 58 + emitProgress: (status: string, name: string, detail?: string) => void; 59 + captureArtifacts: (args: { 60 + name: string; 61 + pageNames?: string[]; 62 + failed: boolean; 63 + }) => Promise<FlexibleRecord>; 64 + defaultTimeoutMs?: number; 39 65 }) => { 40 66 return async ( 41 - name, 42 - fn, 43 - { optional = false, timeoutMs, pageNames = [] } = {}, 44 - ) => { 67 + name: string, 68 + fn: () => Promise<unknown>, 69 + { 70 + optional = false, 71 + timeoutMs, 72 + pageNames = [], 73 + }: { 74 + optional?: boolean; 75 + timeoutMs?: number; 76 + pageNames?: string[]; 77 + } = {}, 78 + ): Promise<unknown> => { 45 79 const effectiveTimeoutMs = Number(timeoutMs || defaultTimeoutMs || 0); 46 80 emitProgress("start", name); 47 - let timeoutId; 81 + let timeoutId: ReturnType<typeof setTimeout> | undefined; 48 82 try { 49 83 const result = 50 84 effectiveTimeoutMs > 0 ··· 64 98 pageNames, 65 99 failed: false, 66 100 }); 67 - recordStep(summary, name, "ok", { ...artifacts, ...(result ?? {}) }); 101 + recordStep(summary, name, "ok", { 102 + ...artifacts, 103 + ...((result as FlexibleRecord | null) ?? {}), 104 + }); 68 105 emitProgress("ok", name); 69 106 return result; 70 107 } catch (error) { ··· 75 112 }); 76 113 recordStep(summary, name, optional ? "skipped" : "failed", { 77 114 ...artifacts, 78 - error: String(error?.message ?? error), 115 + error: errorMessage(error), 79 116 }); 80 117 emitProgress( 81 118 optional ? "skip" : "fail", 82 119 name, 83 - String(error?.message ?? error), 120 + errorMessage(error), 84 121 ); 85 122 if (!optional) { 86 123 throw error; ··· 94 131 }; 95 132 }; 96 133 97 - export const buildBrowserLaunchCandidates = async (config) => { 134 + export const buildBrowserLaunchCandidates = async ( 135 + config: FlexibleRecord, 136 + ): Promise<{ label: string; options: FlexibleRecord }[]> => { 98 137 const base = { 99 138 headless: config.headless !== false, 100 139 chromiumSandbox: true, 101 140 }; 102 - const candidates = []; 141 + const candidates: { label: string; options: FlexibleRecord }[] = []; 103 142 if (config.browserExecutablePath) { 104 143 candidates.push({ 105 144 label: `executable:${config.browserExecutablePath}`, ··· 124 163 return candidates; 125 164 }; 126 165 127 - export const fetchJsonWithTimeout = async (url, options = {}) => { 128 - const timeoutMs = options.timeoutMs ?? 30000; 166 + export const fetchJsonWithTimeout = async ( 167 + url: string, 168 + options: FlexibleRecord = {}, 169 + ): Promise<FetchJsonResult> => { 170 + const timeoutMs = 171 + typeof options.timeoutMs === "number" ? options.timeoutMs : 30000; 129 172 const controller = new AbortController(); 130 173 const timer = setTimeout(() => controller.abort(), timeoutMs); 131 - const fetchOptions = { 132 - ...options, 174 + const fetchOptions: RequestInit & { timeoutMs?: number } = { 175 + ...(options as RequestInit), 133 176 signal: controller.signal, 134 177 }; 135 178 delete fetchOptions.timeoutMs; 136 - let res; 179 + let res: Response; 137 180 try { 138 181 res = await fetch(url, fetchOptions); 139 182 } finally { 140 183 clearTimeout(timer); 141 184 } 142 185 const text = await res.text(); 143 - let json; 186 + let json: FetchJsonResult["json"]; 144 187 try { 145 - json = text ? JSON.parse(text) : null; 188 + json = text ? (JSON.parse(text) as FetchJsonResult["json"]) : null; 146 189 } catch { 147 190 json = null; 148 191 } 149 192 return { ok: res.ok, status: res.status, text, json }; 150 193 }; 151 194 152 - export const fetchStatusWithTimeout = async (url, options = {}) => { 153 - const timeoutMs = options.timeoutMs ?? 30000; 195 + export const fetchStatusWithTimeout = async ( 196 + url: string, 197 + options: FlexibleRecord = {}, 198 + ): Promise<FetchStatusResult> => { 199 + const timeoutMs = 200 + typeof options.timeoutMs === "number" ? options.timeoutMs : 30000; 154 201 const controller = new AbortController(); 155 202 const timer = setTimeout(() => controller.abort(), timeoutMs); 156 203 try { 204 + const redirect = 205 + typeof options.redirect === "string" ? options.redirect : "follow"; 157 206 const res = await fetch(url, { 158 - ...options, 159 - redirect: options.redirect || "follow", 207 + ...(options as RequestInit), 208 + redirect: redirect as RequestRedirect, 160 209 signal: controller.signal, 161 210 }); 162 211 return { ok: res.ok, status: res.status, url: res.url }; ··· 165 214 } 166 215 }; 167 216 168 - export const buttonText = async (locator) => { 217 + export const buttonText = async (locator: Locator): Promise<string> => { 169 218 const label = await locator.getAttribute("aria-label"); 170 219 if (label && label.trim()) { 171 220 return label.trim(); ··· 174 223 return text.trim(); 175 224 }; 176 225 177 - export const dismissBlockingOverlays = async (page) => { 226 + export const dismissBlockingOverlays = async (page: Page): Promise<void> => { 178 227 const backdrop = page.locator('[aria-label*="click to close"]').last(); 179 228 if (await backdrop.count()) { 180 229 await backdrop ··· 203 252 password, 204 253 notes, 205 254 noteTarget, 206 - }) => { 255 + }: { 256 + page: Page; 257 + appUrl: string; 258 + pdsHost: string; 259 + loginIdentifier: string; 260 + password: string; 261 + notes?: string[]; 262 + noteTarget?: string; 263 + }): Promise<{ loginPath: string }> => { 207 264 let loginPath = "legacy-service-picker"; 208 265 const activeScope = () => page.locator('[role="dialog"]').last(); 209 266 210 - const clickNamedControl = async (name) => { 267 + const clickNamedControl = async (name: string): Promise<void> => { 211 268 const scope = activeScope(); 212 269 const asButton = scope.getByRole("button", { name }).first(); 213 270 if (await asButton.count()) { ··· 292 349 timeoutMs, 293 350 fetchJson, 294 351 intervalMs = 5000, 295 - }) => { 352 + }: { 353 + name: string; 354 + buildUrl: () => string; 355 + predicate: (result: FetchJsonResult) => boolean; 356 + timeoutMs: number; 357 + fetchJson: (url: string, options?: FlexibleRecord) => Promise<FetchJsonResult>; 358 + intervalMs?: number; 359 + }): Promise<FetchJsonResult> => { 296 360 const started = Date.now(); 297 - let last; 361 + let last: FetchJsonResult | undefined; 298 362 while (Date.now() - started < timeoutMs) { 299 363 last = await fetchJson(buildUrl(), { 300 364 timeoutMs: Math.min(timeoutMs, 30000), ··· 313 377 chromium, 314 378 config, 315 379 summary, 316 - }) => { 317 - const errors = []; 380 + }: { 381 + chromium: { launch: (options: FlexibleRecord) => Promise<Browser> }; 382 + config: FlexibleRecord; 383 + summary: Summary; 384 + }): Promise<Browser> => { 385 + const errors: string[] = []; 318 386 for (const candidate of await buildBrowserLaunchCandidates(config)) { 319 387 try { 320 388 const browser = await chromium.launch(candidate.options); ··· 323 391 ); 324 392 return browser; 325 393 } catch (error) { 326 - errors.push(`${candidate.label}: ${String(error?.message ?? error)}`); 394 + errors.push(`${candidate.label}: ${errorMessage(error)}`); 327 395 } 328 396 } 329 397 throw new Error( ··· 336 404 page, 337 405 pageName, 338 406 xrpcLimit = 200, 339 - }) => { 407 + }: { 408 + summary: Summary; 409 + page: Page; 410 + pageName?: string; 411 + xrpcLimit?: number; 412 + }): void => { 340 413 const maybePage = pageName ? { page: pageName } : {}; 341 414 342 415 page.on("console", (msg) => { ··· 388 461 }); 389 462 }; 390 463 391 - export const createProgressEmitter = ({ enabled, write = console.error }) => { 392 - return (status, name, detail = "") => { 464 + export const createProgressEmitter = ({ 465 + enabled, 466 + write = console.error, 467 + }: { 468 + enabled: boolean; 469 + write?: (message: string) => void; 470 + }) => { 471 + return (status: string, name: string, detail = ""): void => { 393 472 if (!enabled) { 394 473 return; 395 474 } ··· 405 484 isIgnoredConsole, 406 485 isIgnoredRequestFailure, 407 486 isIgnoredHttpFailure, 408 - }) => { 487 + }: { 488 + summary: Summary; 489 + strictErrors: boolean; 490 + isIgnoredConsole: (entry: FlexibleRecord) => boolean; 491 + isIgnoredRequestFailure: (entry: FlexibleRecord) => boolean; 492 + isIgnoredHttpFailure: (entry: FlexibleRecord) => boolean; 493 + }): Summary => { 409 494 summary.finishedAt = new Date().toISOString(); 410 495 summary.unexpected = { 411 496 console: summary.console.filter((entry) => !isIgnoredConsole(entry)), ··· 437 522 browser, 438 523 summary, 439 524 timeoutMs = 15000, 440 - }) => { 525 + }: { 526 + browser: Browser; 527 + summary: Summary; 528 + timeoutMs?: number; 529 + }): Promise<void> => { 441 530 await Promise.race([ 442 531 browser.close(), 443 532 new Promise((_, reject) => {
src/browser/lib/settings.mjs src/browser/lib/settings.ts
+3 -3
src/browser/lib/single-actions.mjs src/browser/lib/single-actions.ts
··· 1 - import { createSingleAuthActions } from "./single-actions/auth.mjs"; 2 - import { createSingleFeedActions } from "./single-actions/feed.mjs"; 3 - import { createSingleProfileActions } from "./single-actions/profile.mjs"; 1 + import { createSingleAuthActions } from "./single-actions/auth.js"; 2 + import { createSingleFeedActions } from "./single-actions/feed.js"; 3 + import { createSingleProfileActions } from "./single-actions/profile.js"; 4 4 5 5 export const createSingleActions = (options) => { 6 6 return {
+2 -2
src/browser/lib/single-actions/auth.mjs src/browser/lib/single-actions/auth.ts
··· 1 - import { loginToBlueskyApp } from "../runtime-utils.mjs"; 2 - import { createPageAuthActions } from "../page-auth-actions.mjs"; 1 + import { loginToBlueskyApp } from "../runtime-utils.js"; 2 + import { createPageAuthActions } from "../page-auth-actions.js"; 3 3 4 4 export const createSingleAuthActions = ({ 5 5 config,
+2 -2
src/browser/lib/single-actions/feed.mjs src/browser/lib/single-actions/feed.ts
··· 1 - import { dismissBlockingOverlays } from "../runtime-utils.mjs"; 2 - import { createPageFeedActions } from "../page-feed-actions.mjs"; 1 + import { dismissBlockingOverlays } from "../runtime-utils.js"; 2 + import { createPageFeedActions } from "../page-feed-actions.js"; 3 3 4 4 export const createSingleFeedActions = ({ 5 5 page,
+6 -3
src/browser/lib/single-actions/profile.mjs src/browser/lib/single-actions/profile.ts
··· 1 - import { dismissBlockingOverlays } from "../runtime-utils.mjs"; 2 - import { createPageProfileEditActions } from "../page-profile-edit-actions.mjs"; 1 + import { dismissBlockingOverlays } from "../runtime-utils.js"; 2 + import { createPageProfileEditActions } from "../page-profile-edit-actions.js"; 3 3 4 4 export const createSingleProfileActions = ({ 5 5 config, ··· 121 121 }; 122 122 123 123 const editProfile = () => 124 - pageActions.editProfile(page, { profileNote: config.profileNote }); 124 + pageActions.editProfile(page, { 125 + profileNote: config.profileNote, 126 + handle: config.handle, 127 + }); 125 128 126 129 return { 127 130 verifyPublicHandleResolution,
+1 -1
src/browser/lib/single-scenario.mjs src/browser/lib/single-scenario.ts
··· 3 3 runSingleCleanupPhase, 4 4 runSingleProfilePhase, 5 5 runSingleTargetInteractionPhase, 6 - } from "./single-scenario/phases.mjs"; 6 + } from "./single-scenario/phases.js"; 7 7 8 8 export const runSingleScenario = async (ctx) => { 9 9 await runSingleBootstrapPhase(ctx);
+28 -8
src/browser/lib/single-scenario/phases.mjs src/browser/lib/single-scenario/phases.ts
··· 9 9 verifyPublicProfile, 10 10 verifyPublicAuthorFeed, 11 11 gotoProfile, 12 + waitForProfileHandle, 12 13 page, 13 14 findRowByPrimaryText, 14 15 ensureLiked, ··· 30 31 await step("own-profile", () => gotoProfile(config.handle)); 31 32 32 33 const ownPost = await step("find-own-post", async () => { 33 - await gotoProfile(config.handle); 34 - await page 35 - .getByTestId("postsFeed") 36 - .first() 37 - .waitFor({ state: "visible", timeout: 60000 }); 38 - const row = await findRowByPrimaryText(config.postText, 60000); 39 - const rowTestId = await row.getAttribute("data-testid"); 40 - return { note: "found own post", rowFound: true, rowTestId }; 34 + const started = Date.now(); 35 + let lastError; 36 + while (Date.now() - started < 60000) { 37 + try { 38 + await gotoProfile(config.handle); 39 + await waitForProfileHandle(config.handle, 15000); 40 + await page 41 + .getByTestId("postsFeed") 42 + .first() 43 + .waitFor({ state: "visible", timeout: 15000 }); 44 + const row = await findRowByPrimaryText(config.postText, 15000); 45 + const rowTestId = await row.getAttribute("data-testid"); 46 + return { note: "found own post", rowFound: true, rowTestId }; 47 + } catch (error) { 48 + lastError = error; 49 + } 50 + 51 + await page 52 + .reload({ waitUntil: "domcontentloaded", timeout: 60000 }) 53 + .catch(() => undefined); 54 + await page.waitForTimeout(3000); 55 + } 56 + 57 + throw ( 58 + lastError ?? 59 + new Error(`feed item with primary text not found: ${config.postText}`) 60 + ); 41 61 }); 42 62 43 63 if (!ownPost) {
+22 -15
src/browser/run-dual.mjs src/browser/run-dual.ts
··· 5 5 setupDualBrowser, 6 6 createDualStepHelpers, 7 7 finalizeDualSummary, 8 - } from "./lib/dual-browser.mjs"; 9 - import { createDualApiHelpers } from "./lib/dual-api.mjs"; 10 - import { createListHelpers } from "./lib/lists.mjs"; 11 - import { createSettingsHelpers } from "./lib/settings.mjs"; 12 - import { runDualScenario } from "./lib/dual-scenario.mjs"; 13 - import { createDualActions } from "./lib/dual-actions.mjs"; 8 + } from "./lib/dual-browser.js"; 9 + import { createDualApiHelpers } from "./lib/dual-api.js"; 10 + import { createListHelpers } from "./lib/lists.js"; 11 + import { createSettingsHelpers } from "./lib/settings.js"; 12 + import { runDualScenario } from "./lib/dual-scenario.js"; 13 + import { createDualActions } from "./lib/dual-actions.js"; 14 14 import { 15 15 AVATAR_PNG_BASE64, 16 16 createBaseSummary, 17 + errorMessage, 17 18 sleep, 18 - } from "./lib/runtime-utils.mjs"; 19 + } from "./lib/runtime-utils.js"; 20 + import type { DualRunConfig, FlexibleRecord, Summary } from "../types.js"; 21 + import { createDualRunConfig } from "../config.js"; 19 22 20 - export const runDualFromConfig = async (config) => { 23 + export const runDualFromConfig = async (config: DualRunConfig): Promise<Summary> => { 21 24 await fs.mkdir(config.artifactsDir, { recursive: true }); 22 25 const appBaseUrl = config.appUrl.replace(/\/$/, ""); 23 26 24 - const summary = createBaseSummary({ 27 + const summary: Summary = createBaseSummary({ 25 28 appUrl: config.appUrl, 26 29 pdsUrl: config.pdsUrl, 27 30 primaryPdsUrl: config.primary?.pdsUrl || config.pdsUrl, ··· 127 130 ...actions, 128 131 }); 129 132 } catch (error) { 130 - summary.fatal = String(error?.message ?? error); 133 + summary.fatal = errorMessage(error); 131 134 } 132 135 133 136 await finalizeDualSummary({ ··· 148 151 return summary; 149 152 }; 150 153 151 - export const runDualFromConfigPath = async (configPath) => { 152 - const config = JSON.parse(await fs.readFile(configPath, "utf8")); 154 + export const runDualFromConfigPath = async ( 155 + configPath: string, 156 + ): Promise<Summary> => { 157 + const config = createDualRunConfig( 158 + JSON.parse(await fs.readFile(configPath, "utf8")) as FlexibleRecord, 159 + ); 153 160 return runDualFromConfig(config); 154 161 }; 155 162 156 - export const runDualFromArgv = async (argv = process.argv) => { 163 + export const runDualFromArgv = async (argv = process.argv): Promise<number> => { 157 164 const configPath = argv[2]; 158 165 if (!configPath) { 159 - console.error("usage: node run-dual.mjs <config.json>"); 166 + console.error("usage: node dist/src/browser/run-dual.js <config.json>"); 160 167 return 2; 161 168 } 162 169 const summary = await runDualFromConfigPath(configPath); ··· 169 176 170 177 if (isDirectExecution) { 171 178 const exitCode = await runDualFromArgv(process.argv); 172 - process.exitCode = exitCode; 179 + process.exit(exitCode); 173 180 }
+48 -20
src/browser/run-single.mjs src/browser/run-single.ts
··· 1 1 import fs from "node:fs/promises"; 2 2 import path from "node:path"; 3 3 import { fileURLToPath } from "node:url"; 4 - import { chromium } from "./lib/playwright-runtime.mjs"; 4 + import { chromium } from "./lib/playwright-runtime.js"; 5 5 import { 6 6 AVATAR_PNG_BASE64, 7 7 attachPageLogging, ··· 17 17 normalizeText, 18 18 pollJsonUntil, 19 19 sleep, 20 - } from "./lib/runtime-utils.mjs"; 20 + errorMessage, 21 + } from "./lib/runtime-utils.js"; 21 22 import { 22 23 isIgnoredConsoleEntry, 23 24 isIgnoredHttpFailureEntry, 24 25 isIgnoredRequestFailureEntry, 25 - } from "./lib/failure-rules.mjs"; 26 - import { runSingleScenario } from "./lib/single-scenario.mjs"; 27 - import { createSingleActions } from "./lib/single-actions.mjs"; 26 + } from "./lib/failure-rules.js"; 27 + import { runSingleScenario } from "./lib/single-scenario.js"; 28 + import { createSingleActions } from "./lib/single-actions.js"; 29 + import type { 30 + FetchJsonResult, 31 + FetchStatusResult, 32 + FlexibleRecord, 33 + SingleRunConfig, 34 + Summary, 35 + } from "../types.js"; 36 + import { createSingleRunConfig } from "../config.js"; 28 37 29 - export const runSingleFromConfig = async (config) => { 38 + export const runSingleFromConfig = async ( 39 + config: SingleRunConfig, 40 + ): Promise<Summary> => { 30 41 await fs.mkdir(config.artifactsDir, { recursive: true }); 31 42 const appBaseUrl = config.appUrl.replace(/\/$/, ""); 32 43 33 - const summary = createBaseSummary({ 44 + const summary: Summary = createBaseSummary({ 34 45 appUrl: config.appUrl, 35 46 pdsUrl: config.pdsUrl, 36 47 publicApiUrl: config.publicApiUrl, ··· 60 71 61 72 attachPageLogging({ summary, page, xrpcLimit: 200 }); 62 73 63 - const screenshot = async (name) => { 74 + const screenshot = async (name: string): Promise<string> => { 64 75 const file = path.join(config.artifactsDir, `${name}.png`); 65 76 await page.screenshot({ path: file, fullPage: true }); 66 77 return file; ··· 76 87 }), 77 88 }); 78 89 79 - const wait = (ms) => page.waitForTimeout(ms); 80 - const fetchJson = (url, options = {}) => 90 + const wait = (ms: number): Promise<void> => page.waitForTimeout(ms); 91 + const fetchJson = ( 92 + url: string, 93 + options: FlexibleRecord = {}, 94 + ): Promise<FetchJsonResult> => 81 95 fetchJsonWithTimeout(url, { 82 96 headers: { accept: "application/json" }, 83 - timeoutMs: options.timeoutMs ?? 30000, 97 + timeoutMs: 98 + typeof options.timeoutMs === "number" ? options.timeoutMs : 30000, 84 99 }); 85 100 86 - const fetchStatus = (url, options = {}) => 101 + const fetchStatus = ( 102 + url: string, 103 + options: FlexibleRecord = {}, 104 + ): Promise<FetchStatusResult> => 87 105 fetchStatusWithTimeout(url, { 88 - timeoutMs: options.timeoutMs ?? 30000, 106 + timeoutMs: 107 + typeof options.timeoutMs === "number" ? options.timeoutMs : 30000, 89 108 }); 90 109 91 - const pollJson = (name, buildUrl, predicate, timeoutMs) => 110 + const pollJson = ( 111 + name: string, 112 + buildUrl: () => string, 113 + predicate: (result: FetchJsonResult) => boolean, 114 + timeoutMs: number, 115 + ): Promise<FetchJsonResult> => 92 116 pollJsonUntil({ 93 117 name, 94 118 buildUrl, ··· 119 143 ...actions, 120 144 }); 121 145 } catch (error) { 122 - summary.fatal = String(error?.message ?? error); 146 + summary.fatal = errorMessage(error); 123 147 } 124 148 125 149 finalizeSummary({ ··· 140 164 return summary; 141 165 }; 142 166 143 - export const runSingleFromConfigPath = async (configPath) => { 144 - const config = JSON.parse(await fs.readFile(configPath, "utf8")); 167 + export const runSingleFromConfigPath = async ( 168 + configPath: string, 169 + ): Promise<Summary> => { 170 + const config = createSingleRunConfig( 171 + JSON.parse(await fs.readFile(configPath, "utf8")) as FlexibleRecord, 172 + ); 145 173 return runSingleFromConfig(config); 146 174 }; 147 175 148 - export const runSingleFromArgv = async (argv = process.argv) => { 176 + export const runSingleFromArgv = async (argv = process.argv): Promise<number> => { 149 177 const configPath = argv[2]; 150 178 if (!configPath) { 151 - console.error("usage: node run-single.mjs <config.json>"); 179 + console.error("usage: node dist/src/browser/run-single.js <config.json>"); 152 180 return 2; 153 181 } 154 182 const summary = await runSingleFromConfigPath(configPath); ··· 161 189 162 190 if (isDirectExecution) { 163 191 const exitCode = await runSingleFromArgv(process.argv); 164 - process.exitCode = exitCode; 192 + process.exit(exitCode); 165 193 }
+34 -14
src/cli.mjs src/cli.ts
··· 3 3 ADAPTER_NAMES, 4 4 getAdapter, 5 5 listAdapters, 6 - } from "./adapters/registry.mjs"; 6 + } from "./adapters/registry.js"; 7 + import type { 8 + DualRunConfig, 9 + FlexibleRecord, 10 + ParsedCliArgs, 11 + SingleRunConfig, 12 + } from "./types.js"; 7 13 8 14 const adapterUsage = ADAPTER_NAMES.join("|"); 9 15 ··· 23 29 - direct API/AppView contract layers are documented as a later v2 expansion 24 30 `; 25 31 26 - const parseArgs = (argv) => { 27 - const result = { 32 + const parseArgs = (argv: string[]): ParsedCliArgs => { 33 + const result: ParsedCliArgs = { 28 34 command: argv[2], 29 35 adapter: "bring-your-own", 30 36 }; ··· 36 42 continue; 37 43 } 38 44 if (arg === "--mode") { 39 - result.mode = argv[++i]; 45 + result.mode = argv[++i] as "single" | "dual"; 40 46 continue; 41 47 } 42 48 if (arg === "--adapter") { ··· 61 67 return result; 62 68 }; 63 69 64 - const normalizeMode = (command, mode) => { 70 + const normalizeMode = ( 71 + command: string | undefined, 72 + mode: ParsedCliArgs["mode"], 73 + ): ParsedCliArgs["mode"] => { 65 74 if (command === "run-single") { 66 75 return "single"; 67 76 } ··· 71 80 return mode; 72 81 }; 73 82 74 - const normalizeConfig = ({ mode, adapter, raw }) => { 83 + const normalizeConfig = ({ 84 + mode, 85 + adapter, 86 + raw, 87 + }: { 88 + mode: "single" | "dual"; 89 + adapter: string; 90 + raw: FlexibleRecord; 91 + }) => { 75 92 const selectedAdapter = getAdapter(adapter); 76 93 if (mode === "single") { 77 94 return selectedAdapter.createSingleConfig(raw); ··· 82 99 throw new Error(`unsupported mode: ${mode}`); 83 100 }; 84 101 85 - const loadJsonConfig = async (configPath) => { 102 + const loadJsonConfig = async (configPath: string): Promise<FlexibleRecord> => { 86 103 const text = await fs.readFile(configPath, "utf8"); 87 104 return JSON.parse(text); 88 105 }; 89 106 90 - const writeJsonConfig = async (outputPath, payload) => { 107 + const writeJsonConfig = async ( 108 + outputPath: string, 109 + payload: FlexibleRecord, 110 + ): Promise<void> => { 91 111 await fs.writeFile( 92 112 outputPath, 93 113 `${JSON.stringify(payload, null, 2)}\n`, ··· 95 115 ); 96 116 }; 97 117 98 - const adapterHelp = () => { 118 + const adapterHelp = (): string => { 99 119 return listAdapters() 100 120 .map((adapter) => { 101 121 const lines = [ ··· 110 130 .join("\n"); 111 131 }; 112 132 113 - export const runCliFromArgv = async (argv = process.argv) => { 133 + export const runCliFromArgv = async (argv = process.argv): Promise<number> => { 114 134 const args = parseArgs(argv); 115 135 116 136 if ( ··· 170 190 } 171 191 172 192 if (args.command === "run-single") { 173 - const { runSingleFromConfig } = await import("./browser/run-single.mjs"); 174 - const summary = await runSingleFromConfig(config); 193 + const { runSingleFromConfig } = await import("./browser/run-single.js"); 194 + const summary = await runSingleFromConfig(config as SingleRunConfig); 175 195 return summary.ok ? 0 : 1; 176 196 } 177 197 178 198 if (args.command === "run-dual") { 179 - const { runDualFromConfig } = await import("./browser/run-dual.mjs"); 180 - const summary = await runDualFromConfig(config); 199 + const { runDualFromConfig } = await import("./browser/run-dual.js"); 200 + const summary = await runDualFromConfig(config as DualRunConfig); 181 201 return summary.ok ? 0 : 1; 182 202 } 183 203
+32 -23
src/config.mjs src/config.ts
··· 1 + import type { 2 + AccountConfig, 3 + DualRunConfig, 4 + FlexibleRecord, 5 + SingleRunConfig, 6 + SuiteConfig, 7 + } from "./types.js"; 8 + 1 9 const DEFAULTS = { 2 10 appUrl: "https://bsky.app", 3 11 publicApiUrl: "https://public.api.bsky.app", ··· 9 17 publicChecks: true, 10 18 }; 11 19 12 - const requireString = (value, label) => { 20 + const requireString = (value: unknown, label: string): string => { 13 21 if (typeof value !== "string" || value.trim() === "") { 14 22 throw new Error(`${label} is required`); 15 23 } 16 24 return value; 17 25 }; 18 26 19 - const optionalString = (value) => { 27 + const optionalString = (value: unknown): string | undefined => { 20 28 if (value === undefined || value === null) { 21 29 return undefined; 22 30 } ··· 27 35 return trimmed === "" ? undefined : trimmed; 28 36 }; 29 37 30 - const optionalPostUrl = (value, label) => { 38 + const optionalPostUrl = (value: unknown, label: string): string | undefined => { 31 39 const maybe = optionalString(value); 32 40 if (!maybe) { 33 41 return undefined; ··· 47 55 return url.toString(); 48 56 }; 49 57 50 - const normalizeCleanupPrefixes = (prefixes) => { 58 + const normalizeCleanupPrefixes = (prefixes: unknown): string[] => { 51 59 if (prefixes === undefined) { 52 60 return []; 53 61 } ··· 61 69 } 62 70 return value.length ? value : undefined; 63 71 }) 64 - .filter(Boolean); 72 + .filter((value): value is string => value !== undefined); 65 73 }; 66 74 67 - export const derivePdsHost = (pdsUrl) => { 75 + export const derivePdsHost = (pdsUrl: string): string | undefined => { 68 76 try { 69 77 return new URL(pdsUrl).host; 70 78 } catch { 71 - const match = String(pdsUrl).match(/^https?:\/\/([^/]+)/); 79 + const match = /^https?:\/\/([^/]+)/.exec(pdsUrl); 72 80 return match?.[1]; 73 81 } 74 82 }; ··· 85 93 profileNote, 86 94 cleanupPostPrefixes, 87 95 ...rest 88 - } = {}) => { 89 - const normalized = { 96 + }: FlexibleRecord = {}): AccountConfig => { 97 + const normalized: AccountConfig = { 90 98 handle: requireString(handle, "account.handle"), 91 99 password: requireString(password, "account.password"), 92 - birthdate: optionalString(birthdate) || DEFAULTS.birthdate, 100 + birthdate: optionalString(birthdate) ?? DEFAULTS.birthdate, 93 101 cleanupPostPrefixes: normalizeCleanupPrefixes(cleanupPostPrefixes), 94 102 ...rest, 95 103 }; ··· 140 148 browserExecutablePath, 141 149 adapter, 142 150 ...rest 143 - } = {}) => { 151 + }: FlexibleRecord = {}): SuiteConfig => { 144 152 const normalized = { 145 153 pdsUrl: requireString(pdsUrl, "pdsUrl"), 146 154 artifactsDir: requireString(artifactsDir, "artifactsDir"), 147 - appUrl: optionalString(appUrl) || DEFAULTS.appUrl, 148 - publicApiUrl: optionalString(publicApiUrl) || DEFAULTS.publicApiUrl, 155 + appUrl: optionalString(appUrl) ?? DEFAULTS.appUrl, 156 + publicApiUrl: optionalString(publicApiUrl) ?? DEFAULTS.publicApiUrl, 149 157 publicCheckTimeoutMs: Number( 150 - publicCheckTimeoutMs || DEFAULTS.publicCheckTimeoutMs, 158 + publicCheckTimeoutMs ?? DEFAULTS.publicCheckTimeoutMs, 151 159 ), 152 - stepTimeoutMs: Number(stepTimeoutMs || DEFAULTS.stepTimeoutMs), 160 + stepTimeoutMs: Number(stepTimeoutMs ?? DEFAULTS.stepTimeoutMs), 153 161 headless: Boolean(headless), 154 162 strictErrors: Boolean(strictErrors), 155 163 publicChecks: Boolean(publicChecks), 156 164 ...rest, 157 - }; 165 + } as SuiteConfig; 158 166 159 - normalized.pdsHost = 160 - optionalString(pdsHost) || derivePdsHost(normalized.pdsUrl); 161 - if (!normalized.pdsHost) { 167 + const derivedPdsHost = 168 + optionalString(pdsHost) ?? derivePdsHost(normalized.pdsUrl); 169 + if (!derivedPdsHost) { 162 170 throw new Error("pdsHost could not be derived from pdsUrl"); 163 171 } 172 + normalized.pdsHost = derivedPdsHost; 164 173 165 174 const maybeTarget = optionalString(targetHandle); 166 175 if (maybeTarget) { ··· 192 201 account, 193 202 editProfile = false, 194 203 ...rest 195 - } = {}) => { 204 + }: FlexibleRecord = {}): SingleRunConfig => { 196 205 const suite = createSuiteConfig(rest); 197 206 if (!suite.targetHandle) { 198 207 throw new Error("targetHandle is required for single-mode runs"); ··· 201 210 ...suite, 202 211 ...createAccountConfig(account), 203 212 editProfile: Boolean(editProfile), 204 - }; 213 + } as SingleRunConfig; 205 214 }; 206 215 207 216 export const createDualRunConfig = ({ ··· 209 218 secondary, 210 219 accountSource, 211 220 ...rest 212 - } = {}) => { 213 - const normalized = { 221 + }: FlexibleRecord = {}): DualRunConfig => { 222 + const normalized: DualRunConfig = { 214 223 ...createSuiteConfig(rest), 215 224 primary: createAccountConfig(primary), 216 225 secondary: createAccountConfig(secondary),
-9
src/index.mjs
··· 1 - export * from "./config.mjs"; 2 - export * from "./adapters/bring-your-own.mjs"; 3 - export * from "./adapters/perlsky.mjs"; 4 - export * from "./adapters/tranquil-pds.mjs"; 5 - export * from "./adapters/registry.mjs"; 6 - export * from "./lab/pdslab-targets.mjs"; 7 - export * from "./browser/run-single.mjs"; 8 - export * from "./browser/run-dual.mjs"; 9 - export * from "./cli.mjs";
+9
src/index.ts
··· 1 + export * from "./config.js"; 2 + export * from "./adapters/bring-your-own.js"; 3 + export * from "./adapters/perlsky.js"; 4 + export * from "./adapters/tranquil-pds.js"; 5 + export * from "./adapters/registry.js"; 6 + export * from "./lab/pdslab-targets.js"; 7 + export * from "./browser/run-single.js"; 8 + export * from "./browser/run-dual.js"; 9 + export * from "./cli.js";
src/lab/pdslab-targets.mjs src/lab/pdslab-targets.ts
+148
src/types.ts
··· 1 + /* eslint-disable @typescript-eslint/no-explicit-any */ 2 + export interface FlexibleRecord { 3 + [key: string]: any; 4 + } 5 + 6 + export interface RepoRecord extends FlexibleRecord { 7 + uri?: string; 8 + value?: FlexibleRecord; 9 + } 10 + 11 + export interface AccountConfig extends FlexibleRecord { 12 + handle: string; 13 + password: string; 14 + birthdate: string; 15 + cleanupPostPrefixes: string[]; 16 + loginIdentifier?: string; 17 + postText?: string; 18 + mediaPostText?: string; 19 + quoteText?: string; 20 + replyText?: string; 21 + profileNote?: string; 22 + did?: string; 23 + email?: string; 24 + pdsUrl?: string; 25 + pdsHost?: string; 26 + accessJwt?: string; 27 + shortHandle?: string; 28 + listName?: string; 29 + listDescription?: string; 30 + listUpdatedName?: string; 31 + listUpdatedDescription?: string; 32 + listRkey?: string; 33 + rootPost?: RepoRecord; 34 + imagePost?: RepoRecord; 35 + quotePost?: RepoRecord; 36 + replyPost?: RepoRecord; 37 + listRecord?: RepoRecord; 38 + baselineCounts?: FlexibleRecord; 39 + } 40 + 41 + export interface SuiteConfig extends FlexibleRecord { 42 + pdsUrl: string; 43 + pdsHost: string; 44 + artifactsDir: string; 45 + appUrl: string; 46 + publicApiUrl: string; 47 + publicCheckTimeoutMs: number; 48 + stepTimeoutMs: number; 49 + headless: boolean; 50 + strictErrors: boolean; 51 + publicChecks: boolean; 52 + targetHandle?: string; 53 + remoteReplyPostUrl?: string; 54 + browserExecutablePath?: string; 55 + adapter?: string; 56 + progress?: boolean; 57 + } 58 + 59 + export type SingleRunConfig = SuiteConfig & 60 + Omit<AccountConfig, "pdsUrl" | "pdsHost"> & { 61 + targetHandle: string; 62 + editProfile: boolean; 63 + }; 64 + 65 + export interface DualRunConfig extends SuiteConfig { 66 + primary: AccountConfig; 67 + secondary: AccountConfig; 68 + accountSource?: string; 69 + } 70 + 71 + export interface ExampleBaseConfig extends FlexibleRecord { 72 + pdsUrl: string; 73 + targetHandle: string; 74 + strictErrors: boolean; 75 + primaryHandle: string; 76 + secondaryHandle: string; 77 + } 78 + 79 + export interface Adapter extends FlexibleRecord { 80 + name: string; 81 + description: string; 82 + accountStrategy: string; 83 + notes: string[]; 84 + createSingleConfig: (raw?: FlexibleRecord) => SingleRunConfig; 85 + createDualConfig: (raw?: FlexibleRecord) => DualRunConfig; 86 + createExampleConfig: (raw: { mode: "single" | "dual" }) => FlexibleRecord; 87 + } 88 + 89 + export interface ParsedCliArgs { 90 + command?: string; 91 + adapter: string; 92 + configPath?: string; 93 + mode?: "single" | "dual"; 94 + outputPath?: string; 95 + help?: boolean; 96 + jsonOnly?: boolean; 97 + } 98 + 99 + export interface SummaryStep extends FlexibleRecord { 100 + name: string; 101 + status: string; 102 + at: string; 103 + } 104 + 105 + export interface SummaryUnexpected extends FlexibleRecord { 106 + console: FlexibleRecord[]; 107 + requestFailures: FlexibleRecord[]; 108 + httpFailures: FlexibleRecord[]; 109 + pageErrors: FlexibleRecord[]; 110 + total?: number; 111 + } 112 + 113 + export interface Summary extends FlexibleRecord { 114 + startedAt: string; 115 + finishedAt?: string; 116 + steps: SummaryStep[]; 117 + console: FlexibleRecord[]; 118 + pageErrors: FlexibleRecord[]; 119 + requestFailures: FlexibleRecord[]; 120 + httpFailures: FlexibleRecord[]; 121 + xrpc: FlexibleRecord[]; 122 + notes: string[]; 123 + unexpected?: SummaryUnexpected; 124 + fatal?: string; 125 + ok?: boolean; 126 + } 127 + 128 + export interface FetchJsonResult extends FlexibleRecord { 129 + ok: boolean; 130 + status: number; 131 + text: string; 132 + json: FlexibleRecord | FlexibleRecord[] | null; 133 + } 134 + 135 + export interface FetchStatusResult extends FlexibleRecord { 136 + ok: boolean; 137 + status: number; 138 + url: string; 139 + } 140 + 141 + export interface XrpcJsonOptions extends FlexibleRecord { 142 + method?: string; 143 + token?: string; 144 + params?: Record<string, string>; 145 + body?: FlexibleRecord; 146 + timeoutMs?: number; 147 + pdsUrl?: string; 148 + }
+13 -8
tsconfig.json
··· 3 3 "target": "ES2022", 4 4 "module": "NodeNext", 5 5 "moduleResolution": "NodeNext", 6 - "allowJs": true, 7 - "checkJs": false, 6 + "lib": ["ES2022", "DOM"], 7 + "rootDir": ".", 8 + "outDir": "dist", 9 + "allowJs": false, 8 10 "skipLibCheck": true, 9 - "noEmit": true, 10 11 "resolveJsonModule": true, 11 - "types": ["node"] 12 + "types": ["node"], 13 + "strict": false, 14 + "strictNullChecks": true, 15 + "declaration": false, 16 + "sourceMap": true 12 17 }, 13 18 "include": [ 14 - "bin/**/*.mjs", 15 - "scripts/**/*.mjs", 16 - "src/**/*.mjs", 19 + "bin/**/*.ts", 20 + "scripts/**/*.ts", 21 + "src/**/*.ts", 17 22 "eslint.config.js", 18 23 "prettier.config.mjs" 19 24 ], 20 - "exclude": ["node_modules", "data", ".tmp"] 25 + "exclude": ["dist", "node_modules", "data", ".tmp"] 21 26 }