From ca180869ec6d6c8ff85e60783424aa15d0acd2ef Mon Sep 17 00:00:00 2001 From: Denys Date: Fri, 1 May 2026 18:47:09 +0100 Subject: [PATCH 1/2] fix: replace mkdtempDisposable with mkdtemp, and wait using with explicit cleanup for Node v22 compatibility --- cli/src/npm-client.ts | 95 ++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/cli/src/npm-client.ts b/cli/src/npm-client.ts index 4764c27e96..5e91fc1211 100644 --- a/cli/src/npm-client.ts +++ b/cli/src/npm-client.ts @@ -2,7 +2,7 @@ import crypto from 'node:crypto' import process from 'node:process' import { execFile } from 'node:child_process' import { promisify } from 'node:util' -import { mkdtempDisposable, writeFile } from 'node:fs/promises' +import { mkdtemp, writeFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import * as v from 'valibot' @@ -572,54 +572,57 @@ export async function packageInit( ): Promise { validatePackageName(name) - // Let Node clean up the temp directory automatically when this scope exits. - await using tempDir = await mkdtempDisposable(join(tmpdir(), 'npmx-init-')) - - // Determine access type based on whether it's a scoped package - const isScoped = name.startsWith('@') - const access = isScoped ? 'public' : undefined - - // Create minimal package.json - const packageJson = { - name, - version: '0.0.0', - description: `Placeholder for ${name}`, - main: 'index.js', - scripts: {}, - keywords: [], - author: author ? `${author} (https://www.npmjs.com/~${author})` : '', - license: 'UNLICENSED', - private: false, - ...(access && { publishConfig: { access } }), - } + const tempDirPath = await mkdtemp(join(tmpdir(), 'npmx-init-')) - await writeFile(join(tempDir.path, 'package.json'), JSON.stringify(packageJson, null, 2)) + try { + // Determine access type based on whether it's a scoped package + const isScoped = name.startsWith('@') + const access = isScoped ? 'public' : undefined + + // Create minimal package.json + const packageJson = { + name, + version: '0.0.0', + description: `Placeholder for ${name}`, + main: 'index.js', + scripts: {}, + keywords: [], + author: author ? `${author} (https://www.npmjs.com/~${author})` : '', + license: 'UNLICENSED', + private: false, + ...(access && { publishConfig: { access } }), + } - // Create empty index.js - await writeFile(join(tempDir.path, 'index.js'), '// Placeholder\n') + await writeFile(join(tempDirPath, 'package.json'), JSON.stringify(packageJson, null, 2)) - // Build npm publish args - const args = ['publish'] - if (access) { - args.push('--access', access) - } + // Create empty index.js + await writeFile(join(tempDirPath, 'index.js'), '// Placeholder\n') - const displayCmd = options?.otp - ? ['npm', ...args, '--otp', '******'].join(' ') - : ['npm', ...args].join(' ') - logCommand(`${displayCmd} (in temp dir for ${name})`) - - const result = await execNpm(args, { ...options, cwd: tempDir.path, silent: true }) - - if (result.exitCode === 0) { - logSuccess(`Published ${name}@0.0.0`) - } else if (result.requiresOtp) { - logError('OTP required') - } else if (result.authFailure) { - logError('Authentication required - please run "npm login" and restart the connector') - } else { - logError(result.stderr.split('\n')[0] || 'Command failed') - } + // Build npm publish args + const args = ['publish'] + if (access) { + args.push('--access', access) + } - return result + const displayCmd = options?.otp + ? ['npm', ...args, '--otp', '******'].join(' ') + : ['npm', ...args].join(' ') + logCommand(`${displayCmd} (in temp dir for ${name})`) + + const result = await execNpm(args, { ...options, cwd: tempDirPath, silent: true }) + + if (result.exitCode === 0) { + logSuccess(`Published ${name}@0.0.0`) + } else if (result.requiresOtp) { + logError('OTP required') + } else if (result.authFailure) { + logError('Authentication required - please run "npm login" and restart the connector') + } else { + logError(result.stderr.split('\n')[0] || 'Command failed') + } + + return result + } finally { + await rm(tempDirPath, { recursive: true, force: true }) + } } From 0b0cfb6f80e41510ee88fc7717318ac002950371 Mon Sep 17 00:00:00 2001 From: Denys Date: Fri, 1 May 2026 19:12:52 +0100 Subject: [PATCH 2/2] fix: preserve original publish error --- cli/src/npm-client.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cli/src/npm-client.ts b/cli/src/npm-client.ts index 5e91fc1211..c6c249151e 100644 --- a/cli/src/npm-client.ts +++ b/cli/src/npm-client.ts @@ -574,6 +574,9 @@ export async function packageInit( const tempDirPath = await mkdtemp(join(tmpdir(), 'npmx-init-')) + let publishResult: NpmExecResult | null = null + let publishError: unknown = null + try { // Determine access type based on whether it's a scoped package const isScoped = name.startsWith('@') @@ -621,8 +624,29 @@ export async function packageInit( logError(result.stderr.split('\n')[0] || 'Command failed') } - return result - } finally { + publishResult = result + } catch (error) { + publishError = error + } + + try { await rm(tempDirPath, { recursive: true, force: true }) + } catch (cleanupError) { + if (publishError) { + // Preserve original error + Object.assign(cleanupError as Error, { cause: publishError }) + } + + throw cleanupError + } + + if (publishError) { + throw publishError } + + if (!publishResult) { + throw new Error('packageInit completed without a publish result') + } + + return publishResult }