diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ba21a9f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +# CI for the action itself: unit tests for the components, and a check that the +# committed bundle (dist/index.js, which action.yml runs verbatim) is fresh and +# loads under Node. +name: ci +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: 24 + - name: Install bit + run: | + npx --yes @teambit/bvm install 2.2.115 + echo "$HOME/bin" >> "$GITHUB_PATH" + - name: Install dependencies + run: bit install + - name: Test + run: bit test + - name: Verify the committed bundle + run: node scripts/verify-bundle.mjs diff --git a/action.yml b/action.yml index 90775a0..958be29 100644 --- a/action.yml +++ b/action.yml @@ -19,7 +19,7 @@ inputs: required: false default: bit-sync/main runs: - using: node20 + using: node24 # Committed esbuild bundle -- see scripts/bundle-action.mjs. Must be ESM # (the root package.json is `"type": "module"`); `npm run verify-bundle` # asserts it is both fresh and loadable. diff --git a/dist/index.js b/dist/index.js index 37d7b39..976150c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -45,7 +45,7 @@ function scopesInComponentIds(componentIds) { function isLaneRelevantToScope(componentIds, repoScope) { if (!componentIds || !repoScope) return true; - const scopes = scopesInComponentIds(componentIds); + const scopes = scopesInComponentIds(Array.isArray(componentIds) ? componentIds.join(",") : String(componentIds)); if (scopes.length === 0) return true; return scopes.includes(repoScope); diff --git a/git-sync/git-sync/event-router/event-router.spec.ts b/git-sync/git-sync/event-router/event-router.spec.ts index 7313215..7b0d699 100644 --- a/git-sync/git-sync/event-router/event-router.spec.ts +++ b/git-sync/git-sync/event-router/event-router.spec.ts @@ -72,6 +72,33 @@ describe('routeEvent', () => { }); }); + it('accepts componentIds as an array (API-created webhooks, hand-rolled dispatches)', () => { + expect( + routeEvent({ + ...base, + eventName: 'repository_dispatch', + dispatchType: 'bit-export', + repoScope: 'acme.cards', + clientPayload: { + laneId: 'acme.cards/cross-scope', + componentIds: ['acme.shop/bar@2.0.0', 'acme.cards/foo@1.0.0'], + }, + }) + ).toEqual({ kind: 'run', args: ['sync', 'acme.cards/cross-scope'] }); + }); + + it('treats an empty componentIds array as absent (fails open)', () => { + expect( + routeEvent({ + ...base, + eventName: 'repository_dispatch', + dispatchType: 'bit-export', + repoScope: 'acme.cards', + clientPayload: { laneId: '', componentIds: [] }, + }) + ).toEqual({ kind: 'run', args: ['sync', '--main'] }); + }); + it('fails open (routes) when componentIds is absent, even with repoScope set', () => { expect( routeEvent({ diff --git a/git-sync/git-sync/event-router/event-router.ts b/git-sync/git-sync/event-router/event-router.ts index 1a3b3ac..04949ac 100644 --- a/git-sync/git-sync/event-router/event-router.ts +++ b/git-sync/git-sync/event-router/event-router.ts @@ -28,9 +28,10 @@ export type RouterInput = { * cross-scope-lane relevance filter (see `repoScope` below) to decide * whether this repo has any stake in a lane export before routing it. * Absent/empty is NOT a signal of anything — it just means the filter - * can't run, so it fails open (see `isLaneRelevantToScope`). + * can't run, so it fails open (see `isLaneRelevantToScope`). A webhook + * created through the API, or a hand-rolled dispatch, may send an array. */ - componentIds?: string; + componentIds?: string | string[]; }; /** push: refs/heads/. */ ref?: string; @@ -179,9 +180,9 @@ function scopesInComponentIds(componentIds: string): string[] { * whose content isn't entirely inside `defaultScope`, so failing open here * only means the halt happens in `bit ci` instead of being pre-empted here. */ -function isLaneRelevantToScope(componentIds: string | undefined, repoScope: string | undefined): boolean { +function isLaneRelevantToScope(componentIds: string | string[] | undefined, repoScope: string | undefined): boolean { if (!componentIds || !repoScope) return true; - const scopes = scopesInComponentIds(componentIds); + const scopes = scopesInComponentIds(Array.isArray(componentIds) ? componentIds.join(',') : String(componentIds)); if (scopes.length === 0) return true; return scopes.includes(repoScope); } diff --git a/scripts/bundle-action.mjs b/scripts/bundle-action.mjs index 12a312e..74f85a7 100644 --- a/scripts/bundle-action.mjs +++ b/scripts/bundle-action.mjs @@ -27,7 +27,7 @@ export const bundleOptions = { entryPoints: [join(repoRoot, 'git-sync/git-sync/action/run.ts')], bundle: true, platform: 'node', - target: 'node20', + target: 'node24', format: 'esm', absWorkingDir: repoRoot, };