Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/bootstraps/initiate_proc_manager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import tty from 'tty';
import { NotiosConfig } from '../../libs/notios-config/src/interfaces/notios-config';
import type { UiOptions } from '../interfaces/ui_options';
import detectNpmClient from '../utils/detect_npm_client';
Expand All @@ -9,8 +10,14 @@ export interface InitiateProcManagerParams {
notiosConfig: NotiosConfig;
}
const initiateProcManager = ({ uiOptions, notiosConfig }: InitiateProcManagerParams): ProcManager => {
const isColorSupported =
!('NO_COLOR' in process.env || uiOptions.forceNoColor) &&
('FORCE_COLOR' in process.env ||
process.platform === 'win32' ||
(tty.isatty(1) && process.env.TERM !== 'dumb') ||
'CI' in process.env);
const procManager = createProcManager({
forceNoColor: uiOptions.forceNoColor,
isColorSupported,
enableUnreadMarker: notiosConfig.v1.enableUnreadMarker,
historyAlwaysKeepHeadSize: notiosConfig.v1.historyAlwaysKeepHeadSize,
historyCacheSize: notiosConfig.v1.historyCacheSize,
Expand Down
59 changes: 45 additions & 14 deletions src/utils/proc_manager.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import { AnsiParser } from 'ansi-parser/interfaces/ansi-parser';
import { decodeAnsiBytes, defaultAnsiParser } from 'ansi-parser/src';
import type childProcess from 'child_process';
import cp from 'cross-spawn';
import tty from 'tty';
import ttyGlobal from 'tty';
import { envVarNames } from '../constants/ipc';
import crossKill from './cross_kill';
import crossKillGlobal from './cross_kill';
import { applyActionToSty, defaultStyContext, restoreSty, StyContext } from './sty';

interface CrossSpawnParams {
command: string;
args: string[];
cwd?: string;
env?: NodeJS.ProcessEnv | undefined;
}
type CrossProcessReadable = {
readonly on: (name: 'data', handler: (buf: Buffer) => void) => void;
readonly off: (name: 'data', handler: (buf: Buffer) => void) => void;
};
type CrossProcess = {
readonly pid?: number | undefined;
readonly once: (name: 'exit', handler: (exitCode: number | null) => void) => void;
readonly stdout: CrossProcessReadable;
readonly stderr: CrossProcessReadable;
};
export type CrossSpawn = (params: CrossSpawnParams) => CrossProcess;

export type ProcStatus = 'waiting' | 'running' | 'killed' | 'finished';

export interface ProcOwn {
Expand All @@ -19,7 +36,8 @@ export interface ProcOwnInternal {
command: string;
cwd: string;
npmPath: string;
$raw?: childProcess.ChildProcessWithoutNullStreams;
// $raw?: childProcess.ChildProcessWithoutNullStreams;
$raw?: CrossProcess;
}

export interface LogOwn {
Expand Down Expand Up @@ -168,24 +186,35 @@ export const createEmptyLogAccumulated = (): LogAccumulated => {
return $createEmptyLogAccumulated();
};

const crossSpawnGlobal: CrossSpawn = ({ env, cwd, args, command }) => {
return cp.spawn(command, args, {
stdio: ['pipe', 'pipe', 'pipe'],
env,
cwd,
});
};

export interface CreateProcManagerParams {
forceNoColor: boolean;
isColorSupported: boolean;
enableUnreadMarker: boolean;
historyAlwaysKeepHeadSize: number;
historyCacheSize: number;

// for test
tty?: typeof ttyGlobal;
crossSpawn?: CrossSpawn;
crossKill?: typeof crossKillGlobal;
}
export const createProcManager = ({
forceNoColor,
isColorSupported,
enableUnreadMarker,
historyAlwaysKeepHeadSize,
historyCacheSize,
crossKill: crossKill0,
crossSpawn: crossSpawn0,
}: CreateProcManagerParams): ProcManager => {
const isColorSupported =
!('NO_COLOR' in process.env || forceNoColor) &&
('FORCE_COLOR' in process.env ||
process.platform === 'win32' ||
(tty.isatty(1) && process.env.TERM !== 'dumb') ||
'CI' in process.env);
const crossSpawn: CrossSpawn = crossSpawn0 ?? crossSpawnGlobal;
const crossKill = crossKill0 ?? crossKillGlobal;

const RESET = isColorSupported ? '\x1b[0m' : '';
const YELLOW = isColorSupported ? '\x1b[33m' : '';
Expand Down Expand Up @@ -496,8 +525,10 @@ export const createProcManager = ({
nodeStderr.parent = node;
$notifyUpdate();

const p = cp.spawn(node.procOwn.npmPath, ['run', node.name], {
stdio: ['pipe', 'pipe', 'pipe'],
const p = crossSpawn({
command: node.procOwn.npmPath,
args: ['run', node.name],
// stdio: ['pipe', 'pipe', 'pipe'],
cwd: node.procOwn.cwd,
env: {
...process.env,
Expand Down
86 changes: 86 additions & 0 deletions test/proc_manager/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, expect, test } from 'vitest';
import { createProcManager, LogLinesReadonly } from '../../src/utils/proc_manager';

const SCRIPT_NAME_1 = 'test1';
const SCRIPT_COMMAND_1 = 'cmd1';
const SCRIPT_NAME_2 = 'test2';
const SCRIPT_COMMAND_2 = 'cmd2';
const SCRIPT_NAME_3 = 'test3';
const SCRIPT_COMMAND_3 = 'cmd3';

const fakeCwd = '/fakeCwd';
const fakeNpmClient = 'fake-npm';

const logLinesToStrLines = (logLines: LogLinesReadonly): string[] => {
// TODO
};

describe('todo', () => {
test('todo1', () => {
const c = createXXX();
c.addFakeProc(SCRIPT_NAME_1, (h) => [h.strLn('1abc1'), h.strLn('1def1'), h.end()]);
c.addFakeProc(SCRIPT_NAME_2, (h) => [h.strLn('2abc2'), h.strLn('2def2'), h.end()]);
c.addFakeProc(SCRIPT_NAME_3, (h) => [h.bufLn(Buffer.from([0x03, 0x13, 0x23, 0x33, 0x43])), h.end()]);
const pm = createProcManager({
historyCacheSize: 3,
historyAlwaysKeepHeadSize: 3,
enableUnreadMarker: true,
isColorSupported: true,

crossKill: c.fakeCrossKill,
crossSpawn: c.fakeCrossSpawn,
});
pm.createNode({
name: SCRIPT_NAME_1,
type: 'none',
procOwn: {
command: SCRIPT_COMMAND_1,
cwd: fakeCwd,
npmPath: fakeNpmClient,
},
status: 'running',
});
c.tick();
expect(pm.rootNode.logAccumulated.lineCount).toBe(2);
expect(logLinesToStrLines(pm.rootNode.logAccumulated.lines)).toBe(['1abc1', '1def1']);
expect(c.calls).toBe([
{
name: SCRIPT_NAME_1,
command: SCRIPT_COMMAND_1,
cwd: fakeCwd,
npmPath: fakeNpmClient,
},
]);
});

test('todo2', () => {
const fakeCwd = '/fakeCwd';
const fakeNpmClient = 'fake-npm';
const c = createXXX();
c.addFakeProc(SCRIPT_NAME_1, (h) => [h.strLn('1abc1'), h.strLn('1def1'), h.end()]);
c.addFakeProc(SCRIPT_NAME_2, (h) => [h.strLn('2abc2'), h.strLn('2def2'), h.end()]);
c.addFakeProc(SCRIPT_NAME_3, (h) => [h.bufLn(Buffer.from([0x03, 0x13, 0x23, 0x33, 0x43])), h.end()]);
const pm = createProcManager({
historyCacheSize: 3,
historyAlwaysKeepHeadSize: 3,
enableUnreadMarker: true,
isColorSupported: true,

crossKill: c.fakeCrossKill,
crossSpawn: c.fakeCrossSpawn,
});
pm.createNode({
name: SCRIPT_NAME_3,
type: 'none',
procOwn: {
command: SCRIPT_COMMAND_3,
cwd: fakeCwd,
npmPath: fakeNpmClient,
},
status: 'running',
});
c.tick();
expect(pm.rootNode.logAccumulated.lineCount).toBe(2);
expect(logLinesToStrLines(pm.rootNode.logAccumulated.lines)).toBe(['\u0023\u0033\u0043']);
});
});