diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index fa41b0906a79..0dcdebf039d2 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -8,8 +8,13 @@ on: workflow_dispatch: jobs: - typecheck: + shard: + name: typecheck (${{ matrix.shard }}/5) runs-on: blacksmith-4vcpu-ubuntu-2404 + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4, 5] steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -17,5 +22,21 @@ jobs: - name: Setup Bun uses: ./.github/actions/setup-bun + # One slice of the workspace per job: all 37 packages at once is more than a 4-vCPU runner + # can hold, and the job that tried used to die mid-run. Assignment is by sorted package + # name (see script/typecheck-shard.ts), so a failure names the package. - name: Run typecheck - run: bun typecheck + run: bun script/typecheck-shard.ts $((${{ matrix.shard }} - 1)) 5 + + # The one check branch protection requires. It passes only when every shard did, so the + # protection rule keeps its name and the matrix can change shape underneath it. + typecheck: + name: typecheck + runs-on: ubuntu-latest + needs: shard + if: always() + steps: + - name: Every shard passed + run: | + echo "shards: ${{ needs.shard.result }}" + test "${{ needs.shard.result }}" = "success" diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index 5d3cc53411be..000000000000 --- a/.husky/pre-push +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -set -e -# Check if bun version matches package.json -# keep in sync with packages/script/src/index.ts semver qualifier -bun -e ' -import { semver } from "bun"; -const pkg = await Bun.file("package.json").json(); -const expectedBunVersion = pkg.packageManager?.split("@")[1]; -if (!expectedBunVersion) { - throw new Error("packageManager field not found in root package.json"); -} -const expectedBunVersionRange = `^${expectedBunVersion}`; -if (!semver.satisfies(process.versions.bun, expectedBunVersionRange)) { - throw new Error(`This script requires bun@${expectedBunVersionRange}, but you are using bun@${process.versions.bun}`); -} -if (process.versions.bun !== expectedBunVersion) { - console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`); -} -' -bun typecheck diff --git a/bun.lock b/bun.lock index d2374a2c6127..67a2a345a93b 100644 --- a/bun.lock +++ b/bun.lock @@ -19,7 +19,6 @@ "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", "glob": "13.0.5", - "husky": "9.1.7", "oxlint": "1.60.0", "oxlint-tsgolint": "0.21.0", "prettier": "3.6.2", @@ -30,7 +29,7 @@ }, "packages/app": { "name": "@reddb-io/redcode-app", - "version": "1.18.20", + "version": "1.19.0", "dependencies": { "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", @@ -97,7 +96,7 @@ }, "packages/cli": { "name": "@reddb-io/redcode-cli", - "version": "1.18.20", + "version": "1.18.21", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -291,7 +290,7 @@ }, "packages/core": { "name": "@reddb-io/redcode-core", - "version": "1.19.0", + "version": "1.20.0", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.112", @@ -464,7 +463,7 @@ }, "packages/enterprise": { "name": "@reddb-io/redcode-enterprise", - "version": "1.18.20", + "version": "1.18.21", "dependencies": { "@hono/standard-validator": "catalog:", "@pierre/diffs": "catalog:", @@ -605,7 +604,7 @@ }, "packages/redcode": { "name": "@reddb-io/redcode", - "version": "0.15.0", + "version": "0.16.0", "bin": { "redcode": "./bin/redcode", }, @@ -770,7 +769,7 @@ }, "packages/sdk-next": { "name": "@reddb-io/redcode-sdk-next", - "version": "1.18.20", + "version": "1.18.21", "dependencies": { "@reddb-io/redcode-client": "workspace:*", "@reddb-io/redcode-core": "workspace:*", @@ -800,7 +799,7 @@ }, "packages/server": { "name": "@reddb-io/redcode-server", - "version": "1.18.20", + "version": "1.18.21", "dependencies": { "@reddb-io/multi-rpc": "0.29.6", "@reddb-io/redcode-core": "workspace:*", @@ -818,7 +817,7 @@ }, "packages/session-ui": { "name": "@reddb-io/redcode-session-ui", - "version": "1.18.20", + "version": "1.18.21", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz", @@ -967,7 +966,7 @@ }, "packages/tui": { "name": "@reddb-io/redcode-tui", - "version": "1.18.20", + "version": "1.19.0", "dependencies": { "@opentui/core": "catalog:", "@opentui/keymap": "catalog:", @@ -4123,8 +4122,6 @@ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], - "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], - "i18n-iso-countries": ["i18n-iso-countries@7.14.0", "", { "dependencies": { "diacritics": "1.3.0" } }, "sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg=="], "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], diff --git a/package.json b/package.json index e460738d69ea..4dd4b714d1a2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "typecheck": "bun turbo typecheck", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "postinstall": "bun run --cwd packages/core fix-node-pty", - "prepare": "husky", "random": "echo 'Random script'", "sso": "aws sso login --sso-session=opencode --no-browser", "translate:app": "bun run script/translate-app.ts", @@ -105,7 +104,6 @@ "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", "glob": "13.0.5", - "husky": "9.1.7", "oxlint": "1.60.0", "oxlint-tsgolint": "0.21.0", "prettier": "3.6.2", diff --git a/script/typecheck-shard.ts b/script/typecheck-shard.ts new file mode 100644 index 000000000000..1350a2d67f06 --- /dev/null +++ b/script/typecheck-shard.ts @@ -0,0 +1,50 @@ +#!/usr/bin/env bun +/** + * Typecheck one slice of the workspace. + * + * Every package at once needs more memory than a 4-vCPU runner has, and the pre-push hook that + * used to do exactly that was killed mid-run often enough to lose a release. So CI runs the + * packages in a fixed number of shards, each its own job: `bun script/typecheck-shard.ts 2 5` + * runs the third of five. The assignment is by sorted package name, so it is stable across runs + * and a failure names the package, not the shard. + */ +import path from "path" + +const [indexArg, totalArg] = process.argv.slice(2) +const index = Number(indexArg) +const total = Number(totalArg) +if (!Number.isInteger(index) || !Number.isInteger(total) || total < 1 || index < 0 || index >= total) { + console.error("usage: bun script/typecheck-shard.ts ") + process.exit(2) +} + +const root = path.resolve(import.meta.dir, "..") +const manifest = (await Bun.file(path.join(root, "package.json")).json()) as { + workspaces: string[] | { packages: string[] } +} +const patterns = Array.isArray(manifest.workspaces) ? manifest.workspaces : manifest.workspaces.packages + +// A Set, because the workspace globs overlap (`packages/*` and `packages/slack`). +const packages = new Set() +for (const pattern of patterns) { + const glob = new Bun.Glob(path.posix.join(pattern, "package.json")) + for await (const file of glob.scan({ cwd: root, dot: false })) { + if (file.includes("node_modules/")) continue + const json = (await Bun.file(path.join(root, file)).json()) as { name?: string; scripts?: Record } + if (json.name && json.scripts?.typecheck) packages.add(json.name) + } +} +const sorted = [...packages].sort() + +const mine = sorted.filter((_, i) => i % total === index) +if (mine.length === 0) { + console.log(`shard ${index + 1}/${total}: nothing to check`) + process.exit(0) +} +console.log(`shard ${index + 1}/${total}: ${mine.join(", ")}`) + +const proc = Bun.spawn(["bun", "turbo", "typecheck", ...mine.flatMap((name) => ["--filter", name])], { + cwd: root, + stdio: ["inherit", "inherit", "inherit"], +}) +process.exit(await proc.exited)