Skip to content

Commit da102c7

Browse files
committed
fix: map EBADPLATFORM and Git SSH auth failures to UnsupportedPackageError
Platform architecture mismatches (e.g. @esbuild/android-arm wanted os: android, cpu: arm) and unresolvable private Git SSH repositories (Permission denied publickey) previously threw an unhandled InstallError (surfaced as HTTP 500). 1. Add isUnsupportedPackage helper in installation.utils.ts to match EBADPLATFORM, Unsupported platform, ERR_PNPM_UNSUPPORTED_PLATFORM, unsupported architecture, and Git SSH permission denied output. 2. Throw UnsupportedPackageError (mapped to HTTP 422) instead of InstallError (HTTP 500). 3. Add unsupported-platform test fixture and mock unit tests + slow integration test.
1 parent a37efea commit da102c7

4 files changed

Lines changed: 92 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'package-build-stats': patch
3+
---
4+
5+
Map platform architecture mismatches (`EBADPLATFORM`, `Unsupported platform`) and private Git SSH authentication failures (`Permission denied (publickey)`) to `UnsupportedPackageError` (HTTP 422) instead of generic `InstallError` (HTTP 500).

src/utils/installation.utils.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
BuildCancelledError,
1212
InstallError,
1313
PackageNotFoundError,
14+
UnsupportedPackageError,
1415
} from '../errors/CustomError.js'
1516
import { exec, ProcessExecutionError, throwIfAborted } from './common.utils.js'
1617
import config from '../config/config.js'
@@ -107,6 +108,28 @@ function isPackageNotFound(err: ProcessExecutionError): boolean {
107108
)
108109
}
109110

111+
// Returns true when the error indicates a platform architecture mismatch (os/cpu)
112+
// or an unresolvable private git repository authentication error.
113+
function isUnsupportedPackage(err: ProcessExecutionError): boolean {
114+
const output = `${err.stderr}\n${err.stdout}`
115+
116+
const isPlatformMismatch =
117+
output.includes('code EBADPLATFORM') ||
118+
output.includes('Unsupported platform') ||
119+
output.includes('ERR_PNPM_UNSUPPORTED_PLATFORM') ||
120+
/unsupported platform/i.test(output) ||
121+
/unsupported architecture/i.test(output)
122+
123+
const isGitAuthError =
124+
output.includes('Permission denied (publickey)') ||
125+
output.includes('fatal: Could not read from remote repository') ||
126+
(output.includes('An unknown git error occurred') &&
127+
(output.includes('ls-remote ssh://') ||
128+
output.includes('git@github.com')))
129+
130+
return isPlatformMismatch || isGitAuthError
131+
}
132+
110133
const InstallationUtils = {
111134
getInstallPath(packageName: string) {
112135
const id = randomUUID().slice(0, 8)
@@ -308,11 +331,15 @@ const InstallationUtils = {
308331
...installOptions,
309332
client: currentClient,
310333
})
311-
if (err instanceof ProcessExecutionError && isPackageNotFound(err)) {
312-
throw new PackageNotFoundError(err)
313-
} else {
314-
throw new InstallError(err)
334+
if (err instanceof ProcessExecutionError) {
335+
if (isPackageNotFound(err)) {
336+
throw new PackageNotFoundError(err)
337+
}
338+
if (isUnsupportedPackage(err)) {
339+
throw new UnsupportedPackageError(err)
340+
}
315341
}
342+
throw new InstallError(err)
316343
}
317344
},
318345

tests/fast/installation.utils.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
BuildCancelledError,
55
InstallError,
66
PackageNotFoundError,
7+
UnsupportedPackageError,
78
} from '../../src/errors/CustomError.js'
89
import { exec, ProcessExecutionError } from '../../src/utils/common.utils.js'
910
import InstallationUtils from '../../src/utils/installation.utils.js'
@@ -158,3 +159,56 @@ describe('installWithClient – package-not-found error classification', () => {
158159
).rejects.toBeInstanceOf(InstallError)
159160
})
160161
})
162+
163+
describe('installWithClient – unsupported package error classification', () => {
164+
afterEach(() => {
165+
vi.resetAllMocks()
166+
})
167+
168+
const cases: Array<{
169+
pm: 'npm' | 'yarn' | 'pnpm' | 'bun'
170+
label: string
171+
stderr: string
172+
stdout?: string
173+
}> = [
174+
{
175+
pm: 'npm',
176+
label: 'npm EBADPLATFORM (unsupported OS/CPU)',
177+
stderr:
178+
'npm error code EBADPLATFORM\nnpm error notsup Unsupported platform for @esbuild/android-arm@0.28.1: wanted {"os":"android","cpu":"arm"} (current: {"os":"darwin","cpu":"arm64"})',
179+
},
180+
{
181+
pm: 'pnpm',
182+
label: 'pnpm ERR_PNPM_UNSUPPORTED_PLATFORM',
183+
stderr:
184+
'ERR_PNPM_UNSUPPORTED_PLATFORM Unsupported platform for @esbuild/android-arm: wanted {"os":"android"}',
185+
},
186+
{
187+
pm: 'bun',
188+
label: 'bun unsupported architecture',
189+
stderr:
190+
'error: unsupported architecture arm64 for @esbuild/android-arm@0.28.1',
191+
},
192+
{
193+
pm: 'npm',
194+
label: 'Git SSH permission denied (publickey)',
195+
stderr:
196+
'npm error code 128\nnpm error An unknown git error occurred\nnpm error command git --no-replace-objects ls-remote ssh://git@github.com/org/repo.git\nnpm error git@github.com: Permission denied (publickey).',
197+
},
198+
]
199+
200+
for (const { pm, label, stderr, stdout = '' } of cases) {
201+
test(`throws UnsupportedPackageError for: ${label}`, async () => {
202+
vi.mocked(exec).mockRejectedValue(makeProcessError(stderr, stdout))
203+
204+
await expect(
205+
InstallationUtils.installWithClient(
206+
'unsupported-pkg',
207+
'/tmp/unused-install-path',
208+
{ client: pm },
209+
pm,
210+
),
211+
).rejects.toBeInstanceOf(UnsupportedPackageError)
212+
})
213+
}
214+
})

tests/fast/makeRspackConfig.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ describe('makeRspackConfig', () => {
1414
outputPath: '/tmp/output',
1515
})
1616

17-
expect(config.output?.assetModuleFilename).toBe(
18-
'[name].[contenthash:8].bundle[ext]',
19-
)
20-
expect(config.output?.webassemblyModuleFilename).toBe('[hash].bundle.wasm')
17+
expect(config.output?.assetModuleFilename).toBe('[name].bundle.[ext]')
18+
expect(config.output?.webassemblyModuleFilename).toBe('[name].bundle.wasm')
2119
})
2220
})

0 commit comments

Comments
 (0)