From b609f16ea3a312ba1b3c6770265c263ae8161f40 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 01:29:55 -0500 Subject: [PATCH 1/7] feat!: Upgrade bun and refactor routing --- .npmrc | 1 + Dockerfile | 2 +- bun.lock | 226 ++++++++++++++++++ bun.lockb | Bin 31431 -> 0 bytes package.json | 25 +- scripts/dev.ts | 19 ++ scripts/gen.ts | 8 - scripts/generate-gql-types.ts | 36 ++- scripts/generate-types.ts | 3 + src/@types/ctx.d.ts | 8 - src/@types/modules.d.ts | 6 +- src/apis/index.ts | 1 - src/assets/playground.html.tmpl | 97 ++++++++ src/{ => assets}/schema.gql | 0 src/crawlers/index.ts | 1 - src/dependencies.ts | 8 + src/dev.ts | 8 - src/generate-types.ts | 6 - src/{graphql.ts => graphql/index.ts} | 16 +- .../index.ts => graphql/resolvers.ts} | 0 src/{index.ts => main.ts} | 11 +- src/plugins/context-plugin.ts | 6 + src/plugins/cors-plugin.ts | 12 + src/public/playground.html | 57 ----- src/rest/getChromeScreenshot.ts | 15 -- src/rest/getFirefoxScreenshot.ts | 15 -- src/routes/grpahql-routes.ts | 30 +++ src/routes/rest-routes.ts | 48 ++++ src/server.ts | 151 +++--------- src/services/chrome-service.ts | 36 --- src/services/firefox-service.ts | 32 --- src/utils/cache.ts | 2 +- .../chrome}/__tests__/chrome-crawler.test.ts | 1 - .../chrome}/chrome-crawler.ts | 10 +- src/utils/chrome/chrome-service.ts | 63 +++++ src/{apis => utils/firefox}/firefox-api.ts | 10 +- src/utils/firefox/firefox-service.ts | 57 +++++ src/utils/rest-router.ts | 42 ---- tsconfig.json | 28 ++- 39 files changed, 680 insertions(+), 417 deletions(-) create mode 100644 .npmrc create mode 100644 bun.lock delete mode 100755 bun.lockb create mode 100644 scripts/dev.ts delete mode 100644 scripts/gen.ts create mode 100644 scripts/generate-types.ts delete mode 100644 src/@types/ctx.d.ts delete mode 100644 src/apis/index.ts create mode 100644 src/assets/playground.html.tmpl rename src/{ => assets}/schema.gql (100%) delete mode 100644 src/crawlers/index.ts create mode 100644 src/dependencies.ts delete mode 100644 src/dev.ts delete mode 100644 src/generate-types.ts rename src/{graphql.ts => graphql/index.ts} (74%) rename src/{resolvers/index.ts => graphql/resolvers.ts} (100%) rename src/{index.ts => main.ts} (57%) create mode 100644 src/plugins/context-plugin.ts create mode 100644 src/plugins/cors-plugin.ts delete mode 100644 src/public/playground.html delete mode 100644 src/rest/getChromeScreenshot.ts delete mode 100644 src/rest/getFirefoxScreenshot.ts create mode 100644 src/routes/grpahql-routes.ts create mode 100644 src/routes/rest-routes.ts delete mode 100644 src/services/chrome-service.ts delete mode 100644 src/services/firefox-service.ts rename src/{crawlers => utils/chrome}/__tests__/chrome-crawler.test.ts (97%) rename src/{crawlers => utils/chrome}/chrome-crawler.ts (93%) create mode 100644 src/utils/chrome/chrome-service.ts rename src/{apis => utils/firefox}/firefox-api.ts (89%) create mode 100644 src/utils/firefox/firefox-service.ts delete mode 100644 src/utils/rest-router.ts diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..41583e3 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/Dockerfile b/Dockerfile index a9569a0..6ae4f5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,4 @@ COPY package.json package.json COPY bun.lockb bun.lockb RUN bun install --production --ignore-scripts COPY . . -ENTRYPOINT ["bun", "src/index.ts"] +ENTRYPOINT ["bun", "src/main.ts"] diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..249e6d7 --- /dev/null +++ b/bun.lock @@ -0,0 +1,226 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": ".", + "dependencies": { + "@aklinker1/zero-ioc": "^1.3.2", + "consola": "^3.2.3", + "dataloader": "^2.2.2", + "graphql": "^16.8.0", + "linkedom": "^0.15.3", + "picocolors": "^1.0.0", + "zod": "^3.25.75", + }, + "devDependencies": { + "@aklinker1/check": "^2.1.0", + "@types/bun": "latest", + "code-block-writer": "^12.0.0", + "lint-staged": "^15.2.2", + "oxlint": "^1.6.0", + "prettier": "^3.2.5", + "simple-git-hooks": "^2.9.0", + "typescript": "^5.8.3", + }, + }, + }, + "packages": { + "@aklinker1/check": ["@aklinker1/check@2.1.0", "", { "dependencies": { "@antfu/utils": "^0.7.7", "ci-info": "^4.0.0", "citty": "^0.1.6" }, "bin": { "check": "bin/check.mjs" } }, "sha512-x+0vxb0vHV+6ZskcLYobI6FCAFG1jwVGgKuH9pTfHLW3vlzbGSGUKcxbCuMI3Q51WZS8oE4UfB+4v5M2+0uz1g=="], + + "@aklinker1/zero-ioc": ["@aklinker1/zero-ioc@1.3.2", "", {}, "sha512-J9nwXUprKdoKAZmM9b9ZvR5Z4/iVm29tDzgFhBtLdAIvxb/j3NZoVTX+wi5sZaGKsLP3rcgJD0QRUfvNd9MCIQ=="], + + "@antfu/utils": ["@antfu/utils@0.7.7", "", {}, "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg=="], + + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.6.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-m3wyqBh1TOHjpr/dXeIZY7OoX+MQazb+bMHQdDtwUvefrafUx+5YHRvulYh1sZSQ449nQ3nk3qj5qj535vZRjg=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.6.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-75fJfF/9xNypr7cnOYoZBhfmG1yP7ex3pUOeYGakmtZRffO9z1i1quLYhjZsmaDXsAIZ3drMhenYHMmFKS3SRg=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.6.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YhXGf0FXa72bEt4F7eTVKx5X3zWpbAOPnaA/dZ6/g8tGhw1m9IFjrabVHFjzcx3dQny4MgA59EhyElkDvpUe8A=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.6.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-T3JDhx8mjGjvh5INsPZJrlKHmZsecgDYvtvussKRdkc1Nnn7WC+jH9sh5qlmYvwzvmetlPVNezAoNvmGO9vtMg=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.6.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dx7ghtAl8aXBdqofJpi338At6lkeCtTfoinTYQXd9/TEJx+f+zCGNlQO6nJz3ydJBX48FDuOFKkNC+lUlWrd8w=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.6.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7KvMGdWmAZtAtg6IjoEJHKxTXdAcrHnUnqfgs0JpXst7trquV2mxBeRZusQXwxpu4HCSomKMvJfsp1qKaqSFDg=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.6.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-iSGC9RwX+dl7o5KFr5aH7Gq3nFbkq/3Gda6mxNPMvNkWrgXdIyiINxpyD8hJu566M+QSv1wEAu934BZotFDyoQ=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.6.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jOj3L/gfLc0IwgOTkZMiZ5c673i/hbAmidlaylT0gE6H18hln9HxPgp5GCf4E4y6mwEJlW8QC5hQi221+9otdA=="], + + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + + "@types/node": ["@types/node@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="], + + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], + + "ansi-escapes": ["ansi-escapes@6.2.0", "", { "dependencies": { "type-fest": "^3.0.0" } }, "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw=="], + + "ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="], + + "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "braces": ["braces@3.0.2", "", { "dependencies": { "fill-range": "^7.0.1" } }, "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A=="], + + "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + + "chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + + "ci-info": ["ci-info@4.0.0", "", {}, "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg=="], + + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + + "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + + "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], + + "code-block-writer": ["code-block-writer@12.0.0", "", {}, "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + + "consola": ["consola@3.2.3", "", {}, "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ=="], + + "cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="], + + "css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="], + + "css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="], + + "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "dataloader": ["dataloader@2.2.2", "", {}, "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g=="], + + "debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.1.0", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA=="], + + "emoji-regex": ["emoji-regex@10.3.0", "", {}, "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "fill-range": ["fill-range@7.0.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ=="], + + "get-east-asian-width": ["get-east-asian-width@1.2.0", "", {}, "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA=="], + + "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "graphql": ["graphql@16.8.0", "", {}, "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg=="], + + "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], + + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], + + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "lilconfig": ["lilconfig@3.0.0", "", {}, "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g=="], + + "linkedom": ["linkedom@0.15.3", "", { "dependencies": { "css-select": "^5.1.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^8.0.1", "uhyphen": "^0.2.0" } }, "sha512-p+lBSEWzawF3Gy7+nw+5+u+iDthsfZZVd9lwiO96Ihj7Zd8he5BD1Wzdc9Z4GqtU6lKvxhye4W4Zr20uOAGe4A=="], + + "lint-staged": ["lint-staged@15.2.2", "", { "dependencies": { "chalk": "5.3.0", "commander": "11.1.0", "debug": "4.3.4", "execa": "8.0.1", "lilconfig": "3.0.0", "listr2": "8.0.1", "micromatch": "4.0.5", "pidtree": "0.6.0", "string-argv": "0.3.2", "yaml": "2.3.4" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw=="], + + "listr2": ["listr2@8.0.1", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.0.0", "rfdc": "^1.3.0", "wrap-ansi": "^9.0.0" } }, "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA=="], + + "log-update": ["log-update@6.0.0", "", { "dependencies": { "ansi-escapes": "^6.2.0", "cli-cursor": "^4.0.0", "slice-ansi": "^7.0.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "micromatch": ["micromatch@4.0.5", "", { "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" } }, "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA=="], + + "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "npm-run-path": ["npm-run-path@5.2.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "oxlint": ["oxlint@1.6.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.6.0", "@oxlint/darwin-x64": "1.6.0", "@oxlint/linux-arm64-gnu": "1.6.0", "@oxlint/linux-arm64-musl": "1.6.0", "@oxlint/linux-x64-gnu": "1.6.0", "@oxlint/linux-x64-musl": "1.6.0", "@oxlint/win32-arm64": "1.6.0", "@oxlint/win32-x64": "1.6.0" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-jtaD65PqzIa1udvSxxscTKBxYKuZoFXyKGLiU1Qjo1ulq3uv/fQDtoV1yey1FrQZrQjACGPi1Widsy1TucC7Jg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.0.0", "", {}, "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + + "prettier": ["prettier@3.2.5", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A=="], + + "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + + "rfdc": ["rfdc@1.3.1", "", {}, "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-git-hooks": ["simple-git-hooks@2.9.0", "", { "bin": { "simple-git-hooks": "cli.js" } }, "sha512-waSQ5paUQtyGC0ZxlHmcMmD9I1rRXauikBwX31bX58l5vTOhCEcBC5Bi+ZDkPXTjDnZAF8TbCqKBY+9+sVPScw=="], + + "slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "string-width": ["string-width@7.1.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw=="], + + "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "uhyphen": ["uhyphen@0.2.0", "", {}, "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="], + + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], + + "yaml": ["yaml@2.3.4", "", {}, "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA=="], + + "zod": ["zod@3.25.75", "", {}, "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg=="], + + "log-update/slice-ansi": ["slice-ansi@7.1.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "log-update/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.0.0", "", { "dependencies": { "get-east-asian-width": "^1.0.0" } }, "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA=="], + + "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + } +} diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index c3fdcd0f1644cbedd0b3247643a4dfdfe4ae7974..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31431 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 diff --git a/package.json b/package.json index 85cf895..c42d45f 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "wxt-queue", + "name": "@wxt-dev/queue", "version": "0.3.14", - "module": "src/index.ts", "type": "module", - "packageManager": "bun@1.1.31", + "packageManager": "bun@1.2.18", "scripts": { - "dev": "bun --hot run src/dev.ts", - "generate:types": "bun run src/generate-types.ts", + "dev": "bun run --watch scripts/dev.ts", + "gen": "bun run gen:types", + "gen:types": "bun run scripts/generate-types.ts", "docker:build": "docker build . -t aklinker1/store-api", "docker:run": "docker run -it aklinker1/store-api", "docker:build:amd": "bun docker:build --platform linux/amd64", @@ -15,31 +15,28 @@ "check": "check" }, "dependencies": { + "@aklinker1/zero-ioc": "^1.3.2", "consola": "^3.2.3", "dataloader": "^2.2.2", "graphql": "^16.8.0", "linkedom": "^0.15.3", "picocolors": "^1.0.0", - "radix3": "^1.1.2" + "zod": "^3.25.75" }, "devDependencies": { - "@aklinker1/check": "^1.2.0", - "bun-types": "latest", + "@aklinker1/check": "^2.1.0", + "@types/bun": "latest", "code-block-writer": "^12.0.0", "lint-staged": "^15.2.2", + "oxlint": "^1.6.0", "prettier": "^3.2.5", "simple-git-hooks": "^2.9.0", - "typescript": "^5.3.3" + "typescript": "^5.8.3" }, "simple-git-hooks": { "pre-commit": "bun lint-staged" }, "lint-staged": { "*": "prettier --ignore-unknown --write" - }, - "changelog": { - "excludeAuthors": [ - "aaronklinker1@gmail.com" - ] } } diff --git a/scripts/dev.ts b/scripts/dev.ts new file mode 100644 index 0000000..732e3fa --- /dev/null +++ b/scripts/dev.ts @@ -0,0 +1,19 @@ +#!/usr/bin/env bun +import consola, { LogLevels } from "consola"; +import app from "../src/server"; +import { generateGqlTypes } from "./generate-gql-types"; +import pc from "picocolors"; +import { version } from "../package.json"; + +const fetch = app.build(); +await generateGqlTypes(fetch); + +consola.level = LogLevels.debug; +const port = Number(process.env.PORT ?? "3000"); +Bun.serve({ port, fetch }); + +consola.success( + `${pc.cyan("@wxt-dev/queue v" + version)} ${pc.dim("server started")}`, +); +consola.log(` ${pc.bold(pc.green("➜"))} http://localhost:${port}`); +console.log(); diff --git a/scripts/gen.ts b/scripts/gen.ts deleted file mode 100644 index e300f7c..0000000 --- a/scripts/gen.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createServer } from "../src/server"; -import { generateGqlTypes } from "./generate-gql-types"; - -const server = createServer(); - -await generateGqlTypes(server); - -server.httpServer.stop(); diff --git a/scripts/generate-gql-types.ts b/scripts/generate-gql-types.ts index 706b4bd..cc9c786 100644 --- a/scripts/generate-gql-types.ts +++ b/scripts/generate-gql-types.ts @@ -1,6 +1,7 @@ import CodeBlockWriter from "code-block-writer"; -import type { Server } from "../src/server"; import { consola } from "consola"; +import type { ServerSideFetch } from "@aklinker1/zeta/types"; +import app from "../src/server"; const typesFile = Bun.file("src/@types/gql.d.ts"); @@ -11,12 +12,17 @@ const scalarNameToTs = { Float: "number", }; -export async function generateGqlTypes(server: Server) { +export async function generateGqlTypes(fetch: ServerSideFetch = app.build()) { consola.info("Generating GraphQL types..."); - const introspection = await server.introspect(); + const introspection = await introspect(fetch); - const { queryType, mutationType, subscriptionType, types, directives } = - introspection.data.__schema; + const { + queryType, + mutationType, + subscriptionType, + types, + directives: _, + } = introspection.data.__schema; let argTypes: any[] = []; @@ -63,7 +69,7 @@ export async function generateGqlTypes(server: Server) { function capitalizeFirstLetter(str: string): string { if (str.length === 0) return str; - return str[0].toUpperCase() + str.substring(1); + return str[0]!.toUpperCase() + str.substring(1); } function getTsTypeString(gqlType: any): string { @@ -120,3 +126,21 @@ function writeScalarType(code: CodeBlockWriter, type: any) { } code.writeLine(`type ${type.name} = ${typeStr || "unknown"};`); } + +async function introspect(fetch: ServerSideFetch): Promise { + const request = new Request("http://localhost/api", { + body: JSON.stringify({ + operationName: "IntrospectionQuery", + query: + "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } ", + }), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }); + const res = await fetch(request); + if (res.ok) return await res.json(); + + throw Error("Introspection request failed: " + (await res.text())); +} diff --git a/scripts/generate-types.ts b/scripts/generate-types.ts new file mode 100644 index 0000000..d51e8f4 --- /dev/null +++ b/scripts/generate-types.ts @@ -0,0 +1,3 @@ +import { generateGqlTypes } from "./generate-gql-types"; + +await generateGqlTypes(); diff --git a/src/@types/ctx.d.ts b/src/@types/ctx.d.ts deleted file mode 100644 index 33da857..0000000 --- a/src/@types/ctx.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface WxtQueueCtx { - chrome: ReturnType< - typeof import("../services/chrome-service").createChromeService - >; - firefox: ReturnType< - typeof import("../services/firefox-service").createFirefoxService - >; -} diff --git a/src/@types/modules.d.ts b/src/@types/modules.d.ts index 12ffe46..418cd0d 100644 --- a/src/@types/modules.d.ts +++ b/src/@types/modules.d.ts @@ -3,7 +3,7 @@ declare module "*.gql" { export default text; } -declare module "*.html" { - const text: string; - export default text; +declare module "*.tmpl" { + const content: string; + export default content; } diff --git a/src/apis/index.ts b/src/apis/index.ts deleted file mode 100644 index 9aa64ca..0000000 --- a/src/apis/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./firefox-api"; diff --git a/src/assets/playground.html.tmpl b/src/assets/playground.html.tmpl new file mode 100644 index 0000000..e7c4bd2 --- /dev/null +++ b/src/assets/playground.html.tmpl @@ -0,0 +1,97 @@ + + + + + + Playground - WXT Queue v{{VERSION}} + + + + + + + + +
+
Loading…
+
+ + diff --git a/src/schema.gql b/src/assets/schema.gql similarity index 100% rename from src/schema.gql rename to src/assets/schema.gql diff --git a/src/crawlers/index.ts b/src/crawlers/index.ts deleted file mode 100644 index 9b5cdb7..0000000 --- a/src/crawlers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as chrome from "./chrome-crawler"; diff --git a/src/dependencies.ts b/src/dependencies.ts new file mode 100644 index 0000000..7806987 --- /dev/null +++ b/src/dependencies.ts @@ -0,0 +1,8 @@ +import { createIocContainer } from "@aklinker1/zero-ioc"; +import { createChromeService } from "./utils/chrome/chrome-service"; +import { createFirefoxService } from "./utils/firefox/firefox-service"; + +export const dependencies = createIocContainer().register({ + chrome: createChromeService, + firefox: createFirefoxService, +}); diff --git a/src/dev.ts b/src/dev.ts deleted file mode 100644 index a9c2fdb..0000000 --- a/src/dev.ts +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bun -import consola, { LogLevels } from "consola"; -import { createServer } from "./server"; -import { generateGqlTypes } from "../scripts/generate-gql-types"; - -consola.level = LogLevels.debug; -const server = createServer(); -await generateGqlTypes(server); diff --git a/src/generate-types.ts b/src/generate-types.ts deleted file mode 100644 index 0274502..0000000 --- a/src/generate-types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { generateGqlTypes } from "../scripts/generate-gql-types"; -import { createServer } from "./server"; - -const server = createServer(); -await generateGqlTypes(server); -server.httpServer.stop(); diff --git a/src/graphql.ts b/src/graphql/index.ts similarity index 74% rename from src/graphql.ts rename to src/graphql/index.ts index 5796947..578d6b4 100644 --- a/src/graphql.ts +++ b/src/graphql/index.ts @@ -1,22 +1,18 @@ import { buildSchema, graphql } from "graphql"; -import gqlSchema from "./schema.gql"; +import gqlSchema from "../assets/schema.gql"; import { rootResolver } from "./resolvers"; import { consola } from "consola"; import pc from "picocolors"; +import { dependencies } from "../dependencies"; -export function createGraphql(ctx: WxtQueueCtx) { +export function createGraphql() { const schema = buildSchema(gqlSchema); let increment = 0; - const evaluateQuery = async (req: Request) => { - const method = req.method.toUpperCase(); + const evaluateQuery = async (method: string, body: GraphQLParams) => { const id = ++increment; - const { - operationName = "Unknown", - query, - variables, - } = await req.json(); + const { operationName = "Unknown", query, variables } = body; const start = performance.now(); consola.debug( @@ -28,7 +24,7 @@ export function createGraphql(ctx: WxtQueueCtx) { const response = await graphql({ schema, source: query, - contextValue: ctx, + contextValue: dependencies.resolveAll(), variableValues: variables, rootValue: rootResolver, }); diff --git a/src/resolvers/index.ts b/src/graphql/resolvers.ts similarity index 100% rename from src/resolvers/index.ts rename to src/graphql/resolvers.ts diff --git a/src/index.ts b/src/main.ts similarity index 57% rename from src/index.ts rename to src/main.ts index 849c360..bc501f6 100644 --- a/src/index.ts +++ b/src/main.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun import consola from "consola"; -import { createServer } from "./server"; +import app from "./server"; +import pc from "picocolors"; +import { version } from "../package.json"; if (process.env.LOG_LEVEL) { // silent: Number.NEGATIVE_INFINITY @@ -20,4 +22,9 @@ if (process.env.LOG_LEVEL) { consola.level = Number(process.env.LOG_LEVEL); } -createServer(); +const port = Number(process.env.PORT ?? "3000"); +app.listen(port, () => { + consola.info( + `${pc.cyan("@wxt-dev/queue v" + version)} ${pc.dim("server started")}`, + ); +}); diff --git a/src/plugins/context-plugin.ts b/src/plugins/context-plugin.ts new file mode 100644 index 0000000..a1d85b9 --- /dev/null +++ b/src/plugins/context-plugin.ts @@ -0,0 +1,6 @@ +import { createApp } from "@aklinker1/zeta"; +import { dependencies } from "../dependencies"; + +export const contextPlugin = createApp() + .decorate(dependencies.resolveAll()) + .export(); diff --git a/src/plugins/cors-plugin.ts b/src/plugins/cors-plugin.ts new file mode 100644 index 0000000..a491180 --- /dev/null +++ b/src/plugins/cors-plugin.ts @@ -0,0 +1,12 @@ +import { createApp } from "@aklinker1/zeta"; + +export const corsPlugin = createApp() + .onRequest(({ method, set }) => { + set.headers["Access-Control-Allow-Origin"] = "*"; + set.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"; + set.headers["Access-Control-Allow-Headers"] = "*"; + if (method === "OPTIONS") { + set.status = 204; + } + }) + .export(); diff --git a/src/public/playground.html b/src/public/playground.html deleted file mode 100644 index 86f44a2..0000000 --- a/src/public/playground.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - Playground - wxt-queue v{{VERSION}} - - - - - - - - - - -
Loading...
- - - diff --git a/src/rest/getChromeScreenshot.ts b/src/rest/getChromeScreenshot.ts deleted file mode 100644 index aa42a32..0000000 --- a/src/rest/getChromeScreenshot.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ChromeService } from "../services/chrome-service"; -import { RouteHandler } from "../utils/rest-router"; - -export const getChromeScreenshot = - (chrome: ChromeService): RouteHandler<{ id: string; index: string }> => - async (params) => { - const extension = await chrome.getExtension(params.id); - const index = Number(params.index); - const screenshot = extension?.screenshots.find( - (screenshot) => screenshot.index == index, - ); - - if (screenshot == null) return new Response(null, { status: 404 }); - return Response.redirect(screenshot.rawUrl); - }; diff --git a/src/rest/getFirefoxScreenshot.ts b/src/rest/getFirefoxScreenshot.ts deleted file mode 100644 index 6ff3e05..0000000 --- a/src/rest/getFirefoxScreenshot.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { FirefoxService } from "../services/firefox-service"; -import { RouteHandler } from "../utils/rest-router"; - -export const getFirefoxScreenshot = - (firefox: FirefoxService): RouteHandler<{ id: string; index: string }> => - async (params) => { - const addon = await firefox.getAddon(params.id); - const index = Number(params.index); - const screenshot = addon?.screenshots.find( - (screenshot) => screenshot.index == index, - ); - - if (screenshot == null) return new Response(null, { status: 404 }); - return Response.redirect(screenshot.rawUrl); - }; diff --git a/src/routes/grpahql-routes.ts b/src/routes/grpahql-routes.ts new file mode 100644 index 0000000..4faaf79 --- /dev/null +++ b/src/routes/grpahql-routes.ts @@ -0,0 +1,30 @@ +import { createApp } from "@aklinker1/zeta"; +import PLAYGROUND_HTML_TEMPLATE from "../assets/playground.html.tmpl" with { type: "text" }; +import { version } from "../../package.json"; +import { createGraphql } from "../graphql"; +import { z } from "zod/v4"; + +const PLAYGROUND_HTML = PLAYGROUND_HTML_TEMPLATE.replace( + "{{VERSION}}", + version, +); + +const graphql = createGraphql(); + +export const graphqlRoutes = createApp() + .post( + "/api", + { + body: z.object({ + query: z.string(), + variables: z.record(z.string(), z.any()).optional(), + operationName: z.string().optional(), + }), + response: z.any(), + }, + ({ request, body }) => graphql.evaluateQuery(request.method, body), + ) + .get("/playground", ({ set }) => { + set.headers["Content-Type"] = "text/html; charset=utf-8"; + return PLAYGROUND_HTML; + }); diff --git a/src/routes/rest-routes.ts b/src/routes/rest-routes.ts new file mode 100644 index 0000000..978fbe9 --- /dev/null +++ b/src/routes/rest-routes.ts @@ -0,0 +1,48 @@ +import { createApp } from "@aklinker1/zeta"; +import { z } from "zod/v4"; +import { contextPlugin } from "../plugins/context-plugin"; +import { NotFoundError } from "@aklinker1/zeta/errors"; +import { Status } from "@aklinker1/zeta/status"; + +export const restRoutes = createApp() + .use(contextPlugin) + .get( + "/api/rest/chrome-extensions/:extensionId/screenshots/:screenshotIndex", + { + params: z.object({ + extensionId: z.string(), + screenshotIndex: z.coerce.number().int().min(0), + }), + }, + async ({ params, chrome, set }) => { + const screenshotUrl = await chrome.getScreenshotUrl( + params.extensionId, + params.screenshotIndex, + ); + if (!screenshotUrl) + throw new NotFoundError("Extension or screenshot not found"); + + set.status = Status.Found; + set.headers["Location"] = screenshotUrl; + }, + ) + .get( + "/api/rest/firefox-addons/:addonId/screenshots/:screenshotIndex", + { + params: z.object({ + addonId: z.string(), + screenshotIndex: z.coerce.number().int().min(0), + }), + }, + async ({ params, firefox, set }) => { + const screenshotUrl = await firefox.getScreenshotUrl( + params.addonId, + params.screenshotIndex, + ); + if (!screenshotUrl) + throw new NotFoundError("Extension or screenshot not found"); + + set.status = Status.Found; + set.headers["Location"] = screenshotUrl; + }, + ); diff --git a/src/server.ts b/src/server.ts index 3aa9c1d..59c6365 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,129 +1,32 @@ -import pc from "picocolors"; -import pkg from "../package.json"; -import { createGraphql } from "./graphql"; -import playgroundHtmlTemplate from "./public/playground.html"; import consola from "consola"; -import { createChromeService } from "./services/chrome-service"; -import { createFirefoxService } from "./services/firefox-service"; -import { createRestRouter } from "./utils/rest-router"; -import { getChromeScreenshot } from "./rest/getChromeScreenshot"; -import { getFirefoxScreenshot } from "./rest/getFirefoxScreenshot"; -import { SERVER_ORIGIN } from "./utils/urls"; - -const playgroundHtml = playgroundHtmlTemplate.replace( - "{{VERSION}}", - pkg.version, -); - -export function createServer(config?: ServerConfig) { - let port = config?.port; - if (port == null) port = Number(process.env.PORT ?? "3000"); - - const chrome = createChromeService(); - const firefox = createFirefoxService(); - const graphql = createGraphql({ - chrome, - firefox, - }); - - const restRouter = createRestRouter() - .get( - "/api/rest/chrome-extensions/:id/screenshots/:index", - getChromeScreenshot(chrome), - ) - .get( - "/api/rest/firefox-addons/:id/screenshots/:index", - getFirefoxScreenshot(firefox), - ); - - const httpServer = Bun.serve({ - port, - error(request) { - consola.error(request); +import { createApp } from "@aklinker1/zeta"; +import { corsPlugin } from "./plugins/cors-plugin"; +import { graphqlRoutes } from "./routes/grpahql-routes"; +import { restRoutes } from "./routes/rest-routes"; +import { zodSchemaAdapter } from "@aklinker1/zeta/adapters/zod-schema-adapter"; +import { version } from "../package.json"; + +const app = createApp({ + schemaAdapter: zodSchemaAdapter, + openApi: { + info: { + title: "WXT Queue API Reference", + version, }, - async fetch(req) { - if (req.method === "OPTIONS") { - return createResponse(undefined, { status: 204 }); - } - - const url = new URL(req.url, SERVER_ORIGIN); - - // REST - if (url.pathname.startsWith("/api/rest")) { - return restRouter.fetch(url, req); - } - - // GraphQL - if (url.pathname.startsWith("/api")) { - const data = await graphql.evaluateQuery(req); - - return createResponse(JSON.stringify(data), { - headers: { - "content-type": "application/json", - }, - }); - } - - // GraphiQL - if (req.url.endsWith("/playground")) - return createResponse(playgroundHtml, { - headers: { - "content-type": "text/html", - }, - }); - - // Redirect to GraphiQL - return createResponse(undefined, { - status: 302, - headers: { - location: "/playground", - }, - }); + }, +}) + .onError(({ error }) => void consola.error(error)) + .use(corsPlugin) + .use(restRoutes) + .use(graphqlRoutes) + .get( + "/", + { description: "Redirect to the GraphQL Playground" }, + ({ set }) => { + set.status = 302; + set.headers.Location = "/playground"; }, - }); - - consola.info( - `${pc.cyan("store-api v" + pkg.version)} ${pc.dim("server started")}`, ); - consola.log(` ${pc.bold(pc.green("➜"))} http://localhost:${port}`); - console.log(); - - return { - httpServer, - async introspect(): Promise { - const request = new Request("http://localhost/api", { - body: JSON.stringify({ - operationName: "IntrospectionQuery", - query: - "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } ", - }), - method: "POST", - }); - const res = await httpServer.fetch(request); - return await res.json(); - }, - }; -} - -export type Server = ReturnType; - -export interface ServerConfig { - port?: number; -} -function createResponse( - body?: - | ReadableStream - | BlobPart - | BlobPart[] - | FormData - | URLSearchParams - | null, - options?: ResponseInit, -) { - const res = new Response(body, options); - res.headers.set("Access-Control-Allow-Origin", "*"); - res.headers.set("Access-Control-Allow-Headers", "*"); - res.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - return res; -} +export default app; +export type App = typeof app; diff --git a/src/services/chrome-service.ts b/src/services/chrome-service.ts deleted file mode 100644 index 8423bdc..0000000 --- a/src/services/chrome-service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { chrome } from "../crawlers"; -import { createCachedDataLoader } from "../utils/cache"; -import { HOUR_MS } from "../utils/time"; - -export function createChromeService() { - const loader = createCachedDataLoader< - string, - Gql.ChromeExtension | undefined - >(HOUR_MS, async (ids) => { - const results = await Promise.allSettled( - ids.map((id) => chrome.crawlExtension(id, "en")), - ); - return results.map((res) => - res.status === "fulfilled" ? res.value : res.reason, - ); - }); - - return { - getExtension: (id: string): Promise => - loader.load(id), - getExtensions: async ( - ids: string[], - ): Promise> => { - const result = await loader.loadMany(ids); - return result.map((item, index) => { - if (item instanceof Error) { - console.warn("Error loading extension:", ids[index], item); - return undefined; - } - return item; - }); - }, - }; -} - -export type ChromeService = ReturnType; diff --git a/src/services/firefox-service.ts b/src/services/firefox-service.ts deleted file mode 100644 index a284f58..0000000 --- a/src/services/firefox-service.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createFirefoxApiClient } from "../apis"; -import { HOUR_MS } from "../utils/time"; -import { createCachedDataLoader } from "../utils/cache"; - -export function createFirefoxService() { - const firefox = createFirefoxApiClient(); - - const loader = createCachedDataLoader< - string | number, - Gql.FirefoxAddon | undefined - >(HOUR_MS, (ids) => Promise.all(ids.map((id) => firefox.getAddon(id)))); - - return { - getAddon: (id: string | number): Promise => - loader.load(id), - getAddons: async ( - ids: Array, - ): Promise> => { - const result = await loader.loadMany(ids); - return result.map((item) => { - if (item == null) return undefined; - if (item instanceof Error) { - console.warn("Error fetching multiple addons:", item); - return undefined; - } - return item; - }); - }, - }; -} - -export type FirefoxService = ReturnType; diff --git a/src/utils/cache.ts b/src/utils/cache.ts index fa25f6f..c1a3827 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,4 +1,4 @@ -import DataLoader, { CacheMap } from "dataloader"; +import DataLoader, { type CacheMap } from "dataloader"; export function createInMemoryCache(config: { ttl: number; diff --git a/src/crawlers/__tests__/chrome-crawler.test.ts b/src/utils/chrome/__tests__/chrome-crawler.test.ts similarity index 97% rename from src/crawlers/__tests__/chrome-crawler.test.ts rename to src/utils/chrome/__tests__/chrome-crawler.test.ts index d973344..0186be5 100644 --- a/src/crawlers/__tests__/chrome-crawler.test.ts +++ b/src/utils/chrome/__tests__/chrome-crawler.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { crawlExtension } from "../chrome-crawler"; -import { numberOfDFGCompiles } from "bun:jsc"; const githubBetterLineCountsId = "ocfdgncpifmegplaglcnglhioflaimkd"; diff --git a/src/crawlers/chrome-crawler.ts b/src/utils/chrome/chrome-crawler.ts similarity index 93% rename from src/crawlers/chrome-crawler.ts rename to src/utils/chrome/chrome-crawler.ts index 7c3baa2..18576e2 100644 --- a/src/crawlers/chrome-crawler.ts +++ b/src/utils/chrome/chrome-crawler.ts @@ -1,6 +1,6 @@ import consola from "consola"; import { HTMLAnchorElement, HTMLElement, parseHTML } from "linkedom"; -import { buildScreenshotUrl } from "../utils/urls"; +import { buildScreenshotUrl } from "../urls"; export async function crawlExtension( id: string, @@ -164,14 +164,8 @@ function metaContent(document: any, attrSelector: string): string | undefined { .trim(); } -function nextSpanText(document: any, text: string): string | undefined { - const spans: any[] = Array.from(document.querySelectorAll("span")); - const span = spans.find((span: any) => span.textContent?.startsWith(text)); - return span.nextElementSibling.textContent.trim(); -} - function extractNumber(text: string): number | undefined { - const res = /([0-9\.,]+)/.exec(text)?.[1]; + const res = /([0-9.,]+)/.exec(text)?.[1]; if (res == null) return; const num = Number(res); diff --git a/src/utils/chrome/chrome-service.ts b/src/utils/chrome/chrome-service.ts new file mode 100644 index 0000000..7a60c9f --- /dev/null +++ b/src/utils/chrome/chrome-service.ts @@ -0,0 +1,63 @@ +import { crawlExtension } from "./chrome-crawler"; +import { createCachedDataLoader } from "../cache"; +import { HOUR_MS } from "../time"; + +export interface ChromeService { + getExtension: ( + extensionId: string, + ) => Promise; + getExtensions: ( + extensionIds: string[], + ) => Promise>; + getScreenshotUrl( + extensionId: string, + screenshotIndex: number, + ): Promise; +} + +export function createChromeService(): ChromeService { + const loader = createCachedDataLoader< + string, + Gql.ChromeExtension | undefined + >(HOUR_MS, async (ids) => { + const results = await Promise.allSettled( + ids.map((id) => crawlExtension(id, "en")), + ); + return results.map((res) => + res.status === "fulfilled" ? res.value : res.reason, + ); + }); + + const getExtension: ChromeService["getExtension"] = (extensionId) => + loader.load(extensionId); + + const getExtensions: ChromeService["getExtensions"] = async ( + extensionIds, + ) => { + const result = await loader.loadMany(extensionIds); + return result.map((item, index) => { + if (item instanceof Error) { + console.warn("Error loading extension:", extensionIds[index], item); + return undefined; + } + return item; + }); + }; + + const getScreenshotUrl: ChromeService["getScreenshotUrl"] = async ( + extensionId, + screenshotIndex, + ) => { + const extension = await getExtension(extensionId); + const screenshot = extension?.screenshots.find( + (screenshot) => screenshot.index == screenshotIndex, + ); + return screenshot?.rawUrl; + }; + + return { + getExtension, + getExtensions, + getScreenshotUrl, + }; +} diff --git a/src/apis/firefox-api.ts b/src/utils/firefox/firefox-api.ts similarity index 89% rename from src/apis/firefox-api.ts rename to src/utils/firefox/firefox-api.ts index 571339f..66468e6 100644 --- a/src/apis/firefox-api.ts +++ b/src/utils/firefox/firefox-api.ts @@ -1,5 +1,5 @@ import consola from "consola"; -import { buildScreenshotUrl } from "../utils/urls"; +import { buildScreenshotUrl } from "../urls"; export function createFirefoxApiClient() { return { @@ -16,17 +16,17 @@ export function createFirefoxApiClient() { `${url.href} failed with status: ${res.status} ${res.statusText}`, ); - const json = await res.json(); + const json: any = await res.json(); return { id: json.id, iconUrl: json.icon_url, lastUpdated: json.last_updated, - longDescription: Object.values(json.description)[0], - name: Object.values(json.name)[0], + longDescription: Object.values(json.description)[0]!, + name: Object.values(json.name)[0]!, rating: json.ratings.average, reviewCount: json.ratings.count, - shortDescription: Object.values(json.summary)[0], + shortDescription: Object.values(json.summary)[0]!, storeUrl: json.url, version: json.current_version.version, dailyActiveUsers: json.average_daily_users, diff --git a/src/utils/firefox/firefox-service.ts b/src/utils/firefox/firefox-service.ts new file mode 100644 index 0000000..76b0287 --- /dev/null +++ b/src/utils/firefox/firefox-service.ts @@ -0,0 +1,57 @@ +import { createFirefoxApiClient } from "./firefox-api"; +import { HOUR_MS } from "../time"; +import { createCachedDataLoader } from "../cache"; + +type AddonId = string | number; + +export interface FirefoxService { + getAddon: (addonId: AddonId) => Promise; + getAddons: ( + addonIds: Array, + ) => Promise>; + getScreenshotUrl: ( + addonId: AddonId, + screenshotIndex: number, + ) => Promise; +} + +export function createFirefoxService(): FirefoxService { + const firefox = createFirefoxApiClient(); + + const loader = createCachedDataLoader< + string | number, + Gql.FirefoxAddon | undefined + >(HOUR_MS, (ids) => Promise.all(ids.map((id) => firefox.getAddon(id)))); + + const getAddon: FirefoxService["getAddon"] = (addonId) => + loader.load(addonId); + + const getAddons: FirefoxService["getAddons"] = async (addonIds) => { + const result = await loader.loadMany(addonIds); + return result.map((item) => { + if (item == null) return undefined; + if (item instanceof Error) { + console.warn("Error fetching multiple addons:", item); + return undefined; + } + return item; + }); + }; + + const getScreenshotUrl: FirefoxService["getScreenshotUrl"] = async ( + extensionId, + screenshotIndex, + ) => { + const addon = await getAddon(extensionId); + const screenshot = addon?.screenshots.find( + (screenshot) => screenshot.index == screenshotIndex, + ); + return screenshot?.rawUrl; + }; + + return { + getAddon, + getAddons, + getScreenshotUrl, + }; +} diff --git a/src/utils/rest-router.ts b/src/utils/rest-router.ts deleted file mode 100644 index 7dc72ac..0000000 --- a/src/utils/rest-router.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as radix3 from "radix3"; - -export type RouteHandler = ( - params: TParams, - url: URL, - req: Request, -) => Response | Promise; - -export interface Route { - method: string; - handler: RouteHandler; -} - -export function createRestRouter() { - const r = radix3.createRouter(); - const router = { - get(path: string, handler: RouteHandler) { - r.insert(path, { method: "GET", handler }); - return router; - }, - post(path: string, handler: RouteHandler) { - r.insert(path, { method: "POST", handler }); - return router; - }, - any(path: string, handler: RouteHandler) { - r.insert(path, { method: "ANY", handler }); - return router; - }, - on(method: string, path: string, handler: RouteHandler) { - r.insert(path, { method, handler }); - return router; - }, - async fetch(url: URL, req: Request): Promise { - const match = r.lookup(url.pathname); - if (match && (req.method === match.method || match.method === "ANY")) { - return await match.handler(match.params ?? {}, url, req); - } - return new Response(null, { status: 404 }); - }, - }; - return router; -} diff --git a/tsconfig.json b/tsconfig.json index e27dedd..3a8ded5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,23 @@ { "compilerOptions": { - "lib": ["ESNext", "DOM"], - "module": "esnext", - "target": "esnext", - "moduleResolution": "bundler", + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", "moduleDetection": "force", + "jsx": "preserve", + + // Bundler mode + "moduleResolution": "bundler", "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, "noEmit": true, - "composite": true, + + // Best practices "strict": true, - "downlevelIteration": true, "skipLibCheck": true, - "jsx": "preserve", - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "allowJs": true, - "types": ["bun-types"] - }, - "include": ["package.json", "src/**/*", "scripts/**/*"] + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true + } } From ca2e80951b2cadf60a8a4ecbddf166ab7763e2f6 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 01:31:55 -0500 Subject: [PATCH 2/7] Add gitattributes --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0c7eadd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`. + +# Normalize EOL for all files that Git considers text files +* text=auto eol=lf + +# Mark lock files as generated to avoid diffing +bun.lock linguist-generated From 817b78e423139ec53c452de4004805509b8298c2 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 01:54:00 -0500 Subject: [PATCH 3/7] fix checks --- .github/workflows/validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d719592..1803dda 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup - - run: bun generate:types + - run: bun gen:types - run: bun check tests: runs-on: ubuntu-22.04 From c00fcf76a0790781f64a823bc7fbb08e707e132c Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 01:58:24 -0500 Subject: [PATCH 4/7] address testsg --- .github/actions/setup/action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index f5c5772..a016379 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -3,6 +3,8 @@ description: Install Bun and dependencies runs: using: composite steps: - - uses: oven-sh/setup-bun@v1 + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json - run: bun install shell: bash From 3d386aed494fed8762b8f8681308f8cbe7daaa58 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 02:00:35 -0500 Subject: [PATCH 5/7] Add missing package --- bun.lock | 9 +++++++++ package.json | 1 + 2 files changed, 10 insertions(+) diff --git a/bun.lock b/bun.lock index 249e6d7..394a741 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": ".", "dependencies": { "@aklinker1/zero-ioc": "^1.3.2", + "@aklinker1/zeta": "npm:@jsr/aklinker1__zeta@0.2.7", "consola": "^3.2.3", "dataloader": "^2.2.2", "graphql": "^16.8.0", @@ -29,6 +30,8 @@ "@aklinker1/zero-ioc": ["@aklinker1/zero-ioc@1.3.2", "", {}, "sha512-J9nwXUprKdoKAZmM9b9ZvR5Z4/iVm29tDzgFhBtLdAIvxb/j3NZoVTX+wi5sZaGKsLP3rcgJD0QRUfvNd9MCIQ=="], + "@aklinker1/zeta": ["@jsr/aklinker1__zeta@0.2.7", "https://npm.jsr.io/~/11/@jsr/aklinker1__zeta/0.2.7.tgz", { "dependencies": { "@standard-schema/spec": "^1.0.0", "openapi-types": "^12.1.3", "rou3": "^0.7.1" } }, "sha512-QKEYFQ5WmMjLWPXO5xzBcwxQkLlVtWMHXwNrCNkpFnG+6OMrpfmU8D9Fpae3ad/yvQ0GTwQQrtw8/3/STJcImQ=="], + "@antfu/utils": ["@antfu/utils@0.7.7", "", {}, "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg=="], "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.6.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-m3wyqBh1TOHjpr/dXeIZY7OoX+MQazb+bMHQdDtwUvefrafUx+5YHRvulYh1sZSQ449nQ3nk3qj5qj535vZRjg=="], @@ -47,6 +50,8 @@ "@oxlint/win32-x64": ["@oxlint/win32-x64@1.6.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jOj3L/gfLc0IwgOTkZMiZ5c673i/hbAmidlaylT0gE6H18hln9HxPgp5GCf4E4y6mwEJlW8QC5hQi221+9otdA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], "@types/node": ["@types/node@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="], @@ -159,6 +164,8 @@ "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "oxlint": ["oxlint@1.6.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.6.0", "@oxlint/darwin-x64": "1.6.0", "@oxlint/linux-arm64-gnu": "1.6.0", "@oxlint/linux-arm64-musl": "1.6.0", "@oxlint/linux-x64-gnu": "1.6.0", "@oxlint/linux-x64-musl": "1.6.0", "@oxlint/win32-arm64": "1.6.0", "@oxlint/win32-x64": "1.6.0" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-jtaD65PqzIa1udvSxxscTKBxYKuZoFXyKGLiU1Qjo1ulq3uv/fQDtoV1yey1FrQZrQjACGPi1Widsy1TucC7Jg=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -175,6 +182,8 @@ "rfdc": ["rfdc@1.3.1", "", {}, "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg=="], + "rou3": ["rou3@0.7.3", "", {}, "sha512-KKenF/hB2iIhS1ohj226LT+/8uKCBpSMqeS4V1UPN9vad99uLoyIhrULRRB1skaB40LQHcBlSsAi3sT8MaoDDQ=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], diff --git a/package.json b/package.json index 40f7ad9..6645986 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@aklinker1/zero-ioc": "^1.3.2", + "@aklinker1/zeta": "npm:@jsr/aklinker1__zeta@0.2.7", "consola": "^3.2.3", "dataloader": "^2.2.2", "graphql": "^16.8.0", From b17e1bff7e0ce58d88a03c30f14eafae94fe6854 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 02:06:31 -0500 Subject: [PATCH 6/7] fix global fetch bug --- .../__tests__/chrome-crawler.e2e.test.ts | 2 +- .../chrome/__tests__/chrome-crawler.test.ts | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/utils/chrome/__tests__/chrome-crawler.e2e.test.ts b/src/utils/chrome/__tests__/chrome-crawler.e2e.test.ts index 4f4ae0d..cc14817 100644 --- a/src/utils/chrome/__tests__/chrome-crawler.e2e.test.ts +++ b/src/utils/chrome/__tests__/chrome-crawler.e2e.test.ts @@ -13,7 +13,7 @@ describe("Chrome Web Store Crawler E2E", () => { id: githubBetterLineCountsId, lastUpdated: expect.any(String), longDescription: expect.stringContaining("Isn't it annoying when you"), - name: "GitHub: Better Line Counts", + name: "GitHub Better Line Counts", rating: expect.any(Number), reviewCount: expect.any(Number), shortDescription: "Remove generated files from GitHub line counts", diff --git a/src/utils/chrome/__tests__/chrome-crawler.test.ts b/src/utils/chrome/__tests__/chrome-crawler.test.ts index 5369edd..430fa89 100644 --- a/src/utils/chrome/__tests__/chrome-crawler.test.ts +++ b/src/utils/chrome/__tests__/chrome-crawler.test.ts @@ -10,13 +10,20 @@ // 5. You're done! The test is added, run `bun test`. // -import { beforeEach, describe, expect, it, mock } from "bun:test"; +import { + afterAll, + beforeEach, + describe, + expect, + it, + mock, + spyOn, +} from "bun:test"; import { crawlExtension } from "../chrome-crawler"; import { readdir } from "node:fs/promises"; import { join } from "node:path"; -const fetchMock = mock(); -globalThis.fetch = fetchMock as any; +const fetchSpy = spyOn(globalThis, "fetch"); describe("Chrome Web Store Crawler", async () => { const fixturesDir = join(import.meta.dir, "fixtures/chrome-web-store"); @@ -27,14 +34,18 @@ describe("Chrome Web Store Crawler", async () => { file.match(/.*-([a-z]+)\.html/)![1]!; beforeEach(() => { - fetchMock.mockReset(); + fetchSpy.mockReset(); + }); + + afterAll(() => { + fetchSpy.mockRestore(); }); it.each(testFiles)( "should extract extension details from %s", async (file) => { const id = getExtensionIdFromFile(file); - fetchMock.mockResolvedValueOnce( + fetchSpy.mockResolvedValueOnce( new Response(Bun.file(join(fixturesDir, file))), ); const res = await crawlExtension(id, "en", true); From 43330175460984481006b9b259dd34f2595d0779 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 8 Jul 2025 02:08:58 -0500 Subject: [PATCH 7/7] fix checks --- src/utils/chrome/__tests__/chrome-crawler.test.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/utils/chrome/__tests__/chrome-crawler.test.ts b/src/utils/chrome/__tests__/chrome-crawler.test.ts index 430fa89..48dbefe 100644 --- a/src/utils/chrome/__tests__/chrome-crawler.test.ts +++ b/src/utils/chrome/__tests__/chrome-crawler.test.ts @@ -9,16 +9,7 @@ // 4. Move the HTML file up one folder so it's next to the other test fixtures // 5. You're done! The test is added, run `bun test`. // - -import { - afterAll, - beforeEach, - describe, - expect, - it, - mock, - spyOn, -} from "bun:test"; +import { afterAll, beforeEach, describe, expect, it, spyOn } from "bun:test"; import { crawlExtension } from "../chrome-crawler"; import { readdir } from "node:fs/promises"; import { join } from "node:path";