// Запуск инструментов из node_modules без .cmd-обёрток и без npx. // Под Windows `node_modules/.bin/tsx` и `npx` — это .cmd-файлы: spawn() без shell // падает с ENOENT. Поэтому всегда запускаем текущий node (process.execPath) с // JS-точкой входа пакета, найденной через require.resolve. import { createRequire } from 'node:module'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; const repoRoot = path.resolve(import.meta.dirname, '../..'); const require = createRequire(path.join(repoRoot, 'package.json')); function packageBin(name, binName = name) { const manifestPath = require.resolve(`${name}/package.json`); const manifest = require(manifestPath); const bin = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin?.[binName]; if (!bin) throw new Error(`${name}: в package.json нет bin "${binName}"`); return path.join(path.dirname(manifestPath), bin); } /** Абсолютный путь к JS-точке входа CLI (не .cmd, не .bin). */ export function toolEntry(tool) { switch (tool) { case 'tsx': return require.resolve('tsx/cli'); case 'vitest': return packageBin('vitest'); case 'prettier': return packageBin('prettier'); default: throw new Error(`неизвестный инструмент: ${tool}`); } } /** Команда для spawn(command, args): всегда process.execPath + JS-файл CLI. */ export function nodeTool(tool, args = []) { return { command: process.execPath, args: [toolEntry(tool), ...args] }; } /** * Запуск TypeScript-файла (например, src/server.ts) в ОДНОМ процессе node с * загрузчиком tsx (`node --import tsx`). CLI tsx запускает дочерний node, и под * Windows kill() родителя (TerminateProcess) оставил бы сервер-сироту на порту. */ export function nodeWithTsx(args = []) { const loader = pathToFileURL(require.resolve('tsx')).href; return { command: process.execPath, args: ['--import', loader, ...args] }; } /** * Минимальное окружение дочернего процесса: PATH и (под Windows) системные * переменные, без которых node не может работать с сетью/криптографией * (SystemRoot, WINDIR, TEMP и т. п.). Секреты родителя не наследуются. */ export function baseEnv(platform = process.platform, env = process.env) { const keys = ['PATH']; if (platform === 'win32') { keys.push( 'SystemRoot', 'WINDIR', 'TEMP', 'TMP', 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA', 'ComSpec', 'PATHEXT', ); } const result = {}; for (const key of keys) { if (env[key] !== undefined) result[key] = env[key]; } return result; } export { repoRoot };