Skip to content

Commit 2289122

Browse files
committed
fix: linear output from function commands
1 parent 43e8145 commit 2289122

File tree

13 files changed

+275
-228
lines changed

13 files changed

+275
-228
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ module.exports = {
1111
'@typescript-eslint/no-explicit-any': 'off',
1212
'@typescript-eslint/no-empty-interface': 'off',
1313
'prettier/prettier': 'warn',
14+
'@typescript-eslint/no-this-alias': 'off',
1415
},
1516
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "@schummar/runp",
3+
"type": "commonjs",
34
"version": "0.0.0-develop",
45
"description": "Neat parallel task execution",
56
"keywords": [
@@ -45,7 +46,7 @@
4546
"@vitejs/plugin-react": "4.3.3",
4647
"chalk": "^5.3.0",
4748
"cleye": "1.3.2",
48-
"cross-state": "0.41.3",
49+
"cross-state": "0.48.0",
4950
"esbuild": "0.24.0",
5051
"eslint": "8.57.0",
5152
"eslint-config-prettier": "9.1.0",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/renderTaskList.tsx

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,12 @@
11
import { RenderOptions, createRoot } from '@schummar/react-terminal';
2-
import { Store } from 'cross-state';
3-
import { Task, TaskState } from '../task';
2+
import { Task } from '../task';
43
import { TaskList } from './taskList';
5-
6-
export type WriteLineGrouped = (text: string, taskState: Store<TaskState>) => void;
4+
import { trackLinearOutput } from '../trackLinearOutput';
75

86
export function renderTaskList(tasks: Task[], options?: RenderOptions) {
97
const { render, writeLine } = createRoot(options);
108

11-
let currentTask: Store<TaskState> | undefined;
12-
13-
const endTask = () => {
14-
if (!currentTask) {
15-
return;
16-
}
17-
18-
writeLine('');
19-
writeLine(`<- [${currentTask.get().title}] --`, {
20-
grow: 1,
21-
shrink: 1,
22-
ellipsis: true,
23-
bold: true,
24-
backgroundColor: 'blue',
25-
});
26-
};
27-
28-
const writeLineGrouped: WriteLineGrouped = (text, taskState) => {
29-
if (taskState !== currentTask) {
30-
endTask();
31-
32-
writeLine('');
33-
writeLine(`-- [${taskState.get().title}] ->`, {
34-
grow: 1,
35-
shrink: 1,
36-
ellipsis: true,
37-
bold: true,
38-
backgroundColor: 'blue',
39-
});
40-
writeLine('');
41-
}
42-
43-
currentTask = taskState;
44-
writeLine(text);
45-
};
46-
47-
tasks.forEach((task) =>
48-
task.result
49-
.catch(() => undefined)
50-
.finally(() => {
51-
if (task.state === currentTask) {
52-
endTask();
53-
currentTask = undefined;
54-
}
55-
}),
56-
);
9+
trackLinearOutput(tasks, writeLine);
5710

58-
return render(<TaskList tasks={tasks} writeLine={writeLineGrouped} />);
11+
return render(<TaskList tasks={tasks} />);
5912
}

src/components/taskList.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { Paragraph } from '@schummar/react-terminal';
22
import { Task } from '../task';
3-
import { WriteLineGrouped } from './renderTaskList';
43
import { TaskListEntry } from './taskListEntry';
54

6-
export function TaskList({ tasks, writeLine }: { tasks: Task[]; writeLine: WriteLineGrouped }) {
5+
export function TaskList({ tasks }: { tasks: Task[] }) {
76
return (
87
<Paragraph margin={[1, 0, 0, 0]}>
98
{tasks.map((task, index) => (
10-
<TaskListEntry key={index} writeLine={writeLine} {...task} />
9+
<TaskListEntry key={index} {...task} />
1110
))}
1211
</Paragraph>
1312
);

src/components/taskListEntry.tsx

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import { Paragraph, Text } from '@schummar/react-terminal';
22
import { useStore } from 'cross-state/react';
3-
import { useEffect } from 'react';
43
import { statusIcons } from '../statusIcons';
54
import { Task } from '../task';
6-
import { WriteLineGrouped } from './renderTaskList';
75
import { Spinner } from './spinner';
86

9-
export function TaskListEntry({
10-
command: { keepOutput, forever, outputLength, displayTimeOver = -Infinity, linearOutput },
11-
state,
12-
writeLine,
13-
}: Task & { writeLine: WriteLineGrouped }) {
7+
export function TaskListEntry({ command: { keepOutput, forever, outputLength, displayTimeOver = -Infinity, linearOutput }, state }: Task) {
148
const status = useStore(state, (x) => x.status);
159
const statusString = useStore(state, (x) => x.statusString);
1610
const title = useStore(state, (x) => x.title);
@@ -31,24 +25,6 @@ export function TaskListEntry({
3125
return undefined;
3226
});
3327

34-
useEffect(() => {
35-
if (!linearOutput) {
36-
return;
37-
}
38-
39-
let offset = 0;
40-
41-
return state
42-
.map((x) => x.output)
43-
.subscribe((output) => {
44-
const newOutput = output.slice(offset);
45-
if (newOutput) {
46-
writeLine(newOutput, state);
47-
}
48-
offset = output.length;
49-
});
50-
}, [linearOutput, state]);
51-
5228
return (
5329
<Paragraph>
5430
{statusString !== undefined ? (
@@ -92,7 +68,7 @@ export function TaskListEntry({
9268

9369
{subTasks?.map((task, index) => (
9470
<Paragraph key={index} margin={[0, 0, 0, 2]}>
95-
<TaskListEntry writeLine={writeLine} {...task} />
71+
<TaskListEntry {...task} />
9672
</Paragraph>
9773
))}
9874
</Paragraph>

src/index.ts

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { renderTaskList } from './components/renderTaskList';
66
import { loadNpmWorkspaceScripts } from './npmUtils';
77
import { statusIcons } from './statusIcons';
88
import { Task, task } from './task';
9+
import { trackLinearOutput } from './trackLinearOutput';
910
import { formatTime, indent } from './util';
1011
import wildcardMatch from './wildcardMatch';
1112

@@ -58,9 +59,12 @@ export interface RunpCommandRaw extends Omit<Partial<RunpCommand>, 'args' | 'dep
5859
dependsOn?: string | number | Array<string | number>;
5960
}
6061

61-
export interface RunpOptions extends RunpCommonOptions {
62+
type Commands = (string | [cmd: string, ...args: string[]] | RunpCommandRaw | false | undefined | null)[];
63+
export type RunpResult = { result: 'success'; output: string } | { result: 'error'; output: string };
64+
65+
export interface RunpOptions<TCommands extends Commands = Commands> extends RunpCommonOptions {
6266
/** A list of command to execute in parallel */
63-
commands: (string | [cmd: string, ...args: string[]] | RunpCommandRaw | false | undefined | null)[];
67+
commands: TCommands;
6468
/** Maximum number of parallel tasks */
6569
parallelTasks?: number;
6670
target?: RenderOptions['target'];
@@ -72,7 +76,9 @@ export const RUNP_TASK_DELEGATE = `__runp_task__${RUNP_TASK_V}__`;
7276

7377
const switchRegexp = /s|p|f(=(true|false))?|k(=(true|false))?|n=\d+/g;
7478

75-
export async function runp(options: RunpOptions) {
79+
export async function runp<const TCommands extends Commands = Commands>(
80+
options: RunpOptions<TCommands>,
81+
): Promise<{ [K in keyof TCommands]: RunpResult }> {
7682
const resolvedCommands = await resolveCommands(options);
7783

7884
if (process.env.RUNP === RUNP_TASK_V) {
@@ -98,32 +104,12 @@ export async function runp(options: RunpOptions) {
98104
if (process.stdout.isTTY || options.target) {
99105
stop = renderTTY(tasks, options.target);
100106
} else {
101-
await renderNonTTY(tasks);
107+
stop = await renderNonTTY(tasks);
102108
}
103109

104-
const results = await Promise.all(
105-
tasks.map((task) =>
106-
task.result
107-
.then(
108-
(output) =>
109-
({
110-
result: 'success',
111-
output,
112-
}) as const,
113-
)
114-
.catch(
115-
(output: string) =>
116-
({
117-
result: 'error',
118-
output,
119-
}) as const,
120-
),
121-
),
122-
);
123-
110+
const results = await Promise.all(tasks.map((task) => task.result));
124111
stop?.();
125-
126-
return results;
112+
return results as { [K in keyof TCommands]: RunpResult };
127113
}
128114

129115
export async function resolveCommands(options: RunpOptions) {
@@ -324,15 +310,14 @@ function renderTTY(tasks: ReturnType<typeof task>[], target?: RenderOptions['tar
324310
}
325311

326312
async function renderNonTTY(tasks: ReturnType<typeof task>[], margin = 0) {
327-
for (const {
328-
command: { keepOutput, displayTimeOver = -Infinity },
329-
result,
330-
state,
331-
} of tasks) {
332-
await result.catch(() => undefined);
313+
trackLinearOutput(tasks, (line: string) => console.log(line));
314+
315+
for (const { command, result, state } of tasks) {
316+
const { linearOutput, keepOutput, displayTimeOver = -Infinity } = command;
317+
await result;
333318
const { status, statusString = statusIcons[status], title, subTasks, time } = state.get();
334319
const output = state.get().output.trim();
335-
const showOutput = (status === 'error' || keepOutput) && output.length > 0;
320+
const showOutput = !linearOutput && (status === 'error' || keepOutput) && output.length > 0;
336321
const timeString = time !== undefined && time >= displayTimeOver ? ` [${formatTime(time)}]` : '';
337322

338323
console.info(indent(`${statusString} ${title}${timeString}`, margin));

src/npmUtils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export async function loadNpmPackage(pkg: string): Promise<{ name: string; scrip
1919
}
2020
}
2121

22+
const supportedNpmRunners = ['npm', 'pnpm', 'yarn'] as const;
23+
2224
export async function loadNpmWorkspaceScripts(cwd: string): Promise<{ scriptName: string; scriptCommand: (args: string[]) => string[] }[]> {
2325
const npmRunner = await whichNpmRunner(cwd);
2426
const workspaceRoot = resolve(cwd);
@@ -67,6 +69,10 @@ export async function loadNpmWorkspaceScripts(cwd: string): Promise<{ scriptName
6769
}
6870

6971
export async function whichNpmRunner(cwd: string) {
72+
if ((supportedNpmRunners as unknown as string[]).includes(process.env.RUNP_PACKAGE_MANAGER ?? '')) {
73+
return process.env.RUNP_PACKAGE_MANAGER as (typeof supportedNpmRunners)[number];
74+
}
75+
7076
if (process.env.npm_config_user_agent?.startsWith('yarn/')) {
7177
return 'yarn';
7278
} else if (process.env.npm_config_user_agent?.startsWith('pnpm/')) {

0 commit comments

Comments
 (0)