From 363ebeba32f297db140445fd4437efbfeadf1e60 Mon Sep 17 00:00:00 2001 From: Sean Derrow Date: Sun, 30 Aug 2026 17:26:53 -0400 Subject: [PATCH] use typescript for scripts --- .github/workflows/ci.yaml | 8 +- package.json | 21 +- pnpm-lock.yaml | 305 ++++++++++++++++++++++- pnpm-workspace.yaml | 1 + scripts/{ci-matrix.mjs => ci-matrix.mts} | 15 +- scripts/{test-ci.mjs => test-ci.mts} | 59 +++-- tsconfig.json | 2 +- 7 files changed, 361 insertions(+), 50 deletions(-) rename scripts/{ci-matrix.mjs => ci-matrix.mts} (84%) rename scripts/{test-ci.mjs => test-ci.mts} (87%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b45e4cb..909d491 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,7 +34,7 @@ jobs: - name: Run build run: pnpm run build - # Matrix data for both matrix jobs below. scripts/ci-matrix.mjs is the + # Matrix data for both matrix jobs below. scripts/ci-matrix.mts is the # single source of truth (also imported by the local runner: `pnpm run # test:ci`), so the workflow and local legs cannot drift apart. matrix-data: @@ -49,7 +49,7 @@ jobs: - name: Emit matrix JSON id: emit - run: node scripts/ci-matrix.mjs --emit >> "$GITHUB_OUTPUT" + run: node scripts/ci-matrix.mts --emit >> "$GITHUB_OUTPUT" test-no-cluster: name: Test (no cluster) @@ -73,7 +73,7 @@ jobs: needs: [checks, matrix-data] strategy: fail-fast: false - # Entries come from scripts/ci-matrix.mjs via the matrix-data job. + # Entries come from scripts/ci-matrix.mts via the matrix-data job. matrix: image: ${{ fromJSON(needs.matrix-data.outputs.cluster-images) }} env: @@ -99,7 +99,7 @@ jobs: strategy: fail-fast: false # Include entries ({client, version, project, image}) come from - # scripts/ci-matrix.mjs via the matrix-data job. + # scripts/ci-matrix.mts via the matrix-data job. matrix: ${{ fromJSON(needs.matrix-data.outputs.client-matrix) }} env: REDIS_IMAGE: ${{ matrix.image }} diff --git a/package.json b/package.json index cd4c208..64633b1 100644 --- a/package.json +++ b/package.json @@ -59,16 +59,16 @@ "lint:fix": "oxlint --fix", "prepare": "husky", "prepublishOnly": "pnpm run build && tsc", - "test": "pnpm exec vitest", - "test:ci": "node scripts/test-ci.mjs", - "test:cluster": "pnpm exec vitest run --project ioredis --project node-redis", - "test:coverage": "pnpm exec vitest run --coverage", - "test:lib": "pnpm exec vitest run --project lib-smoke", - "test:light": "pnpm exec vitest run --project light-smoke", - "test:local": "pnpm exec vitest run --project local", - "test:memory": "pnpm exec vitest run --project memory", - "test:no-cluster": "SKIP_REDIS_CONTAINER=1 pnpm exec vitest run --project local --project light-smoke --project memory", - "test:smoke": "pnpm exec vitest run --project light-smoke --project lib-smoke" + "test": "vitest", + "test:ci": "tsx scripts/test-ci.mts", + "test:cluster": "vitest run --project ioredis --project node-redis", + "test:coverage": "vitest run --coverage", + "test:lib": "vitest run --project lib-smoke", + "test:light": "vitest run --project light-smoke", + "test:local": "vitest run --project local", + "test:memory": "vitest run --project memory", + "test:no-cluster": "SKIP_REDIS_CONTAINER=1 vitest run --project local --project light-smoke --project memory", + "test:smoke": "vitest run --project light-smoke --project lib-smoke" }, "devDependencies": { "@testcontainers/redis": "^12.1.0", @@ -82,6 +82,7 @@ "redis": "^6.2.1", "testcontainers": "^12.1.0", "tsdown": "^0.22.14", + "tsx": "^4.23.12", "typescript": "^6.0.0", "vitest": "^4.1.11" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 011bef3..f49eda6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,13 +144,16 @@ importers: version: 12.1.0(supports-color@7.2.0) tsdown: specifier: ^0.22.14 - version: 0.22.14(typescript@6.0.3) + version: 0.22.14(tsx@4.23.12)(typescript@6.0.3) + tsx: + specifier: ^4.23.12 + version: 4.23.12 typescript: specifier: ^6.0.0 version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@24.13.3)(yaml@2.9.0)) + version: 4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0)) packages: @@ -178,6 +181,162 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@grpc/grpc-js@1.14.4': resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} engines: {node: '>=12.10.0'} @@ -1083,6 +1242,11 @@ packages: es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1793,6 +1957,11 @@ packages: unrun: optional: true + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} + engines: {node: '>=18.0.0'} + hasBin: true + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -1975,6 +2144,84 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + '@grpc/grpc-js@1.14.4': dependencies: '@grpc/proto-loader': 0.8.1 @@ -2296,7 +2543,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@24.13.3)(yaml@2.9.0)) + vitest: 4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/expect@4.1.11': dependencies: @@ -2307,13 +2554,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@24.13.3)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.2.2(@types/node@24.13.3)(yaml@2.9.0) + vite: 8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0) '@vitest/pretty-format@4.1.11': dependencies: @@ -2640,6 +2887,35 @@ snapshots: es-module-lexer@2.3.2: {} + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} estree-walker@3.0.3: @@ -3225,7 +3501,7 @@ snapshots: tree-kill@1.2.2: {} - tsdown@0.22.14(typescript@6.0.3): + tsdown@0.22.14(tsx@4.23.12)(typescript@6.0.3): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -3243,6 +3519,7 @@ snapshots: unconfig-core: 7.5.0 verkit: 0.3.2 optionalDependencies: + tsx: 4.23.12 typescript: 6.0.3 transitivePeerDependencies: - '@typescript/native-preview' @@ -3250,6 +3527,12 @@ snapshots: - oxc-resolver - vue-tsc + tsx@4.23.12: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + tweetnacl@0.14.5: {} typescript@6.0.3: {} @@ -3269,7 +3552,7 @@ snapshots: verkit@0.3.2: {} - vite@8.2.2(@types/node@24.13.3)(yaml@2.9.0): + vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -3278,13 +3561,15 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.13.3 + esbuild: 0.28.2 fsevents: 2.3.3 + tsx: 4.23.12 yaml: 2.9.0 - vitest@4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@24.13.3)(yaml@2.9.0)): + vitest@4.1.11(@types/node@24.13.3)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@24.13.3)(yaml@2.9.0)) + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -3301,7 +3586,7 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.2.2(@types/node@24.13.3)(yaml@2.9.0) + vite: 8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(tsx@4.23.12)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a1b00a1..52d3bb6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ allowBuilds: cpu-features: true + esbuild: true protobufjs: true ssh2: true diff --git a/scripts/ci-matrix.mjs b/scripts/ci-matrix.mts similarity index 84% rename from scripts/ci-matrix.mjs rename to scripts/ci-matrix.mts index 176e5c7..2bd6163 100644 --- a/scripts/ci-matrix.mjs +++ b/scripts/ci-matrix.mts @@ -1,9 +1,9 @@ #!/usr/bin/env node // Single source of truth for the CI test matrices, consumed by: // - .github/workflows/ci.yaml — the "matrix-data" prepare job runs -// `node scripts/ci-matrix.mjs --emit >> "$GITHUB_OUTPUT"` and the matrix +// `node scripts/ci-matrix.mts --emit >> "$GITHUB_OUTPUT"` and the matrix // jobs feed those outputs into strategy.matrix via fromJSON. -// - scripts/test-ci.mjs — the local CI runner imports these exports to +// - scripts/test-ci.mts — the local CI runner imports these exports to // recreate the exact same legs locally (`pnpm run test:ci`). // // Edit here to change CI coverage (a new server image, a new client pin): @@ -31,17 +31,22 @@ export const CLIENT_MATRIX_IMAGE = "redis:6-alpine"; // Varies the CLIENT library versions. Each vitest project uses exactly one // client, so each leg overrides a single devDependency and runs only the // matching project — no cross-product legs. -export const CLIENT_PINS = [ +type ClientPin = { + client: "ioredis" | "redis"; + version: string; + project: string; +}; +export const CLIENT_PINS: ClientPin[] = [ { client: "ioredis", version: "5", project: "ioredis" }, { client: "redis", version: "4", project: "node-redis" }, { client: "redis", version: "5", project: "node-redis" }, ]; // CLI: emit GitHub Actions output assignments for ci.yaml's matrix-data job. -// Only runs on direct execution, never when test-ci.mjs imports this module. +// Only runs on direct execution, never when test-ci.mts imports this module. if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { if (process.argv[2] !== "--emit") { - console.error("usage: node scripts/ci-matrix.mjs --emit"); + console.error("usage: pnpm tsx scripts/ci-matrix.mts --emit"); process.exit(2); } console.log(`cluster-images=${JSON.stringify(CLUSTER_IMAGES)}`); diff --git a/scripts/test-ci.mjs b/scripts/test-ci.mts similarity index 87% rename from scripts/test-ci.mjs rename to scripts/test-ci.mts index 968e2d3..c53e38e 100644 --- a/scripts/test-ci.mjs +++ b/scripts/test-ci.mts @@ -14,7 +14,7 @@ // runs, results are summarized, and the exit code is non-zero if any failed. // // Assumes the same environment as any test run here: pnpm and a running Docker -// daemon. Matrix inputs come from ./ci-matrix.mjs — the same single source of +// daemon. Matrix inputs come from ./ci-matrix.mts — the same single source of // truth .github/workflows/ci.yaml consumes. import { spawnSync } from "node:child_process"; @@ -22,10 +22,27 @@ import { readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import process from "node:process"; import { fileURLToPath } from "node:url"; -import { CLIENT_MATRIX_IMAGE, CLIENT_PINS, CLUSTER_IMAGES } from "./ci-matrix.mjs"; +import { CLIENT_MATRIX_IMAGE, CLIENT_PINS, CLUSTER_IMAGES } from "./ci-matrix.mts"; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +type Pin = (typeof CLIENT_PINS)[number]; +type Step = { + title: string; + cmd: string[]; + env?: Record; + pin?: Pin; +}; +type Leg = { + name: string; + steps: Step[]; +}; +type LegResult = { + leg: Leg; + ok: boolean; + ms: number; +}; + const USAGE = `Locally recreates the CI test matrices from .github/workflows/ci.yaml. Usage: @@ -47,18 +64,18 @@ Notes: \`pnpm install\` re-syncs node_modules, so nothing is left mutated.`; const tty = process.stdout.isTTY; -const color = (code, text) => (tty ? `\x1b[${code}m${text}\x1b[0m` : text); -const dim = (text) => color(2, text); -const green = (text) => color(32, text); -const red = (text) => color(31, text); -const bold = (text) => color(1, text); +const color = (code: number, text: string) => (tty ? `\x1b[${code}m${text}\x1b[0m` : text); +const dim = (text: string) => color(2, text); +const green = (text: string) => color(32, text); +const red = (text: string) => color(31, text); +const bold = (text: string) => color(1, text); -function fatal(message) { +function fatal(message: string): never { console.error(red(`error: ${message}`)); process.exit(2); } -function formatDuration(ms) { +function formatDuration(ms: number) { if (ms < 1000) return `${ms}ms`; const seconds = ms / 1000; if (seconds < 60) return `${seconds.toFixed(1)}s`; @@ -66,7 +83,7 @@ function formatDuration(ms) { return `${minutes}m ${String(Math.round(seconds % 60)).padStart(2, "0")}s`; } -function formatCmd(cmd, env = {}) { +function formatCmd(cmd: string[], env: Record = {}) { const prefix = Object.entries(env) .map(([key, value]) => `${key}=${value}`) .join(" "); @@ -77,13 +94,14 @@ function formatCmd(cmd, env = {}) { const LEG_NAMES = ["cluster", "clients"]; const args = process.argv.slice(2); -const imageFilter = []; -const clientFilter = []; -const explicitLegs = []; +const imageFilter: string[] = []; +const clientFilter: string[] = []; +const explicitLegs: string[] = []; let dryRun = false; for (let i = 0; i < args.length; i++) { const arg = args[i]; + if (arg === undefined) continue; if (arg === "-h" || arg === "--help") { console.log(USAGE); process.exit(0); @@ -126,7 +144,7 @@ if (clientFilter.length) selectedLegs.add("clients"); // -------------------------------------------------------------------- plan -const plan = []; +const plan: Leg[] = []; if (selectedLegs.has("cluster")) { for (const image of imageFilter.length ? imageFilter : CLUSTER_IMAGES) { plan.push({ @@ -175,9 +193,9 @@ if (dryRun) { // ------------------------------------------------------------------ runner -function exec(cmd, env = {}) { +function exec(cmd: string[], env: Record = {}) { console.log(dim(` $ ${formatCmd(cmd, env)}`)); - const result = spawnSync(cmd[0], cmd.slice(1), { + const result = spawnSync(cmd[0] as string, cmd.slice(1), { cwd: ROOT, env: { ...process.env, ...env }, stdio: "inherit", @@ -198,7 +216,7 @@ function exec(cmd, env = {}) { // pnpm-lock.yaml are mutated then restored. spawnSync blocks the event loop, // so the restore is wired into SIGINT/SIGTERM instead of relying on finally; // a hard SIGKILL is recoverable with `git checkout package.json pnpm-lock.yaml`. -let savedManifests; +let savedManifests: Map | undefined; let pinnedThisRun = false; function saveManifests() { @@ -214,7 +232,7 @@ function restoreManifests() { } let finishing = false; -function finish(code) { +function finish(code: number) { if (finishing) return; finishing = true; restoreManifests(); @@ -224,17 +242,18 @@ function finish(code) { process.on("SIGINT", () => finish(130)); process.on("SIGTERM", () => finish(143)); -function runClientLeg(leg) { +function runClientLeg(leg: Leg) { saveManifests(); pinnedThisRun = true; const [pinStep, testStep] = leg.steps; + if (!pinStep || !testStep) throw new Error(`Client leg ${leg.name} needs exactly two steps`); let ok = exec(pinStep.cmd); if (ok) ok = exec(testStep.cmd, testStep.env); restoreManifests(); return ok; } -const results = []; +const results: LegResult[] = []; for (const [index, leg] of plan.entries()) { console.log(`\n${bold(`▶ ${leg.name}`)} ${dim(`(${index + 1}/${plan.length})`)}`); const startedAt = Date.now(); diff --git a/tsconfig.json b/tsconfig.json index 883f265..5e0c278 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src", "test", "test.ts", "vitest.config.mts", "tsdown.config.mts"], + "include": ["src", "test", "test.ts", "scripts", "vitest.config.mts", "tsdown.config.mts"], "compilerOptions": { // Environment setup & latest features "lib": ["ESNext"],