diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3426443fc20..99244e4fdef 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,10 +1,15 @@ name: Publish permissions: - contents: read + id-token: write + contents: write on: - workflow_dispatch: + push: + branches: + - main + paths: + - version.txt jobs: publish: @@ -13,17 +18,71 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Read go.version + - name: Read version info run: | echo "GO_VERSION=$(cat go.version)" >> $GITHUB_ENV + echo "ESBUILD_VERSION=$(cat version.txt)" >> $GITHUB_ENV - - name: Set up Go 1.x + # This is here to fail quickly if the release already exists + - name: Try to create the "v${{ env.ESBUILD_VERSION }}" tag + run: | + git fetch --tags + git tag "$ESBUILD_VERSION" + + - name: Extract the release notes + run: | + CHANGELOG=$(awk -v "ver=$ESBUILD_VERSION" '/^## / { if (p) { exit }; if ($2 == ver) { p=1; next} } p' CHANGELOG.md) + echo "CHANGELOG<> $GITHUB_ENV + echo "$CHANGELOG" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + # Make sure we'll be able to generate release notes later on below + - name: Release notes must not be empty + run: | + test -n "$CHANGELOG" + + - name: Set up Go ${{ env.GO_VERSION }} uses: actions/setup-go@v3 with: go-version: ${{ env.GO_VERSION }} - id: go - name: Setup Node.js environment uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 24 + + # This updates the version in all "package.json" files + - name: Build for all platforms + run: | + make platform-all + + # All "package.json" files should have been updated already by running "make platform-all" and committing the results + - name: Reject uncommitted/untracked changes + run: | + git status --porcelain + test -z "$(git status --porcelain)" + + # Trusted publishing requires this specific version of npm + - name: Install npm + run: | + npm install -g npm@11.5.1 + + - name: Publish packages + run: | + make publish-all + + - name: Push the tag to GitHub + run: | + git push origin tag "v$ESBUILD_VERSION" + + # Only do this after publishing was successful + - name: Create a GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ env.ESBUILD_VERSION }} + release_name: v${{ env.ESBUILD_VERSION }} + body: ${{ env.CHANGELOG }} + draft: false + prerelease: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 3da2366441d..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Release - -on: - push: - tags: ['v*'] - -permissions: {} -jobs: - release: - permissions: - contents: write # to create a release (actions/create-release) - - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Extract changelog - run: | - CHANGELOG=$(awk -v ver=$(cat version.txt) '/^## / { if (p) { exit }; if ($2 == ver) { p=1; next} } p' CHANGELOG.md) - echo "CHANGELOG<> $GITHUB_ENV - echo "$CHANGELOG" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Create GitHub Release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - body: ${{ env.CHANGELOG }} - draft: false - prerelease: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2d648956f..a83940685cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +* Enable trusted publishing ([#4281](https://github.com/evanw/esbuild/issues/4281)) + + GitHub and npm are recommending that maintainers for packages such as esbuild switch to [trusted publishing](https://docs.npmjs.com/trusted-publishers). With this release, a VM on GitHub will now build and publish all of esbuild's packages to npm instead of me. In theory. + + Unfortunately there isn't really a way to test that this works other than to do it live. So this release is that live test. Hopefully this release is uneventful and is exactly the same as the previous one (well, except for the green provenance attestation checkmark on npm that happens with trusted publishing). + ## 0.25.12 * Fix a minification regression with CSS media queries ([#4315](https://github.com/evanw/esbuild/issues/4315)) diff --git a/Makefile b/Makefile index fcd341530e5..c4b4d1f2893 100644 --- a/Makefile +++ b/Makefile @@ -421,190 +421,128 @@ platform-deno: platform-wasm node scripts/esbuild.js ./esbuild --deno publish-all: check-go-version - @grep "## $(ESBUILD_VERSION)" CHANGELOG.md || (echo "Missing '## $(ESBUILD_VERSION)' in CHANGELOG.md (required for automatic release notes)" && false) - @npm --version > /dev/null || (echo "The 'npm' command must be in your path to publish" && false) - @echo "Checking for uncommitted/untracked changes..." && test -z "`git status --porcelain | grep -vE 'M (CHANGELOG\.md|version\.txt)'`" || \ - (echo "Refusing to publish with these uncommitted/untracked changes:" && \ - git status --porcelain | grep -vE 'M (CHANGELOG\.md|version\.txt)' && false) - @echo "Checking for main branch..." && test main = "`git rev-parse --abbrev-ref HEAD`" || \ - (echo "Refusing to publish from non-main branch `git rev-parse --abbrev-ref HEAD`" && false) - @echo "Checking for unpushed commits..." && git fetch - @test "" = "`git cherry`" || (echo "Refusing to publish with unpushed commits" && false) - - # Prebuild now to prime go's compile cache and avoid timing issues later - @$(MAKE) --no-print-directory platform-all - - # Commit now before publishing so git is clean for this: https://github.com/golang/go/issues/37475 - # Note: If this fails, then the version number was likely not incremented before running this command - git commit -am "publish $(ESBUILD_VERSION) to npm" - git tag "v$(ESBUILD_VERSION)" - @test -z "`git status --porcelain`" || (echo "Aborting because git is somehow unclean after a commit" && false) - # Make sure the npm directory is pristine (including .gitignored files) since it will be published rm -fr npm && git checkout npm - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-win32-x64 \ - publish-win32-ia32 \ - publish-win32-arm64 \ - publish-wasi-preview1 - - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-freebsd-arm64 \ - publish-freebsd-x64 \ - publish-openbsd-arm64 \ - publish-openbsd-x64 - - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-darwin-arm64 \ - publish-darwin-x64 \ - publish-netbsd-arm64 \ - publish-netbsd-x64 - - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-android-x64 \ - publish-android-arm \ - publish-android-arm64 \ - publish-openharmony-arm64 - - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-linux-x64 \ - publish-linux-ia32 \ - publish-linux-arm - - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-linux-arm64 \ - publish-linux-riscv64 \ - publish-linux-loong64 \ - publish-linux-mips64el - - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-aix-ppc64 \ - publish-linux-ppc64 \ - publish-linux-s390x \ - publish-sunos-x64 - - # Do these last to avoid race conditions - @echo Enter one-time password: - @read OTP && OTP="$$OTP" $(MAKE) --no-print-directory -j4 \ - publish-neutral \ - publish-deno \ - publish-wasm \ - publish-dl - - git push origin main "v$(ESBUILD_VERSION)" + # Publish all platform-dependent packages first + @$(MAKE) --no-print-directory publish-aix-ppc64 + @$(MAKE) --no-print-directory publish-android-arm + @$(MAKE) --no-print-directory publish-android-arm64 + @$(MAKE) --no-print-directory publish-android-x64 + @$(MAKE) --no-print-directory publish-darwin-arm64 + @$(MAKE) --no-print-directory publish-darwin-x64 + @$(MAKE) --no-print-directory publish-freebsd-arm64 + @$(MAKE) --no-print-directory publish-freebsd-x64 + @$(MAKE) --no-print-directory publish-linux-arm + @$(MAKE) --no-print-directory publish-linux-arm64 + @$(MAKE) --no-print-directory publish-linux-ia32 + @$(MAKE) --no-print-directory publish-linux-loong64 + @$(MAKE) --no-print-directory publish-linux-mips64el + @$(MAKE) --no-print-directory publish-linux-ppc64 + @$(MAKE) --no-print-directory publish-linux-riscv64 + @$(MAKE) --no-print-directory publish-linux-s390x + @$(MAKE) --no-print-directory publish-linux-x64 + @$(MAKE) --no-print-directory publish-netbsd-arm64 + @$(MAKE) --no-print-directory publish-netbsd-x64 + @$(MAKE) --no-print-directory publish-openbsd-arm64 + @$(MAKE) --no-print-directory publish-openbsd-x64 + @$(MAKE) --no-print-directory publish-openharmony-arm64 + @$(MAKE) --no-print-directory publish-sunos-x64 + @$(MAKE) --no-print-directory publish-wasi-preview1 + @$(MAKE) --no-print-directory publish-win32-arm64 + @$(MAKE) --no-print-directory publish-win32-ia32 + @$(MAKE) --no-print-directory publish-win32-x64 + + # Publish platform-independent packages last to avoid race conditions + @$(MAKE) --no-print-directory publish-neutral + @$(MAKE) --no-print-directory publish-wasm publish-win32-x64: platform-win32-x64 - test -n "$(OTP)" && cd npm/@esbuild/win32-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/win32-x64 && npm publish publish-win32-ia32: platform-win32-ia32 - test -n "$(OTP)" && cd npm/@esbuild/win32-ia32 && npm publish --otp="$(OTP)" + cd npm/@esbuild/win32-ia32 && npm publish publish-win32-arm64: platform-win32-arm64 - test -n "$(OTP)" && cd npm/@esbuild/win32-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/win32-arm64 && npm publish publish-wasi-preview1: platform-wasi-preview1 - test -n "$(OTP)" && cd npm/@esbuild/wasi-preview1 && npm publish --otp="$(OTP)" + cd npm/@esbuild/wasi-preview1 && npm publish publish-aix-ppc64: platform-aix-ppc64 - test -n "$(OTP)" && cd npm/@esbuild/aix-ppc64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/aix-ppc64 && npm publish publish-android-x64: platform-android-x64 - test -n "$(OTP)" && cd npm/@esbuild/android-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/android-x64 && npm publish publish-android-arm: platform-android-arm - test -n "$(OTP)" && cd npm/@esbuild/android-arm && npm publish --otp="$(OTP)" + cd npm/@esbuild/android-arm && npm publish publish-android-arm64: platform-android-arm64 - test -n "$(OTP)" && cd npm/@esbuild/android-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/android-arm64 && npm publish publish-darwin-x64: platform-darwin-x64 - test -n "$(OTP)" && cd npm/@esbuild/darwin-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/darwin-x64 && npm publish publish-darwin-arm64: platform-darwin-arm64 - test -n "$(OTP)" && cd npm/@esbuild/darwin-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/darwin-arm64 && npm publish publish-freebsd-x64: platform-freebsd-x64 - test -n "$(OTP)" && cd npm/@esbuild/freebsd-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/freebsd-x64 && npm publish publish-freebsd-arm64: platform-freebsd-arm64 - test -n "$(OTP)" && cd npm/@esbuild/freebsd-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/freebsd-arm64 && npm publish publish-netbsd-arm64: platform-netbsd-arm64 - test -n "$(OTP)" && cd npm/@esbuild/netbsd-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/netbsd-arm64 && npm publish publish-netbsd-x64: platform-netbsd-x64 - test -n "$(OTP)" && cd npm/@esbuild/netbsd-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/netbsd-x64 && npm publish publish-openbsd-arm64: platform-openbsd-arm64 - test -n "$(OTP)" && cd npm/@esbuild/openbsd-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/openbsd-arm64 && npm publish publish-openbsd-x64: platform-openbsd-x64 - test -n "$(OTP)" && cd npm/@esbuild/openbsd-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/openbsd-x64 && npm publish publish-openharmony-arm64: platform-openharmony-arm64 - test -n "$(OTP)" && cd npm/@esbuild/openharmony-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/openharmony-arm64 && npm publish publish-linux-x64: platform-linux-x64 - test -n "$(OTP)" && cd npm/@esbuild/linux-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-x64 && npm publish publish-linux-ia32: platform-linux-ia32 - test -n "$(OTP)" && cd npm/@esbuild/linux-ia32 && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-ia32 && npm publish publish-linux-arm: platform-linux-arm - test -n "$(OTP)" && cd npm/@esbuild/linux-arm && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-arm && npm publish publish-linux-arm64: platform-linux-arm64 - test -n "$(OTP)" && cd npm/@esbuild/linux-arm64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-arm64 && npm publish publish-linux-loong64: platform-linux-loong64 - test -n "$(OTP)" && cd npm/@esbuild/linux-loong64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-loong64 && npm publish publish-linux-mips64el: platform-linux-mips64el - test -n "$(OTP)" && cd npm/@esbuild/linux-mips64el && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-mips64el && npm publish publish-linux-ppc64: platform-linux-ppc64 - test -n "$(OTP)" && cd npm/@esbuild/linux-ppc64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-ppc64 && npm publish publish-linux-riscv64: platform-linux-riscv64 - test -n "$(OTP)" && cd npm/@esbuild/linux-riscv64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-riscv64 && npm publish publish-linux-s390x: platform-linux-s390x - test -n "$(OTP)" && cd npm/@esbuild/linux-s390x && npm publish --otp="$(OTP)" + cd npm/@esbuild/linux-s390x && npm publish publish-sunos-x64: platform-sunos-x64 - test -n "$(OTP)" && cd npm/@esbuild/sunos-x64 && npm publish --otp="$(OTP)" + cd npm/@esbuild/sunos-x64 && npm publish publish-wasm: platform-wasm - test -n "$(OTP)" && cd npm/esbuild-wasm && npm publish --otp="$(OTP)" + cd npm/esbuild-wasm && npm publish publish-neutral: platform-neutral - test -n "$(OTP)" && cd npm/esbuild && npm publish --otp="$(OTP)" - -publish-deno: - test -d deno/.git || (rm -fr deno && git clone git@github.com:esbuild/deno-esbuild.git deno) - cd deno && git fetch && git checkout main && git reset --hard origin/main - @$(MAKE) --no-print-directory platform-deno - cd deno && git add mod.js mod.d.ts wasm.js wasm.d.ts esbuild.wasm - cd deno && git commit -m "publish $(ESBUILD_VERSION) to deno" - cd deno && git tag "v$(ESBUILD_VERSION)" - cd deno && git push origin main "v$(ESBUILD_VERSION)" - -publish-dl: - test -d www/.git || (rm -fr www && git clone git@github.com:esbuild/esbuild.github.io.git www) - cd www && git fetch && git checkout gh-pages && git reset --hard origin/gh-pages - cd www && cat ../dl.sh | sed 's/$$ESBUILD_VERSION/$(ESBUILD_VERSION)/' > dl/latest - cd www && cat ../dl.sh | sed 's/$$ESBUILD_VERSION/$(ESBUILD_VERSION)/' > "dl/v$(ESBUILD_VERSION)" - cd www && git add dl/latest "dl/v$(ESBUILD_VERSION)" - cd www && git commit -m "publish download script for $(ESBUILD_VERSION)" - cd www && git push origin gh-pages + cd npm/esbuild && npm publish validate-build: @test -n "$(TARGET)" || (echo "The environment variable TARGET must be provided" && false) diff --git a/RUNBOOK.md b/RUNBOOK.md new file mode 100644 index 00000000000..b33ccafc907 --- /dev/null +++ b/RUNBOOK.md @@ -0,0 +1,73 @@ +# Runbook + +This documents some maintenance tasks for esbuild so I don't forget how they +work. There are a lot of moving parts now that esbuild uses trusted publishing. + +## Publishing a release + +Publishing a release is now done by using GitHub Actions as a +[trusted publisher](https://docs.npmjs.com/trusted-publishers). +To publish a new release: + +1. Update the version in [`version.txt`](./version.txt). Only include the number. Do not include a leading `v`. +2. Copy that version verbatim (without the leading `v`) to a `##` header in [`CHANGELOG.md`](./CHANGELOG.md). This usually replaces the `## Unreleased` header used for unreleased changes. +3. Run `make platform-all` to update the version number in all `package.json` files. The publishing workflow will fail without this step. +4. Commit and push using a message such as `publish 0.X.Y to npm`. This should trigger the publishing workflow described below. + +Pushing a change to [`version.txt`](./version.txt) causes the following: + +- The [`publish.yml`](./.github/workflows/publish.yml) workflow in this repo + will be triggered, which will: + + 1. Build and publish all npm packages to npm using trusted publishing + 2. Create a tag for the release that looks like `v0.X.Y` + 3. Publish a [GitHub Release](https://github.com/evanw/esbuild/releases) containing the release notes in [`CHANGELOG.md`](./CHANGELOG.md) + +- The [`release.yml`](https://github.com/esbuild/deno-esbuild/blob/main/.github/workflows/release.yml) + workflow in the https://github.com/esbuild/deno-esbuild repo runs + occasionally. On the next run, it will notice the version change and: + + 1. Clone this repo + 2. Run `make platform-deno` + 3. Commit and push the new contents of the `deno` folder to the `deno-esbuild` repo + 4. Create a tag for the release that looks like `v0.X.Y` + 5. Post an event to the https://api.deno.land/webhook/gh/esbuild webhook + 6. Deno will then add a new version to https://deno.land/x/esbuild + + You can also manually trigger this workflow if you want it to happen immediately. + +- The [`release.yml`](https://github.com/esbuild/esbuild.github.io/blob/main/.github/workflows/release.yml) + workflow in the https://github.com/esbuild/esbuild.github.io repo runs + occasionally. On the next run, it will notice the version change and: + + 1. Create a new `dl/v0.X.Y` script for the new version number + 2. Update the `dl/latest` script with the new version number + 3. Commit and push these new scripts to the `gh-pages` branch of the `esbuild.github.io` repo + 4. GitHub Pages will then deploy these updates to https://esbuild.github.io/ + + You can also manually trigger this workflow if you want it to happen immediately. + +## Adding a new package + +Each platform (operating system + architecture) needs a separate optional npm +package due to how esbuild's installer works. New packages should be created +under the `@esbuild/` scope so it's obvious that they are official. + +Create a directory for the new package inside the [`npm/@esbuild`](./npm/@esbuild/) +directory. Then modify the rest of the repo to reference the new package. The +specifics for what to modify depends on the platform, but a good place to +start is to search for the name of a similar existing package and see where +it's used. + +In addition, you'll need to prepare that package for the next release. To do +that: + +1. Create an empty package with the expected name and a version of 0.0.1 +2. Publish it with `npm publish --access public` (note that scoped packages are private by default) +3. Log in to the npm website and go to the package settings +4. Ensure that the only maintainer is the [esbuild](https://www.npmjs.com/~esbuild) user +5. Add the GitHub repo as the trusted publisher: + - **Organization or user:** `evanw` + - **Repository:** `esbuild` + - **Workflow filename:** `publish.yml` +6. Ensure publishing access is set to **Require two-factor authentication and disallow tokens (recommended)**