From ec155eddbbb444e3f23564faf59ca462c168aaa0 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar <29282228+andrii-bodnar@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:03:00 +0300 Subject: [PATCH] fix(build): add Windows PE metadata to crowdin.exe The released crowdin.exe carried no VERSIONINFO resource at all, so Windows showed no publisher, product name or version in file properties. An unsigned PE with a blank version resource also feeds AV heuristics - 5.0.0 came back 1/68 on the Chocolatey virus scan. Bun's --windows-* flags only work on a Windows host (they depend on Windows APIs), so the exe can no longer be cross-compiled on ubuntu. Build it once in a new win-binary job and reuse that artifact for the npm package, the release asset and the Inno Setup installer. Side benefit: the installer now ships the exact binary the release publishes instead of a separately compiled one. Both Windows jobs assert the metadata is non-empty, so the flags cannot fall out of the build script unnoticed. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-test.yml | 12 +++- .github/workflows/publish.yml | 113 +++++++++++++++++++++++++------ package.json | 4 +- 3 files changed, 106 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 061d96f56..43838eb84 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -61,7 +61,8 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Build all platform binaries + - name: Build cross-compilable platform binaries + # win32-x64 is built by the windows-installer job - the PE metadata flags need a Windows host run: bun run build:all windows-installer: @@ -89,6 +90,15 @@ jobs: - name: Smoke test binary run: packages\npm\win32-x64\bin\crowdin.exe --version + - name: Verify PE metadata + # Catches the --windows-* flags silently falling out of the build script + run: | + $vi = (Get-Item packages\npm\win32-x64\bin\crowdin.exe).VersionInfo + $vi | Format-List CompanyName, ProductName, FileDescription, FileVersion, LegalCopyright + foreach ($field in 'CompanyName', 'ProductName', 'FileDescription', 'FileVersion') { + if (-not $vi.$field) { throw "PE metadata field $field is empty" } + } + - name: Compile .ISS to .EXE Installer # Inno Setup is preinstalled on the windows runner image env: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 68b391ac1..3956113a1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -76,7 +76,6 @@ jobs: - linux-arm64 - linux-x64-musl - linux-arm64-musl - - win32-x64 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -101,9 +100,85 @@ jobs: - name: Publish run: npm publish packages/npm/${{ matrix.package }} + win-binary: + # Bun's --windows-* PE metadata flags only work on a Windows host, so crowdin.exe is + # built once here and reused by the npm package, the release asset and the installer + needs: verify + if: github.event.inputs.package == 'all' || github.event.inputs.package == 'npm' + runs-on: windows-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + # Baseline build, so the win32-x64-baseline compile target matches the host + # and bun doesn't download the target executable - that download is broken + # on Windows hosts (oven-sh/bun#11198). Keep the version in sync with .bun-version + bun-download-url: https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/bun-windows-x64-baseline.zip + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Read version + id: version + uses: ./.github/actions/read-version + + - name: Build binary + run: bun run build:win32-x64 + + - name: Smoke test binary + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + $out = & packages\npm\win32-x64\bin\crowdin.exe --version + Write-Host "manifest version: $env:VERSION / binary version: $out" + if ("$out" -ne $env:VERSION) { exit 1 } + + - name: Verify PE metadata + # Catches the --windows-* flags silently falling out of the build script + run: | + $vi = (Get-Item packages\npm\win32-x64\bin\crowdin.exe).VersionInfo + $vi | Format-List CompanyName, ProductName, FileDescription, FileVersion, LegalCopyright + foreach ($field in 'CompanyName', 'ProductName', 'FileDescription', 'FileVersion') { + if (-not $vi.$field) { throw "PE metadata field $field is empty" } + } + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: crowdin-exe + path: packages/npm/win32-x64/bin/crowdin.exe + if-no-files-found: error + + publish-win: + # Publishes the win-binary exe as the win32-x64 npm package, so npm ships the same + # binary as the release and the installer rather than a separate cross-compiled one + needs: win-binary + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + + - name: Update npm + run: npm install -g npm@11.18.0 + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: crowdin-exe + path: packages/npm/win32-x64/bin + + - name: Publish + run: npm publish packages/npm/win32-x64 + # Last: the launcher pins exact platform-package versions, so it must never be published before its binaries publish-cli: - needs: publish-platforms + needs: [publish-platforms, publish-win] runs-on: ubuntu-latest permissions: contents: read @@ -124,7 +199,7 @@ jobs: assets: # Builds the standalone binaries and creates the GitHub release once the npm packages are published - needs: publish-cli + needs: [publish-cli, win-binary] runs-on: ubuntu-latest permissions: contents: write @@ -143,15 +218,20 @@ jobs: uses: ./.github/actions/read-version - name: Build binaries + # build:all covers the cross-compilable targets only; crowdin.exe comes from win-binary run: bun run build:all + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: crowdin-exe + path: release-assets + - name: Stage release assets run: | - mkdir release-assets + mkdir -p release-assets for platform in darwin-arm64 darwin-x64 linux-x64 linux-arm64 linux-x64-musl linux-arm64-musl; do cp packages/npm/$platform/bin/crowdin release-assets/crowdin-$platform done - cp packages/npm/win32-x64/bin/crowdin.exe release-assets/crowdin.exe cd release-assets shasum -a 256 crowdin* > crowdin-cli_checksums.sha256 echo ${{ steps.version.outputs.version }} > version.txt @@ -359,31 +439,24 @@ jobs: fork-user: crowdin-bot exe: - # Builds the Windows installer (Inno Setup) around the standalone win32-x64 - # binary and attaches it to the GitHub release created by the assets job + # Wraps the Windows installer (Inno Setup) around the win-binary exe - the same binary + # the release and the npm package ship - and attaches it to the GitHub release runs-on: windows-latest - needs: assets + needs: [assets, win-binary] permissions: contents: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - with: - # Baseline build, so the win32-x64-baseline compile target matches the host - # and bun doesn't download the target executable - that download is broken - # on Windows hosts (oven-sh/bun#11198). Keep the version in sync with .bun-version - bun-download-url: https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/bun-windows-x64-baseline.zip - - - name: Install dependencies - run: bun install --frozen-lockfile - - name: Read version id: version uses: ./.github/actions/read-version - - name: Build binary - run: bun run build:win32-x64 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: crowdin-exe + # The path the .iss Source directive reads + path: packages/npm/win32-x64/bin - name: Smoke test binary run: packages\npm\win32-x64\bin\crowdin.exe --version diff --git a/package.json b/package.json index 2bd21a88e..ac455c9bd 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "build:linux-arm64": "bun build src-next/cli.ts --compile --target=bun-linux-arm64 --outfile packages/npm/linux-arm64/bin/crowdin", "build:linux-x64-musl": "bun build src-next/cli.ts --compile --target=bun-linux-x64-musl-baseline --outfile packages/npm/linux-x64-musl/bin/crowdin", "build:linux-arm64-musl": "bun build src-next/cli.ts --compile --target=bun-linux-arm64-musl --outfile packages/npm/linux-arm64-musl/bin/crowdin", - "build:win32-x64": "bun build src-next/cli.ts --compile --target=bun-windows-x64-baseline --outfile packages/npm/win32-x64/bin/crowdin.exe", - "build:all": "bun run build:darwin-arm64 && bun run build:darwin-x64 && bun run build:linux-x64 && bun run build:linux-arm64 && bun run build:linux-x64-musl && bun run build:linux-arm64-musl && bun run build:win32-x64", + "build:win32-x64": "bun build src-next/cli.ts --compile --target=bun-windows-x64-baseline --windows-title=\"Crowdin CLI\" --windows-publisher=\"OÜ Crowdin\" --windows-version=$npm_package_version --windows-description=\"Command line tool for managing localization resources with Crowdin\" --windows-copyright=\"© Crowdin\" --outfile packages/npm/win32-x64/bin/crowdin.exe", + "build:all": "bun run build:darwin-arm64 && bun run build:darwin-x64 && bun run build:linux-x64 && bun run build:linux-arm64 && bun run build:linux-x64-musl && bun run build:linux-arm64-musl", "test": "bun test tests/ e2e/helpers/", "test:coverage": "bun test --coverage tests/ e2e/helpers/", "test:e2e": "bun test e2e/suites/ --timeout 120000",