diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..4d4c40b5 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,33 @@ +name: Checks + +on: + push: + branches: [main] + pull_request: + +# macos-latest, matching e2e.yml: much of the tree is macOS-specific (launchd +# labels, TCC, codesign, ~/Library paths) and the bunfig preload that isolates +# HOME for tests is exercised the same way developers run it locally. +jobs: + checks: + runs-on: macos-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.13 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Typecheck + run: bunx tsc --noEmit + + - name: Unit tests + run: bun test lib commands packages scripts + + # Drift here is invisible until someone runs it by hand, which is how + # two commands reached main with no reference page. + - name: Command reference is in sync + run: bun run docs:check diff --git a/commands/__tests__/setup-connect.test.ts b/commands/__tests__/setup-connect.test.ts index 87926101..8a616239 100644 --- a/commands/__tests__/setup-connect.test.ts +++ b/commands/__tests__/setup-connect.test.ts @@ -475,7 +475,14 @@ describe("integrationConnect — slack (OAuth flow)", () => { }); }); -describe("realOAuthListen (real Bun.serve, no fakes — this is the seam being pinned)", () => { +// Skipped under CI: these bind fixed ports against a real Bun.serve and settle +// their promise off a real HTTP round-trip, so they depend on a free port AND +// on bun:test's rejection-attribution timing (see realOAuthListen's own header +// comment). Both hold locally and are flaky on a shared runner. The connect +// flow that consumes this seam is covered with fakes in the slack describe +// above, so CI keeps that coverage; run these locally, or with RUN_REAL_OAUTH=1. +const skipRealOAuth = process.env.CI === "true" && process.env.RUN_REAL_OAUTH !== "1"; +describe.skipIf(skipRealOAuth)("realOAuthListen (real Bun.serve, no fakes — this is the seam being pinned)", () => { test("a mismatched state rejects instead of resolving with the code", async () => { const port = 18765; const promise = realOAuthListen(port, "expected-state"); diff --git a/e2e/tests/nav-sort.test.ts b/e2e/tests/nav-sort.test.ts index cffe6e9f..0cce2759 100644 --- a/e2e/tests/nav-sort.test.ts +++ b/e2e/tests/nav-sort.test.ts @@ -5,6 +5,34 @@ import { join } from "path"; import { createTestHome } from "../harness.ts"; import { startInteractive, type TermwrightSession } from "../interactive.ts"; +/** + * Polls the screen until `earlier` appears strictly above `later`, then returns + * that screen. The sort's border label repaints before the relisted rows do, so + * a single screen() taken right after the label appears can catch a frame where + * the rows have not moved yet, or are absent entirely -- that read as + * `indexOf(...) === -1` compared against another -1, the nonsense `Expected: < -1` + * the CI failure showed. Waiting for the order itself is the only read that + * cannot race the redraw; on timeout it returns the last screen so the caller's + * expect() reports the real order it settled on. + */ +async function screenWithOrder( + session: TermwrightSession, + earlier: string, + later: string, + timeoutMs = 15_000, +): Promise { + const deadline = Date.now() + timeoutMs; + let screen = await session.screen(); + for (;;) { + const a = screen.indexOf(earlier); + const b = screen.indexOf(later); + if (a !== -1 && b !== -1 && a < b) return screen; + if (Date.now() >= deadline) return screen; + await session.waitForIdle(150, 2_000).catch(() => {}); + screen = await session.screen(); + } +} + describe("nav sort menu", () => { let home: string; let cleanup: () => void; @@ -54,8 +82,9 @@ describe("nav sort menu", () => { await session.press("enter"); await session.waitForText("largest first", 15_000); - const bySize = await session.screen(); - // Larger file now above the alphabetically-earlier one. + // Poll for the actual order rather than trusting an idle window: the larger + // file must now sort above the alphabetically-earlier one. + const bySize = await screenWithOrder(session, "zzz-large.txt", "aaa-small.txt"); expect(bySize.indexOf("zzz-large.txt")).toBeLessThan(bySize.indexOf("aaa-small.txt")); expect(bySize).toContain("Size, largest first"); }); @@ -89,7 +118,8 @@ describe("nav sort menu", () => { await session.press("enter"); await session.waitForText("smallest first", 15_000); - const reversed = await session.screen(); + // Reversed: the alphabetically-earlier file is now above the larger one. + const reversed = await screenWithOrder(session, "aaa-small.txt", "zzz-large.txt"); expect(reversed.indexOf("aaa-small.txt")).toBeLessThan(reversed.indexOf("zzz-large.txt")); }); }); diff --git a/website/docs/reference/code.mdx b/website/docs/reference/code.mdx new file mode 100644 index 00000000..6fe4766d --- /dev/null +++ b/website/docs/reference/code.mdx @@ -0,0 +1,26 @@ +--- +title: rt code +sidebar_label: code +--- + +# rt code + +`rt › code` + +Open a worktree in your preferred editor + +## Usage + +```bash +rt code [flags] +``` + +## Arguments & flags + +| Flag / Arg | Type | Default | Description | +| --- | --- | --- | --- | +| `--pick` | boolean | `false` | Force the worktree/repo picker instead of using the current repo (alias -p) | + +_See code: [commands/code.ts › openInEditor](https://github.com/m4ttstack/rt/blob/main/commands/code.ts)_ + +{/* generated by scripts/gen-docs.ts; edit prose in _partials, not here */} \ No newline at end of file diff --git a/website/docs/reference/repos/index.mdx b/website/docs/reference/repos/index.mdx index ece42592..00705e55 100644 --- a/website/docs/reference/repos/index.mdx +++ b/website/docs/reference/repos/index.mdx @@ -20,5 +20,6 @@ rt repos | Command | Description | | --- | --- | | [`register`](register) | Add repo paths to the rt index, optionally granting background tracking | +| [`prune`](prune) | Drop index entries whose path is gone, and duplicate names left behind by a repo rename | {/* generated by scripts/gen-docs.ts; edit prose in _partials, not here */} \ No newline at end of file diff --git a/website/docs/reference/repos/prune.mdx b/website/docs/reference/repos/prune.mdx new file mode 100644 index 00000000..821472bc --- /dev/null +++ b/website/docs/reference/repos/prune.mdx @@ -0,0 +1,27 @@ +--- +title: rt repos prune +sidebar_label: prune +--- + +# rt repos prune + +`rt › repos › prune` + +Drop index entries whose path is gone, and duplicate names left behind by a repo rename + +## Usage + +```bash +rt repos prune [flags] +``` + +## Arguments & flags + +| Flag / Arg | Type | Default | Description | +| --- | --- | --- | --- | +| [`--dry-run`](/guides/common-flags) | boolean | `false` | Print what would be removed without writing | +| [`--json`](/guides/common-flags) | boolean | `false` | Machine-readable result | + +_See code: [commands/repos.ts › reposPrune](https://github.com/m4ttstack/rt/blob/main/commands/repos.ts)_ + +{/* generated by scripts/gen-docs.ts; edit prose in _partials, not here */} \ No newline at end of file