From 72e6a5c67f00463a263e7a1cd5bf9006ca177eb3 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:10:11 +0530 Subject: [PATCH 1/8] Publish to npm via trusted publishing Adds a workflow_dispatch publish workflow so releases no longer need the npm token: after npm version + git push --follow-tags, run the workflow with the version. It checks out v, verifies the package name, version, master ancestry for latest and that the version is new, then publishes via npm OIDC. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..09af673 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,65 @@ +name: publish + +on: + workflow_dispatch: + inputs: + version: + description: Version to publish; the v tag must already be pushed (npm version + git push --follow-tags) + required: true + type: string + tag: + description: npm dist-tag (latest for releases; prereleases must use another tag, e.g. next) + required: true + default: latest + type: string + +permissions: + contents: read + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Validate inputs + run: | + [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || { echo "::error::version $VERSION is not semver"; exit 1; } + [[ "$TAG" =~ ^[a-z][a-z0-9-]*$ ]] || { echo "::error::invalid dist-tag $TAG"; exit 1; } + if [ "$TAG" = latest ] && [[ "$VERSION" == *-* ]]; then + echo "::error::prerelease $VERSION cannot be published to latest" + exit 1 + fi + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: refs/tags/v${{ inputs.version }} + fetch-depth: 0 + persist-credentials: false + + # No registry-url: setup-node would write an .npmrc token line that shadows OIDC. + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 24 + + - run: npm install -g npm@11.21.0 + + - name: Verify package + run: | + name=$(node -p 'require("./package.json").name') + actual=$(node -p 'require("./package.json").version') + [ "$name" = browserstack-local ] || { echo "::error::package $name != browserstack-local"; exit 1; } + [ "$actual" = "$VERSION" ] || { echo "::error::package.json $actual != input $VERSION"; exit 1; } + if [ "$TAG" = latest ] && ! git merge-base --is-ancestor HEAD origin/master; then + echo "::error::v$VERSION is not on master" + exit 1 + fi + code=$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/browserstack-local/$VERSION") + [ "$code" = 404 ] || { echo "::error::browserstack-local@$VERSION already exists on npm (HTTP $code)"; exit 1; } + + - name: Publish + run: npm publish --tag "$TAG" --access public From 0348a857aa76cac150a8150b13168f0ec8e98559 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:41:45 +0530 Subject: [PATCH 2/8] Pick the release bump from a dropdown The workflow now bumps the version itself (patch/minor/major, or a prerelease to the next dist-tag) from the higher of npm latest and master, publishes, pushes the v tag and pushes the version bump to a release-v- branch for a PR to master. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 92 +++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 09af673..ab35dbe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,41 +3,41 @@ name: publish on: workflow_dispatch: inputs: - version: - description: Version to publish; the v tag must already be pushed (npm version + git push --follow-tags) + bump: + description: Version bump from the latest published version (prerelease publishes -tp.N to the next dist-tag, without a bump PR) required: true - type: string - tag: - description: npm dist-tag (latest for releases; prereleases must use another tag, e.g. next) - required: true - default: latest - type: string + default: patch + type: choice + options: + - patch + - minor + - major + - prerelease permissions: contents: read +concurrency: + group: publish + cancel-in-progress: false + jobs: publish: runs-on: ubuntu-latest permissions: - contents: read + contents: write id-token: write env: - VERSION: ${{ inputs.version }} - TAG: ${{ inputs.tag }} + BUMP: ${{ inputs.bump }} steps: - - name: Validate inputs + - name: Require master + if: github.ref != 'refs/heads/master' run: | - [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || { echo "::error::version $VERSION is not semver"; exit 1; } - [[ "$TAG" =~ ^[a-z][a-z0-9-]*$ ]] || { echo "::error::invalid dist-tag $TAG"; exit 1; } - if [ "$TAG" = latest ] && [[ "$VERSION" == *-* ]]; then - echo "::error::prerelease $VERSION cannot be published to latest" - exit 1 - fi + echo "::error::run this workflow from master" + exit 1 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: refs/tags/v${{ inputs.version }} fetch-depth: 0 persist-credentials: false @@ -48,18 +48,56 @@ jobs: - run: npm install -g npm@11.21.0 - - name: Verify package + - name: Set version run: | name=$(node -p 'require("./package.json").name') - actual=$(node -p 'require("./package.json").version') [ "$name" = browserstack-local ] || { echo "::error::package $name != browserstack-local"; exit 1; } - [ "$actual" = "$VERSION" ] || { echo "::error::package.json $actual != input $VERSION"; exit 1; } - if [ "$TAG" = latest ] && ! git merge-base --is-ancestor HEAD origin/master; then - echo "::error::v$VERSION is not on master" + # Start from whichever is higher: npm latest or master, since master can lag until the bump PR merges. + published=$(npm view browserstack-local dist-tags.latest) + local_version=$(node -p 'require("./package.json").version') + base=$(node -e 'const p=v=>v.split("-")[0].split(".").map(Number),[a,b]=process.argv.slice(1),x=p(a),y=p(b);for(let i=0;i<3;i++){if(x[i]!==y[i]){console.log(x[i]>y[i]?a:b);process.exit()}}console.log(a)' "$published" "$local_version") + npm version "$base" --no-git-tag-version --allow-same-version > /dev/null + if [ "$BUMP" = prerelease ]; then + npm version prerelease --preid tp --no-git-tag-version > /dev/null + # Skip -tp.N numbers that are already on npm. + while [ "$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/browserstack-local/$(node -p 'require("./package.json").version')")" != 404 ]; do + npm version prerelease --preid tp --no-git-tag-version > /dev/null + done + echo "DIST_TAG=next" >> "$GITHUB_ENV" + else + npm version "$BUMP" --no-git-tag-version > /dev/null + echo "DIST_TAG=latest" >> "$GITHUB_ENV" + fi + version=$(node -p 'require("./package.json").version') + code=$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/browserstack-local/$version") + [ "$code" = 404 ] || { echo "::error::browserstack-local@$version already exists on npm (HTTP $code)"; exit 1; } + if git ls-remote --exit-code --tags origin "refs/tags/v$version" > /dev/null; then + echo "::error::tag v$version already exists" exit 1 fi - code=$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/browserstack-local/$VERSION") - [ "$code" = 404 ] || { echo "::error::browserstack-local@$VERSION already exists on npm (HTTP $code)"; exit 1; } + echo "VERSION=$version" >> "$GITHUB_ENV" + echo "Publishing $version (base $base: npm latest $published, master $local_version) to dist-tag $([ "$BUMP" = prerelease ] && echo next || echo latest)" + + - name: Commit and tag + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -qam "$VERSION" + git tag -a "v$VERSION" -m "$VERSION" - name: Publish - run: npm publish --tag "$TAG" --access public + run: npm publish --tag "$DIST_TAG" --access public + + - name: Push tag + env: + GH_TOKEN: ${{ github.token }} + run: git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/v$VERSION" + + - name: Push version bump branch + if: env.DIST_TAG == 'latest' + env: + GH_TOKEN: ${{ github.token }} + run: | + branch="release-v$VERSION-$(date +%s)" + git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/$branch" + echo "Open the version bump PR: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/master...$branch?expand=1" | tee -a "$GITHUB_STEP_SUMMARY" From b3bbb5b402f5ec3f4a0b48a3a865f6978962e988 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:46:51 +0530 Subject: [PATCH 3/8] Simplify publish workflow to the existing release steps npm version , npm publish --access public, then push the tag and the version bump branch. Drops the extra version checks and the prerelease option. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 68 ++++++----------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ab35dbe..e2b3b11 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: bump: - description: Version bump from the latest published version (prerelease publishes -tp.N to the next dist-tag, without a bump PR) + description: Version bump required: true default: patch type: choice @@ -12,17 +12,13 @@ on: - patch - minor - major - - prerelease permissions: contents: read -concurrency: - group: publish - cancel-in-progress: false - jobs: publish: + if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest permissions: contents: write @@ -30,16 +26,9 @@ jobs: env: BUMP: ${{ inputs.bump }} steps: - - name: Require master - if: github.ref != 'refs/heads/master' - run: | - echo "::error::run this workflow from master" - exit 1 - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - persist-credentials: false # No registry-url: setup-node would write an .npmrc token line that shadows OIDC. - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -48,56 +37,19 @@ jobs: - run: npm install -g npm@11.21.0 - - name: Set version - run: | - name=$(node -p 'require("./package.json").name') - [ "$name" = browserstack-local ] || { echo "::error::package $name != browserstack-local"; exit 1; } - # Start from whichever is higher: npm latest or master, since master can lag until the bump PR merges. - published=$(npm view browserstack-local dist-tags.latest) - local_version=$(node -p 'require("./package.json").version') - base=$(node -e 'const p=v=>v.split("-")[0].split(".").map(Number),[a,b]=process.argv.slice(1),x=p(a),y=p(b);for(let i=0;i<3;i++){if(x[i]!==y[i]){console.log(x[i]>y[i]?a:b);process.exit()}}console.log(a)' "$published" "$local_version") - npm version "$base" --no-git-tag-version --allow-same-version > /dev/null - if [ "$BUMP" = prerelease ]; then - npm version prerelease --preid tp --no-git-tag-version > /dev/null - # Skip -tp.N numbers that are already on npm. - while [ "$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/browserstack-local/$(node -p 'require("./package.json").version')")" != 404 ]; do - npm version prerelease --preid tp --no-git-tag-version > /dev/null - done - echo "DIST_TAG=next" >> "$GITHUB_ENV" - else - npm version "$BUMP" --no-git-tag-version > /dev/null - echo "DIST_TAG=latest" >> "$GITHUB_ENV" - fi - version=$(node -p 'require("./package.json").version') - code=$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/browserstack-local/$version") - [ "$code" = 404 ] || { echo "::error::browserstack-local@$version already exists on npm (HTTP $code)"; exit 1; } - if git ls-remote --exit-code --tags origin "refs/tags/v$version" > /dev/null; then - echo "::error::tag v$version already exists" - exit 1 - fi - echo "VERSION=$version" >> "$GITHUB_ENV" - echo "Publishing $version (base $base: npm latest $published, master $local_version) to dist-tag $([ "$BUMP" = prerelease ] && echo next || echo latest)" - - - name: Commit and tag + - name: Upgrade version run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git commit -qam "$VERSION" - git tag -a "v$VERSION" -m "$VERSION" + npm version "$BUMP" - name: Publish - run: npm publish --tag "$DIST_TAG" --access public + run: npm publish --access public - - name: Push tag - env: - GH_TOKEN: ${{ github.token }} - run: git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/v$VERSION" - - - name: Push version bump branch - if: env.DIST_TAG == 'latest' - env: - GH_TOKEN: ${{ github.token }} + - name: Push tag and version bump branch run: | - branch="release-v$VERSION-$(date +%s)" - git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/$branch" + version=$(node -p 'require("./package.json").version') + branch="release-v$version-$(date +%s)" + git push origin "v$version" + git push origin "HEAD:refs/heads/$branch" echo "Open the version bump PR: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/master...$branch?expand=1" | tee -a "$GITHUB_STEP_SUMMARY" From 48900df1c26eac8e4fc97f87fba167313a8280e2 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:50:05 +0530 Subject: [PATCH 4/8] Push the bump branch and tag with git push --follow-tags Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e2b3b11..ae107c5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -50,6 +50,5 @@ jobs: run: | version=$(node -p 'require("./package.json").version') branch="release-v$version-$(date +%s)" - git push origin "v$version" - git push origin "HEAD:refs/heads/$branch" + git push --follow-tags origin "HEAD:refs/heads/$branch" echo "Open the version bump PR: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/master...$branch?expand=1" | tee -a "$GITHUB_STEP_SUMMARY" From 54258b93e42908b591d0627afc02ea95e5f581de Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:51:56 +0530 Subject: [PATCH 5/8] Name the version bump branch release- Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ae107c5..442c034 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -48,7 +48,6 @@ jobs: - name: Push tag and version bump branch run: | - version=$(node -p 'require("./package.json").version') - branch="release-v$version-$(date +%s)" + branch="release-$(date +%s)" git push --follow-tags origin "HEAD:refs/heads/$branch" echo "Open the version bump PR: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/master...$branch?expand=1" | tee -a "$GITHUB_STEP_SUMMARY" From 10ece45640981d0feb391ec844fad91d988a0bf1 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:54:56 +0530 Subject: [PATCH 6/8] Add a prerelease option for test releases prerelease publishes -tp.0 to the next dist-tag and pushes only its tag, so the setup can be checked without moving latest. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 442c034..5b42d18 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: bump: - description: Version bump + description: Version bump (prerelease publishes -tp.0 to the next dist-tag for testing) required: true default: patch type: choice @@ -12,6 +12,7 @@ on: - patch - minor - major + - prerelease permissions: contents: read @@ -41,12 +42,17 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - npm version "$BUMP" + npm version "$BUMP" --preid tp - name: Publish - run: npm publish --access public + run: npm publish --access public --tag "$([ "$BUMP" = prerelease ] && echo next || echo latest)" + + - name: Push tag + if: inputs.bump == 'prerelease' + run: git push origin "$(git describe --exact-match)" - name: Push tag and version bump branch + if: inputs.bump != 'prerelease' run: | branch="release-$(date +%s)" git push --follow-tags origin "HEAD:refs/heads/$branch" From 23658c8156c2912f1b6c0a24960d1a6b1925fc93 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:56:12 +0530 Subject: [PATCH 7/8] Label the release input Version Type Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5b42d18..d8c93db 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,8 +3,8 @@ name: publish on: workflow_dispatch: inputs: - bump: - description: Version bump (prerelease publishes -tp.0 to the next dist-tag for testing) + version_type: + description: Version Type required: true default: patch type: choice @@ -25,7 +25,7 @@ jobs: contents: write id-token: write env: - BUMP: ${{ inputs.bump }} + VERSION_TYPE: ${{ inputs.version_type }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -42,17 +42,17 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - npm version "$BUMP" --preid tp + npm version "$VERSION_TYPE" --preid tp - name: Publish - run: npm publish --access public --tag "$([ "$BUMP" = prerelease ] && echo next || echo latest)" + run: npm publish --access public --tag "$([ "$VERSION_TYPE" = prerelease ] && echo next || echo latest)" - name: Push tag - if: inputs.bump == 'prerelease' + if: inputs.version_type == 'prerelease' run: git push origin "$(git describe --exact-match)" - name: Push tag and version bump branch - if: inputs.bump != 'prerelease' + if: inputs.version_type != 'prerelease' run: | branch="release-$(date +%s)" git push --follow-tags origin "HEAD:refs/heads/$branch" From 79981cf3e0fe208d75ef0b70491d1ac35737d8b4 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 5 Nov 2026 22:57:14 +0530 Subject: [PATCH 8/8] Name the workflow Local Binding Publish Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d8c93db..789323d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: publish +name: Local Binding Publish on: workflow_dispatch: