Зачем: проект разрабатывается под Windows, а стенд там не стартовал:
spawn('npx') и spawn('node_modules/.bin/tsx') без shell падают с ENOENT,
потому что в Windows это .cmd-файлы (приёмка владельца, TASK-GROK-2 п.1).
Что: scripts/test/node-commands.mjs находит JS-точки входа через
require.resolve (tsx/cli, vitest.mjs, prettier.cjs) и всегда запускает их
текущим node (process.execPath). Сервер запускается одним процессом
`node --import tsx src/server.ts`: CLI tsx порождает дочерний node, и под
Windows kill() родителя оставил бы сервер-сироту на порту. baseEnv()
передаёт дочерним процессам PATH и под win32 системные переменные
(SystemRoot, TEMP и др.), без которых node на Windows не работает с сетью,
но не наследует секреты родителя.
Проверка на Linux: tests/e2e/harness/node-commands.test.ts (входит в
npm test) — команды начинаются с process.execPath, без .cmd/.bin/npx,
файлы существуют; исходники стенда не вызывают npx и node_modules/.bin.
Реальный прогон на Windows 11 — не проверено (нет Windows-раннера).
51 lines
2.3 KiB
TypeScript
51 lines
2.3 KiB
TypeScript
// Проверяемо на Linux: стенд запускает инструменты так, что под Windows не нужен
|
||
// .cmd-обёрточный файл (spawn без shell падает на них с ENOENT).
|
||
import { existsSync, readFileSync } from 'node:fs';
|
||
import path from 'node:path';
|
||
import { describe, expect, it } from 'vitest';
|
||
import { baseEnv, nodeTool, nodeWithTsx, repoRoot } from '../../../scripts/test/node-commands.mjs';
|
||
|
||
const forbidden = /\.cmd\b|[\\/]\.bin[\\/]|^npx$|^npm$/i;
|
||
|
||
describe('[HARNESS-WIN] запуск инструментов без .cmd-обёрток', () => {
|
||
it.each(['tsx', 'vitest', 'prettier'] as const)('%s: process.execPath + JS-файл', (tool) => {
|
||
const command = nodeTool(tool, ['--version']);
|
||
expect(command.command).toBe(process.execPath);
|
||
const entry = command.args[0]!;
|
||
expect(entry).not.toMatch(forbidden);
|
||
expect(entry).toMatch(/\.(mjs|cjs|js)$/);
|
||
expect(path.isAbsolute(entry)).toBe(true);
|
||
expect(existsSync(entry)).toBe(true);
|
||
});
|
||
|
||
it('сервер: один процесс node с загрузчиком tsx', () => {
|
||
const command = nodeWithTsx(['src/server.ts']);
|
||
expect(command.command).toBe(process.execPath);
|
||
expect(command.args[0]).toBe('--import');
|
||
expect(command.args[1]).toMatch(/^file:\/\/.*\.mjs$/);
|
||
expect(command.args.join(' ')).not.toMatch(forbidden);
|
||
expect(command.args.at(-1)).toBe('src/server.ts');
|
||
});
|
||
|
||
it('под win32 дочерний процесс получает SystemRoot/TEMP, а секреты не наследуются', () => {
|
||
const env = baseEnv('win32', {
|
||
PATH: 'C:\\Windows',
|
||
SystemRoot: 'C:\\Windows',
|
||
TEMP: 'C:\\Temp',
|
||
DATABASE_URL: 'secret-must-not-leak',
|
||
});
|
||
expect(env).toMatchObject({ PATH: 'C:\\Windows', SystemRoot: 'C:\\Windows', TEMP: 'C:\\Temp' });
|
||
expect(env).not.toHaveProperty('DATABASE_URL');
|
||
expect(baseEnv('linux', { PATH: '/bin', SystemRoot: 'x' })).toEqual({ PATH: '/bin' });
|
||
});
|
||
|
||
it.each([
|
||
'scripts/test/run-e2e.mjs',
|
||
'scripts/test/n13-race.mjs',
|
||
'tests/e2e/harness/global-setup.ts',
|
||
])('%s не вызывает npx и node_modules/.bin', (file) => {
|
||
const source = readFileSync(path.join(repoRoot, file), 'utf8');
|
||
expect(source).not.toMatch(/spawn(Sync)?\(\s*['"`]npx/);
|
||
expect(source).not.toMatch(/node_modules\/\.bin/);
|
||
});
|
||
});
|