From a2f54429de2836f7a78bcf24f7b75019f921ad93 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 12 Aug 2026 11:45:13 -0500 Subject: [PATCH 1/2] fix: create-release-from-tags should no-op, not throw, when nothing's new A release run (manual workflow_dispatch or a real push) can legitimately land on a commit with no new tags to process - e.g. dispatching manually against a commit that isn't a version bump, or re-running after everything's already published. `changeset publish` correctly creates no new tags in that case; create-release-from-tags shouldn't treat "nothing new" as a hard failure. --- scripts/create-release-from-tags/index.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/create-release-from-tags/index.ts b/scripts/create-release-from-tags/index.ts index 78a7578ac..bc8edcd70 100755 --- a/scripts/create-release-from-tags/index.ts +++ b/scripts/create-release-from-tags/index.ts @@ -40,9 +40,10 @@ export const getConfig = async (): Promise => { const isDryRun = Boolean(DRY_RUN) const tags = TAGS ? parseRawTags(TAGS) : await getCurrentGitTags() - if (!tags.length) { - throw new Error('No git tags found.') - } + // No tags at HEAD is expected, not exceptional: a manual release run with + // no pending changesets publishes nothing new, so `changeset publish` + // correctly creates no tags. Let createReleaseFromTags no-op on this + // rather than treating it as a hard failure. return { isDryRun, tags, @@ -256,6 +257,11 @@ const createGithubReleaseFromTag = async ( } export const createReleaseFromTags = async (config: Config) => { + if (!config.tags.length) { + console.log('No git tags at HEAD - nothing new to release. Skipping.') + return + } + console.log('Processing tags:', config.tags, '\n') for (const tag of config.tags) { From 33111f9b8bcf0f0c52a03288d5e0fc0918532fc1 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Wed, 12 Aug 2026 12:24:46 -0500 Subject: [PATCH 2/2] ci: build once, share the artifact between npm publish and CDN deploy publish and deploy-cdn each ran their own full build independently (yarn build --force inside yarn release, and yarn . build inside release:cdn) - double the build time, and no guarantee the two destinations ever got the exact same bits, since each build was a separate yarn install + compile on a separate runner. Adds a build job that runs once (after test passes), uploads packages/*/dist and packages/consent/*/dist as a single GitHub Actions artifact. publish and deploy-cdn now both depend only on build (not on each other, so they run in parallel) and download that same artifact instead of rebuilding: - release:publish-only (new script) is `release` minus the clean+build - just changeset publish, tag push, and CDN cache purge. - release:cdn:no-build (new script, packages/browser) is release:cdn minus the `yarn . build` prefix. Both now publish/deploy the identical, single build produced by the build job. --- .github/workflows/publish.yml | 55 ++++++++++++++++++++++++++++++++--- package.json | 1 + packages/browser/package.json | 1 + 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0908525c0..017476289 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -61,9 +61,40 @@ jobs: - run: yarn scripts lint - run: yarn scripts test + build: + name: Build release artifacts + needs: [should-release, test] + if: needs.should-release.outputs.release == 'true' + runs-on: ubuntu-latest-large + permissions: + contents: read + id-token: write # Artifactory OIDC + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + - name: Artifactory OIDC Auth + uses: ./.github/actions/artifactory-oidc + - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 + with: + node-version: 20 + - run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn install --immutable + - run: yarn build --force + # publish (npm) and deploy-cdn need the exact same build - built once + # here and shared via artifact, rather than each job rebuilding + # independently (which cost double the build time and risked the two + # destinations ending up with subtly different bits). + - name: Upload build artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: release-build-${{ github.run_id }} + path: | + packages/*/dist + packages/consent/*/dist + retention-days: 1 + if-no-files-found: error + publish: name: Publish to npm - needs: [should-release, test] + needs: [should-release, build] if: needs.should-release.outputs.release == 'true' runs-on: ubuntu-latest-large environment: production # manual-approval gate before publish @@ -89,12 +120,20 @@ jobs: with: node-version: 24 - run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn install --immutable + - name: Download build artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: release-build-${{ github.run_id }} + path: . - name: Configure git for tag push run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - name: Publish packages + push tags - run: yarn release + # release:publish-only skips the clean+build the plain `release` + # script does - build already happened once in the build job above, + # and its output was just restored from the artifact. + run: yarn release:publish-only env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create GitHub releases from tags @@ -104,7 +143,7 @@ jobs: deploy-cdn: name: Deploy to CDN - needs: [should-release, test, publish] + needs: [should-release, build] if: needs.should-release.outputs.release == 'true' runs-on: ubuntu-latest-large environment: production # same manual-approval gate as npm publish @@ -140,6 +179,11 @@ jobs: with: node-version: 20 - run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn install --immutable + - name: Download build artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: release-build-${{ github.run_id }} + path: . - name: Assume AWS role via OIDC # aws-actions/configure-aws-credentials isn't on segmentio/analytics-next's # allowed-actions list (third-party, not enterprise-owned/GitHub-created) - @@ -150,4 +194,7 @@ jobs: role-arn: arn:aws:iam::812113486725:role/ajs-private-assets-upload role-session-name: gha-analytics-next-cdn-deploy aws-region: us-west-2 - - run: yarn run -T browser release:cdn + # release:cdn:no-build skips the `yarn . build` the plain release:cdn + # script does - build already happened once in the build job above, + # and its output was just restored from the artifact. + - run: yarn run -T browser release:cdn:no-build diff --git a/package.json b/package.json index 3f3f72b43..aab07e5ce 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "changeset": "changeset", "update-versions-and-changelogs": "changeset version && yarn version-run-all && bash scripts/update-lockfile.sh", "release": "yarn clean && yarn build --force && changeset publish && git push origin HEAD:master --follow-tags --no-verify && yarn scripts purge-cdn-cache", + "release:publish-only": "changeset publish && git push origin HEAD:master --follow-tags --no-verify && yarn scripts purge-cdn-cache", "version-run-all": "yarn workspaces foreach -vpt --no-private run version", "core": "yarn workspace @segment/analytics-core", "browser": "yarn workspace @segment/analytics-next", diff --git a/packages/browser/package.json b/packages/browser/package.json index 35030efb3..f35a27295 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -35,6 +35,7 @@ "watch": "yarn concurrently 'WATCH=true yarn umd --watch' 'yarn pkg --watch'", "build": "yarn clean && yarn build-prep && yarn concurrently 'NODE_ENV=production yarn umd' 'yarn pkg' 'yarn cjs'", "release:cdn": "yarn . build && NODE_ENV=production bash scripts/release.sh && NODE_ENV=stage bash scripts/release.sh", + "release:cdn:no-build": "NODE_ENV=production bash scripts/release.sh && NODE_ENV=stage bash scripts/release.sh", "pkg": "yarn tsc -p tsconfig.build.json", "cjs": "yarn tsc -p tsconfig.build.json --outDir ./dist/cjs --module commonjs", "clean": "rm -rf dist",