From ce03ef1940e878c296f0ebb12f518086be737362 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Sat, 7 Feb 2026 02:11:56 +0000 Subject: [PATCH] fix(targets): Add missing spawnProcess imports in pypi and crates The pypi and crates targets used spawnProcess without importing it, causing runtime errors during publishing. This was not caught because: - esbuild (used for builds) doesn't perform type checking - Tests mocked the entire method instead of the dependency Changes: - Add missing spawnProcess import to pypi.ts and crates.ts - Add test that exercises real uploadAssets implementation - Add typecheck script for local development --- package.json | 1 + src/targets/__tests__/pypi.test.ts | 34 +++++++++++++++++++++++------- src/targets/crates.ts | 1 + src/targets/pypi.ts | 6 +++++- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 31c885fe..021aabe1 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "fix": "pnpm lint --fix", "test": "vitest run", "test:watch": "vitest", + "typecheck": "tsc --noEmit", "docs:dev": "cd docs && pnpm dev", "docs:build": "cd docs && pnpm build" }, diff --git a/src/targets/__tests__/pypi.test.ts b/src/targets/__tests__/pypi.test.ts index 2499cda9..d93a4c24 100644 --- a/src/targets/__tests__/pypi.test.ts +++ b/src/targets/__tests__/pypi.test.ts @@ -3,14 +3,17 @@ import { PypiTarget } from '../pypi'; import { NoneArtifactProvider } from '../../artifact_providers/none'; import { RemoteArtifact } from '../../artifact_providers/base'; -vi.mock('../../utils/system', async (importOriginal) => { +vi.mock('../../utils/system', async importOriginal => { const actual = await importOriginal(); return { ...actual, checkExecutableIsPresent: vi.fn(), + spawnProcess: vi.fn(), }; }); +import { spawnProcess } from '../../utils/system'; + describe('pypi', () => { const oldEnv = { ...process.env }; @@ -25,16 +28,18 @@ describe('pypi', () => { test('it uploads all artifacts in a single twine call', async () => { const target = new PypiTarget({ name: 'pypi' }, new NoneArtifactProvider()); - target.getArtifactsForRevision = vi.fn().mockResolvedValueOnce([ - { filename: 'pkg-1-py3-none-macos_11_0_arm64.whl' }, - { filename: 'pkg-1-py3-none-manylinux_2_17_x86_64.whl' }, - { filename: 'pkg-1.tar.gz' }, - ]); + target.getArtifactsForRevision = vi + .fn() + .mockResolvedValueOnce([ + { filename: 'pkg-1-py3-none-macos_11_0_arm64.whl' }, + { filename: 'pkg-1-py3-none-manylinux_2_17_x86_64.whl' }, + { filename: 'pkg-1.tar.gz' }, + ]); target.artifactProvider.downloadArtifact = vi.fn( async ( artifact: RemoteArtifact, - _downloadDirectory?: string | undefined - ) => `downloaded/path/${artifact.filename}` + _downloadDirectory?: string | undefined, + ) => `downloaded/path/${artifact.filename}`, ); const upload = vi.fn(); target.uploadAssets = upload; @@ -47,4 +52,17 @@ describe('pypi', () => { 'downloaded/path/pkg-1.tar.gz', ]); }); + + test('uploadAssets calls twine with correct arguments', async () => { + vi.mocked(spawnProcess).mockResolvedValueOnce(Buffer.from('')); + + const target = new PypiTarget({ name: 'pypi' }, new NoneArtifactProvider()); + await target.uploadAssets(['/path/to/pkg.whl', '/path/to/pkg.tar.gz']); + + expect(spawnProcess).toHaveBeenCalledWith('twine', [ + 'upload', + '/path/to/pkg.whl', + '/path/to/pkg.tar.gz', + ]); + }); }); diff --git a/src/targets/crates.ts b/src/targets/crates.ts index 3790ff8a..6eb1596c 100644 --- a/src/targets/crates.ts +++ b/src/targets/crates.ts @@ -11,6 +11,7 @@ import { checkExecutableIsPresent, resolveExecutable, runWithExecutable, + spawnProcess, } from '../utils/system'; import { BaseTarget } from './base'; import { BaseArtifactProvider } from '../artifact_providers/base'; diff --git a/src/targets/pypi.ts b/src/targets/pypi.ts index 0d89ee0c..98070f86 100644 --- a/src/targets/pypi.ts +++ b/src/targets/pypi.ts @@ -7,7 +7,11 @@ import { RemoteArtifact, } from '../artifact_providers/base'; import { ConfigurationError, reportError } from '../utils/errors'; -import { checkExecutableIsPresent, runWithExecutable } from '../utils/system'; +import { + checkExecutableIsPresent, + runWithExecutable, + spawnProcess, +} from '../utils/system'; import { BaseTarget } from './base'; import { logger } from '../logger';