Skip to content

Commit 8a36727

Browse files
trivikrghostdevv
andauthored
refactor(cli)!: use mkdtempDisposable and require node >=24.4 (#2365)
Co-authored-by: Willow (GHOST) <git@willow.sh>
1 parent b1595e8 commit 8a36727

File tree

2 files changed

+70
-77
lines changed

2 files changed

+70
-77
lines changed

cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@
4646
"typescript": "6.0.2"
4747
},
4848
"engines": {
49-
"node": ">=24"
49+
"node": ">=24.4.0"
5050
}
5151
}

cli/src/npm-client.ts

Lines changed: 69 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import crypto from 'node:crypto'
22
import process from 'node:process'
33
import { execFile } from 'node:child_process'
44
import { promisify } from 'node:util'
5-
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
5+
import { mkdtempDisposable, writeFile } from 'node:fs/promises'
66
import { tmpdir } from 'node:os'
77
import { join } from 'node:path'
88
import * as v from 'valibot'
@@ -569,91 +569,84 @@ export async function packageInit(
569569
): Promise<NpmExecResult> {
570570
validatePackageName(name)
571571

572-
// Create a temporary directory
573-
const tempDir = await mkdtemp(join(tmpdir(), 'npmx-init-'))
574-
575-
try {
576-
// Determine access type based on whether it's a scoped package
577-
const isScoped = name.startsWith('@')
578-
const access = isScoped ? 'public' : undefined
579-
580-
// Create minimal package.json
581-
const packageJson = {
582-
name,
583-
version: '0.0.0',
584-
description: `Placeholder for ${name}`,
585-
main: 'index.js',
586-
scripts: {},
587-
keywords: [],
588-
author: author ? `${author} (https://www.npmjs.com/~${author})` : '',
589-
license: 'UNLICENSED',
590-
private: false,
591-
...(access && { publishConfig: { access } }),
592-
}
572+
// Let Node clean up the temp directory automatically when this scope exits.
573+
await using tempDir = await mkdtempDisposable(join(tmpdir(), 'npmx-init-'))
574+
575+
// Determine access type based on whether it's a scoped package
576+
const isScoped = name.startsWith('@')
577+
const access = isScoped ? 'public' : undefined
578+
579+
// Create minimal package.json
580+
const packageJson = {
581+
name,
582+
version: '0.0.0',
583+
description: `Placeholder for ${name}`,
584+
main: 'index.js',
585+
scripts: {},
586+
keywords: [],
587+
author: author ? `${author} (https://www.npmjs.com/~${author})` : '',
588+
license: 'UNLICENSED',
589+
private: false,
590+
...(access && { publishConfig: { access } }),
591+
}
593592

594-
await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson, null, 2))
593+
await writeFile(join(tempDir.path, 'package.json'), JSON.stringify(packageJson, null, 2))
595594

596-
// Create empty index.js
597-
await writeFile(join(tempDir, 'index.js'), '// Placeholder\n')
595+
// Create empty index.js
596+
await writeFile(join(tempDir.path, 'index.js'), '// Placeholder\n')
598597

599-
// Build npm publish args
600-
const args = ['publish']
601-
if (access) {
602-
args.push('--access', access)
603-
}
598+
// Build npm publish args
599+
const args = ['publish']
600+
if (access) {
601+
args.push('--access', access)
602+
}
604603

605-
// Run npm publish from the temp directory
606-
const npmArgs = otp ? [...args, '--otp', otp] : args
604+
// Run npm publish from the temp directory
605+
const npmArgs = otp ? [...args, '--otp', otp] : args
607606

608-
// Log the command being run (hide OTP value for security)
609-
const displayCmd = otp ? `npm ${args.join(' ')} --otp ******` : `npm ${args.join(' ')}`
610-
logCommand(`${displayCmd} (in temp dir for ${name})`)
607+
// Log the command being run (hide OTP value for security)
608+
const displayCmd = otp ? `npm ${args.join(' ')} --otp ******` : `npm ${args.join(' ')}`
609+
logCommand(`${displayCmd} (in temp dir for ${name})`)
611610

612-
try {
613-
const { stdout, stderr } = await execFileAsync('npm', npmArgs, {
614-
timeout: 60000,
615-
cwd: tempDir,
616-
env: createNpmEnv(),
617-
shell: process.platform === 'win32',
618-
})
611+
try {
612+
const { stdout, stderr } = await execFileAsync('npm', npmArgs, {
613+
timeout: 60000,
614+
cwd: tempDir.path,
615+
env: createNpmEnv(),
616+
shell: process.platform === 'win32',
617+
})
619618

620-
logSuccess(`Published ${name}@0.0.0`)
619+
logSuccess(`Published ${name}@0.0.0`)
621620

622-
return {
623-
stdout: stdout.trim(),
624-
stderr: filterNpmWarnings(stderr),
625-
exitCode: 0,
626-
}
627-
} catch (error) {
628-
const err = error as { stdout?: string; stderr?: string; code?: number }
629-
const stderr = err.stderr?.trim() ?? String(error)
630-
const requiresOtp = detectOtpRequired(stderr)
631-
const authFailure = detectAuthFailure(stderr)
621+
return {
622+
stdout: stdout.trim(),
623+
stderr: filterNpmWarnings(stderr),
624+
exitCode: 0,
625+
}
626+
} catch (error) {
627+
const err = error as { stdout?: string; stderr?: string; code?: number }
628+
const stderr = err.stderr?.trim() ?? String(error)
629+
const requiresOtp = detectOtpRequired(stderr)
630+
const authFailure = detectAuthFailure(stderr)
632631

633-
if (requiresOtp) {
634-
logError('OTP required')
635-
} else if (authFailure) {
636-
logError('Authentication required - please run "npm login" and restart the connector')
637-
} else {
638-
logError(filterNpmWarnings(stderr).split('\n')[0] || 'Command failed')
639-
}
632+
if (requiresOtp) {
633+
logError('OTP required')
634+
} else if (authFailure) {
635+
logError('Authentication required - please run "npm login" and restart the connector')
636+
} else {
637+
logError(filterNpmWarnings(stderr).split('\n')[0] || 'Command failed')
638+
}
640639

641-
return {
642-
stdout: err.stdout?.trim() ?? '',
643-
stderr: requiresOtp
644-
? 'This operation requires a one-time password (OTP).'
645-
: authFailure
646-
? 'Authentication failed. Please run "npm login" and restart the connector.'
647-
: filterNpmWarnings(stderr),
648-
exitCode: err.code ?? 1,
649-
requiresOtp,
650-
authFailure,
651-
}
640+
return {
641+
stdout: err.stdout?.trim() ?? '',
642+
stderr: requiresOtp
643+
? 'This operation requires a one-time password (OTP).'
644+
: authFailure
645+
? 'Authentication failed. Please run "npm login" and restart the connector.'
646+
: filterNpmWarnings(stderr),
647+
exitCode: err.code ?? 1,
648+
requiresOtp,
649+
authFailure,
652650
}
653-
} finally {
654-
// Clean up temp directory
655-
await rm(tempDir, { recursive: true, force: true }).catch(() => {
656-
// Ignore cleanup errors
657-
})
658651
}
659652
}

0 commit comments

Comments
 (0)