From ac4fef3341d6147c1be430d8e032057c570e1c10 Mon Sep 17 00:00:00 2001 From: Dipesh Babu Date: Mon, 5 Oct 2026 00:22:12 -0400 Subject: [PATCH] fix: Windows build:host fails because the zip command is unavailable --- .github/workflows/ci.yml | 17 +++++++++++++++++ packages/cli/scripts/archive.mjs | 14 ++++++++++++++ packages/cli/scripts/build-cli.mjs | 4 ++-- packages/cli/test/archive.test.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 packages/cli/scripts/archive.mjs create mode 100644 packages/cli/test/archive.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42a3e5a..4604c0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,3 +28,20 @@ jobs: - run: pnpm test - name: Bundle for npm and smoke it run: pnpm build:npm && node packages/cli/dist/vibi.mjs --version && node packages/cli/dist/vibi.mjs --help + + windows-host-build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - uses: actions/setup-go@v5 + with: + go-version-file: packages/cli/tui/go.mod + - uses: oven-sh/setup-bun@v2 + - run: pnpm install --frozen-lockfile + - run: pnpm build:host + - run: packages/cli/dist/bin/windows-x64/vibi.exe --version diff --git a/packages/cli/scripts/archive.mjs b/packages/cli/scripts/archive.mjs new file mode 100644 index 0000000..0612a1f --- /dev/null +++ b/packages/cli/scripts/archive.mjs @@ -0,0 +1,14 @@ +import { execFileSync } from 'node:child_process'; +import path from 'node:path'; + +export function archiveRelease(asset, outfile) { + if (asset.endsWith('.zip')) { + if (process.platform === 'win32') { + execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', + 'Compress-Archive -LiteralPath $env:VIBI_ARCHIVE_SOURCE -DestinationPath $env:VIBI_ARCHIVE_DESTINATION -Force'], { + stdio: 'inherit', + env: {...process.env, VIBI_ARCHIVE_SOURCE: outfile, VIBI_ARCHIVE_DESTINATION: asset} + }); + } else execFileSync('zip', ['-q', '-j', asset, outfile], {stdio: 'inherit'}); + } else execFileSync('tar', ['-czf', asset, '-C', path.dirname(outfile), path.basename(outfile)], {stdio: 'inherit'}); +} diff --git a/packages/cli/scripts/build-cli.mjs b/packages/cli/scripts/build-cli.mjs index 4f6a9fb..8433ccf 100644 --- a/packages/cli/scripts/build-cli.mjs +++ b/packages/cli/scripts/build-cli.mjs @@ -16,6 +16,7 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { archiveRelease } from './archive.mjs'; const CLI_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const DIST = path.join(CLI_DIR, 'dist'); @@ -79,8 +80,7 @@ if (targets.length) { const asset = path.join(release, `vibi-${t.name}${t.exe ? '.zip' : '.tar.gz'}`); fs.rmSync(asset, { force: true }); - if (t.exe) execFileSync('zip', ['-q', '-j', asset, outfile], { stdio: 'inherit' }); - else execFileSync('tar', ['-czf', asset, '-C', outDir, 'vibi'], { stdio: 'inherit' }); + archiveRelease(asset, outfile); const digest = createHash('sha256').update(fs.readFileSync(asset)).digest('hex'); checksums.push(`${digest} ${path.basename(asset)}`); console.log(` ${path.relative(CLI_DIR, asset)} (${(fs.statSync(asset).size / 1048576).toFixed(1)} MB)`); diff --git a/packages/cli/test/archive.test.ts b/packages/cli/test/archive.test.ts new file mode 100644 index 0000000..8601b7f --- /dev/null +++ b/packages/cli/test/archive.test.ts @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; +import { archiveRelease } from '../scripts/archive.mjs'; + +test('Windows release archives build without an external zip executable', () => { + const dir = mkdtempSync(path.join(os.tmpdir(), 'vibi archive spaces ')); + try { + const source = path.join(dir, 'vibi.exe'); + const asset = path.join(dir, 'vibi-windows-x64.zip'); + writeFileSync(source, 'executable fixture'); + archiveRelease(asset, source); + const bytes = readFileSync(asset); + assert.equal(bytes.subarray(0, 2).toString(), 'PK'); + if (process.platform === 'win32') { + const destination = path.join(dir, 'unpacked'); + execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', 'Expand-Archive -LiteralPath $env:VIBI_ARCHIVE_SOURCE -DestinationPath $env:VIBI_ARCHIVE_DESTINATION'], {env: {...process.env, VIBI_ARCHIVE_SOURCE: asset, VIBI_ARCHIVE_DESTINATION: destination}}); + assert.equal(readFileSync(path.join(destination, 'vibi.exe'), 'utf8'), 'executable fixture'); + } else { + assert.equal(execFileSync('unzip', ['-p', asset, 'vibi.exe'], {encoding: 'utf8'}), 'executable fixture'); + } + } finally {rmSync(dir, {recursive: true, force: true});} +});