diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml new file mode 100644 index 0000000..9b9bf0b --- /dev/null +++ b/.github/actions/build-test/action.yml @@ -0,0 +1,42 @@ +name: "Build and Test" +description: "Install deps, build and test the Chat SDK (shared by CI and publish workflows)" +inputs: + node-version: + description: "Node.js version" + required: false + default: "22.14.0" # pinned >= the OIDC minimum (Node 22.14); major-only "22" can resolve below it + run-checks: + description: "Run lint + format checks before build+test (enable for PR CI)" + required: false + default: "false" +runs: + using: "composite" + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + registry-url: "https://registry.npmjs.org" # writes .npmrc; harmless on PR, used by OIDC on publish + cache: "npm" + - name: Upgrade npm (trusted publishing needs >= 11.5.1) + shell: bash + # Pinned for deterministic builds; bump intentionally (must stay >= 11.5.1 for OIDC). + run: npm install -g npm@11.5.1 + - name: Install dependencies + shell: bash + run: npm ci + # Run lint + format as non-mutating gates BEFORE build/test, so build/test exercise the actual + # committed code rather than code an `eslint --fix` / `prettier --write` may have rewritten + # afterwards. prettier:check is scoped to first-party src/tests (a repo-wide check fails on + # pre-existing unrelated files such as the semantic-release-managed CHANGELOG.md). + - name: Lint and check formatting + if: ${{ inputs.run-checks == 'true' }} + shell: bash + run: | + npm run lint + npm run prettier:check + - name: Build + shell: bash + run: npm run build + - name: Test + shell: bash + run: npm run test diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 09b36f5..0b7856f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,23 +1,10 @@ -name: Call reusable workflow - Chat SDK +name: CI - Chat SDK on: - push: - branches: - - main pull_request: branches: - main jobs: - call-reusable-workflow-PR: - uses: ./.github/workflows/ci_reusable.yaml - if: github.event_name == 'pull_request' - with: - PUBLISH_NPM: false - secrets: inherit - call-reusable-workflow-testing: + build-test: uses: ./.github/workflows/ci_reusable.yaml - if: github.event_name == 'push' - with: - PUBLISH_NPM: true - secrets: inherit diff --git a/.github/workflows/ci_reusable.yaml b/.github/workflows/ci_reusable.yaml index 6654dc4..522d221 100644 --- a/.github/workflows/ci_reusable.yaml +++ b/.github/workflows/ci_reusable.yaml @@ -1,60 +1,14 @@ -name: Build, Test and Publish - Chat SDK +name: Build and Test - Chat SDK on: workflow_call: - inputs: - PUBLISH_NPM: - required: true - type: boolean - secrets: - NPM_TOKEN: - required: true jobs: - build-test-publish: - name: Build, Test and Publish + build-test: + name: Build and Test runs-on: ubuntu-latest - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - PUBLISH_NPM: ${{ inputs.PUBLISH_NPM }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # defaults to github.sha (the PR merge commit); works in workflow_call and for fork PRs + - uses: ./.github/actions/build-test # same shared composite as publish.yaml with: - ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/setup-node@v3 - with: - node-version: "20" - registry-url: "https://registry.npmjs.org" - cache: "npm" - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ~/.npm - key: npm-${{ hashFiles('package-lock.json') }} - restore-keys: npm- - - run: npm ci - - run: npm run prettier - - run: npm run lint:fix - - run: npm run build - - run: npm run test - - name: Set github bot - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - - name: "Update to alpha version" - if: inputs.PUBLISH_NPM - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - git reset --hard - npm version prerelease --preid alpha - OLD_MSG=$(git log --format=%B -n1) - git commit --amend -m "$OLD_MSG" -m "[skip ci]" - git push - git push --tags - - name: "Publish to npm" - if: inputs.PUBLISH_NPM - run: | - npm publish --tag alpha - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run-checks: "true" # lint runs (non-mutating) before build+test on PRs; skipped on publish diff --git a/.github/workflows/publish-latest.yaml b/.github/workflows/publish-latest.yaml deleted file mode 100644 index ff92517..0000000 --- a/.github/workflows/publish-latest.yaml +++ /dev/null @@ -1,59 +0,0 @@ -name: Publish latest - -on: - workflow_dispatch: - -permissions: - contents: write # for GitHub releases - issues: write # for release comments - pull-requests: write # for PR comments - id-token: write # for npm provenance if needed - -jobs: - release: - name: Build and release latest - if: ${{ github.ref == 'refs/heads/main' }} - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - steps: - # 1. Checkout latest main - - uses: actions/checkout@v3 - with: - persist-credentials: false - fetch-depth: 0 - - - run: | - git fetch origin main - git checkout main - git reset --hard origin/main - - # 2. Setup Node and npm auth - - uses: actions/setup-node@v3 - with: - node-version: "20" - registry-url: "https://registry.npmjs.org" - cache: "npm" - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - # 3. Install dependencies and build - - run: npm ci - - run: npm run build - - - name: Semantic Release Publish - uses: cycjimmy/semantic-release-action@v4 - with: - semantic_version: 24 - extra_plugins: | - @semantic-release/changelog@6 - @semantic-release/git@10 - branches: | - [ - 'main' - ] - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..122f9a9 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,75 @@ +name: Publish - Chat SDK + +on: + push: + branches: + - main + workflow_dispatch: + +# Serialize publish runs: this job bumps + pushes main and publishes to npm, so overlapping runs +# (back-to-back pushes, or a manual dispatch during a push publish) would race on the main push and +# the prerelease sequence. Never cancel a run mid-publish. +concurrency: + group: publish-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write # push alpha version commit/tag + create GitHub release + id-token: write # OIDC trusted publishing + provenance + issues: write # semantic-release release comments + pull-requests: write # semantic-release release comments + +jobs: + publish: + name: Build, Test and Publish + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} # tie the run to the triggering commit, not the latest main + fetch-depth: 0 # semantic-release needs full history + # persist-credentials defaults true -> alpha `git push` uses GITHUB_TOKEN + - name: Create local main branch at the triggering commit + run: | + git checkout -B main # non-detached HEAD (needed for the alpha git push) tied to github.sha + # -B leaves the branch untracked; set its upstream to origin/main so semantic-release's + # own `git push` (workflow_dispatch / latest path) and any bare `git push` resolve a target. + git config branch.main.remote origin + git config branch.main.merge refs/heads/main + - uses: ./.github/actions/build-test # shared composite: setup-node + npm upgrade + ci + build + test + # Needed by both paths: the alpha bump commit and semantic-release's @semantic-release/git commits. + - name: Configure git author + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + + # ---- alpha: every push to main ---- + - name: Publish alpha to npm + if: github.event_name == 'push' + run: | + git reset --hard + # Bake [skip ci] into the commit message so the version bump commit+tag are created in + # one step (no --amend, which would leave the tag pointing at the pre-amend commit) and + # the pushed commit to main does not re-trigger this workflow. + npm version prerelease --preid alpha -m "%s [skip ci]" + git push --follow-tags origin main # push the bump commit + its tag explicitly (upstream is set above) + npm publish --tag alpha # tokenless OIDC, provenance automatic + + # ---- latest: on demand (workflow_dispatch) ---- + - name: Publish latest + GitHub release (semantic-release) + if: github.event_name == 'workflow_dispatch' + uses: cycjimmy/semantic-release-action@v4 + with: + semantic_version: 25 # bundles @semantic-release/npm >= 13.1 (OIDC) + extra_plugins: | + @semantic-release/changelog@6 + @semantic-release/git@10 + branches: | + [ + 'main' + ] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # no NPM_TOKEN — OIDC handles npm auth diff --git a/package.json b/package.json index 9d7d690..d9360ed 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "lint": "eslint --ignore-path .gitignore --ext .js,.ts . --config .eslintrc.tests.cjs", "lint:fix": "npm run lint -- --fix", "prettier": "prettier --write .", + "prettier:check": "prettier --check \"src/**/*\" \"tests/**/*\"", "test": "run-p test:browser test:node", "test:browser": "vitest --config vite.config.mts", "test:node": "vitest --config vite.config.node.mts", @@ -119,7 +120,7 @@ "vitest": "^3.1.1" }, "volta": { - "node": "20.19.0", - "npm": "8.11.0" + "node": "22.14.0", + "npm": "11.5.1" } }