krona-staff/tests/e2e/harness/node-commands.test.ts
grok 0aaa2f4d2d test(e2e): запуск vitest/tsx/prettier без .cmd-обёрток (Windows)
Зачем: проект разрабатывается под 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-раннера).
2026-09-26 15:19:47 +07:00

51 lines
2.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Проверяемо на 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/);
});
});