diff --git a/.github/workflows/ci-diagnose.yml b/.github/workflows/ci-diagnose.yml new file mode 100644 index 00000000..8cdbae1a --- /dev/null +++ b/.github/workflows/ci-diagnose.yml @@ -0,0 +1,93 @@ +name: CI Failure Diagnosis + +on: + workflow_run: + workflows: ['CI'] + types: [completed] + +permissions: + actions: read + contents: read + issues: write + pull-requests: write + id-token: write + +concurrency: + group: ci-diagnosis-${{ github.event.workflow_run.id }} + cancel-in-progress: true + +jobs: + diagnose: + if: github.event.workflow_run.conclusion == 'failure' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 1 + + - name: Extract run context + id: context + run: | + pr_number="$(jq -r '.workflow_run.pull_requests[0].number // empty' "$GITHUB_EVENT_PATH")" + echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" + + - name: Skip non-PR runs + if: steps.context.outputs.pr_number == '' + run: echo "Failed CI run is not attached to a pull request. Skipping PR diagnosis comment." + + - name: Run Claude CI diagnostician + if: steps.context.outputs.pr_number != '' + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + claude_args: | + --model opus + --max-turns 18 + --allowedTools "Bash(gh:*)" "WebSearch" "WebFetch" + prompt: | + You are the CI failure diagnosis agent for this repository. + + Failed run context: + - Run ID: ${{ github.event.workflow_run.id }} + - Run URL: ${{ github.event.workflow_run.html_url }} + - Workflow: ${{ github.event.workflow_run.name }} + - Conclusion: ${{ github.event.workflow_run.conclusion }} + - Head SHA: ${{ github.event.workflow_run.head_sha }} + - PR Number: #${{ steps.context.outputs.pr_number }} + - PR URL: https://github.com/${{ github.repository }}/pull/${{ steps.context.outputs.pr_number }} + + Tasks: + 1. Diagnose what failed by inspecting the failed workflow run and failed jobs using `gh` (jobs, steps, and logs). + 2. Focus issue types on: lint, typecheck, build, test, link. + 3. If there are multiple issues, include only actionable root causes (max 3 rows). Collapse duplicates. + 4. For each actionable issue, include: + - file (or nearest path/scope when file is unavailable), + - issue type, + - concise failure description. + 5. If the failure is only simple lint/style warnings, keep it brief and generic; do not enumerate every file. + 6. Avoid noise: + - Do not post redundant comments. + - Use one comment per CI run. + - If a comment already exists with marker ``, update it instead of creating a new one. + - Do not include stack traces or long command output in comments. + + Post or update a single PR comment on #${{ steps.context.outputs.pr_number }} using this template exactly: + + + ### CI Failure Diagnosis + - Run: ${{ github.event.workflow_run.html_url }} + - Commit: `${{ github.event.workflow_run.head_sha }}` + + | File | Type | What failed | + | --- | --- | --- | + | ... | lint\|typecheck\|build\|test\|link | ... | + + Rules for output: + - Keep the table concise and actionable. + - Keep "What failed" to one short sentence. + - If only simple lint/style noise exists, use one row: + `| (various) | lint | Simple lint/style warnings only. |` + - If failure cause is not determinable from logs, use one row with best evidence and next diagnostic step. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d195202..b81ac7cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,14 +2,34 @@ name: CI on: push: - branches-ignore: - - 'rfc/**' + branches: + - main pull_request: branches-ignore: - 'rfc/**' jobs: - build: + install: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: '.nvmrc' + cache: pnpm + + - name: Install dependencies (cache warm-up) + run: pnpm install --frozen-lockfile + + lint: + needs: install runs-on: ubuntu-latest steps: @@ -26,7 +46,99 @@ jobs: cache: pnpm - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Lint + run: pnpm lint + + typecheck: + needs: install + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: '.nvmrc' + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Build packages for type resolution + run: pnpm build:packages + + - name: Typecheck + run: pnpm typecheck + + test: + needs: install + name: Test (${{ matrix.package }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: + - '@videojs/core' + - '@videojs/store' + - '@videojs/utils' + - '@videojs/element' + - '@videojs/html' + - '@videojs/react' + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: '.nvmrc' + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Cache turbo test setup + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-test-${{ github.sha }}-${{ matrix.package }} + restore-keys: | + ${{ runner.os }}-turbo-test- + ${{ runner.os }}-turbo- + + - name: Test package + run: pnpm turbo run test --filter="${{ matrix.package }}" + + build: + needs: install + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: '.nvmrc' + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile --prefer-offline - name: Cache turbo build setup uses: actions/cache@v5 diff --git a/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts b/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts index d70352c3..b51f9297 100644 --- a/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts +++ b/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts @@ -42,14 +42,14 @@ describe('PiPButtonCore', () => { }); describe('getLabel', () => { - it('returns Enter PiP when not in PiP', () => { + it('returns Enter picture-in-picture when not in PiP', () => { const core = new PiPButtonCore(); - expect(core.getLabel(createState({ pip: false }))).toBe('Enter PiP'); + expect(core.getLabel(createState({ pip: false }))).toBe('Enter picture-in-picture'); }); - it('returns Exit PiP when in PiP', () => { + it('returns Exit picture-in-picture when in PiP', () => { const core = new PiPButtonCore(); - expect(core.getLabel(createState({ pip: true }))).toBe('Exit PiP'); + expect(core.getLabel(createState({ pip: true }))).toBe('Exit picture-in-picture'); }); it('returns custom string label', () => { @@ -69,7 +69,7 @@ describe('PiPButtonCore', () => { it('returns aria-label', () => { const core = new PiPButtonCore(); const attrs = core.getAttrs(createState()); - expect(attrs['aria-label']).toBe('Enter PiP'); + expect(attrs['aria-label']).toBe('Enter picture-in-picture'); }); it('sets aria-disabled when disabled', () => { diff --git a/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/html/src/player/player-controller.ts b/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/html/src/player/player-controller.ts index 748ec115..fb7fcc01 100644 --- a/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/html/src/player/player-controller.ts +++ b/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/html/src/player/player-controller.ts @@ -10,10 +10,8 @@ interface ReactiveController { /** Manages the video player lifecycle. */ export class PlayerController implements ReactiveController { - #host: ReactiveControllerHost; - constructor(host: ReactiveControllerHost) { - this.#host = host; + host.addController(this); } /** Whether the player is ready. */ diff --git a/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/core/selector.ts b/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/core/selector.ts index edc602f5..983b3982 100644 --- a/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/core/selector.ts +++ b/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/core/selector.ts @@ -1,7 +1,3 @@ -interface Store { - getState(): S; -} - type SelectorFn = (state: S) => R; /** Create a memoized selector function. */ diff --git a/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/html/controllers/snapshot-controller.ts b/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/html/controllers/snapshot-controller.ts index b9cd64f6..c1d5d26d 100644 --- a/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/html/controllers/snapshot-controller.ts +++ b/site/scripts/api-docs-builder/src/tests/fixtures/monorepo/packages/store/src/html/controllers/snapshot-controller.ts @@ -14,8 +14,6 @@ interface Store { /** Takes a snapshot of store state. */ export class SnapshotController implements ReactiveController { - #host: ReactiveControllerHost; - /** * @param host - The host element. * @param state - The store to snapshot. @@ -28,7 +26,7 @@ export class SnapshotController implements ReactiveController { */ constructor(host: ReactiveControllerHost, state: Store); constructor(host: ReactiveControllerHost, state: Store, selector?: (state: S) => R) { - this.#host = host; + host.addController(this); } /** The current snapshot value. */ diff --git a/tsconfig.json b/tsconfig.json index 7b7b98fc..43844387 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,8 +16,6 @@ { "path": "packages/core" }, { "path": "packages/core/src/dom" }, - { "path": "packages/icons" }, - { "path": "packages/html" }, { "path": "packages/react" } ],