mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
process.argv[1] keeps the path as typed, so a script started through a symlink (or from a symlinked directory) still carries the link path there, while Node builds import.meta.url of the main module from the real path. The two never matched, and every CLI guarded by isMainModule silently did nothing and exited 0. Both sides are now resolved with realpathSync before the pathToFileURL comparison; a path that does not exist is compared as is, without throwing, exactly as before. The unit test writes a CLI and a module it imports into a temporary directory, launches the CLI directly, through a directory link (a junction on Windows, no admin rights needed) and through a file symlink (skipped on EPERM), and checks that only the launched script runs its main. It is red on the previous implementation. Issue: #733 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
42 lines
2.5 KiB
JavaScript
42 lines
2.5 KiB
JavaScript
// Переносимый запуск команд (#496).
|
||
//
|
||
// Две Windows-ловушки, из-за которых тесты инфраструктуры падали на машине
|
||
// владельца при зелёном Linux CI:
|
||
//
|
||
// 1. `new URL(\`file://${process.argv[1]}\`)` для `C:\...\x.mjs` даёт
|
||
// `file:///C:/C:/...` — сравнение с `import.meta.url` ложно, CLI молчит.
|
||
// Правильно — `pathToFileURL`, он знает про буквы дисков.
|
||
// 2. `spawn(cmd, args, { shell: true })` склеивает аргументы в строку без
|
||
// экранирования: `node -e "…"` с кавычками и пробелами разваливается. Оболочка
|
||
// нужна ТОЛЬКО для `.cmd`/`.bat` (Node ≥ 18.20 иначе бросает EINVAL), то есть
|
||
// для `npm` на Windows; `node` и `git` запускаются напрямую.
|
||
//
|
||
// #733: `process.argv[1]` — путь, КАК его набрали (симлинк остаётся симлинком),
|
||
// а `import.meta.url` главного модуля Node строит по реальному пути. Запуск
|
||
// через симлинк (или из каталога-симлинка) давал `false`, и CLI молча выходил
|
||
// с кодом 0. Поэтому сравниваются реальные пути обеих сторон.
|
||
|
||
import { realpathSync } from 'node:fs';
|
||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||
|
||
/** Реальный путь; для несуществующего — сам путь, как было до #733. */
|
||
function realOrSame(path) {
|
||
try { return realpathSync(path); } catch { return path; }
|
||
}
|
||
|
||
/** Скрипт запущен как CLI, а не импортирован (переносимая форма). */
|
||
export function isMainModule(metaUrl, argv1 = process.argv[1]) {
|
||
if (!argv1) return false;
|
||
try {
|
||
return pathToFileURL(realOrSame(argv1)).href === pathToFileURL(realOrSame(fileURLToPath(metaUrl))).href;
|
||
} catch { return false; }
|
||
}
|
||
|
||
/** Что и как запускать: `{ cmd, shell }` для `spawn`/`spawnSync`. */
|
||
export function portableCommand(cmd, platform = process.platform) {
|
||
if (platform !== 'win32') return { cmd, shell: false };
|
||
if (cmd === 'npm' || cmd === 'npx') return { cmd: `${cmd}.cmd`, shell: true };
|
||
if (/\.(cmd|bat)$/i.test(cmd)) return { cmd, shell: true };
|
||
return { cmd, shell: false };
|
||
}
|