Skip to content

Commit eca1175

Browse files
stesieMarcScheib
authored andcommitted
feat: allow to capture command output to variable
1 parent 9a70690 commit eca1175

5 files changed

Lines changed: 102 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ Each step consists of the following properties:
275275

276276
- `type`: the type of the step; can be one of the following
277277
- `FORMULA`: a JavaScript formula that is executed in this step; executed via `eval`
278-
- `COMMAND`: execute a command; the command is executed via NodeJS `child_process.execSync`
278+
- `COMMAND`: execute a command; the command is executed via NodeJS `child_process.execSync`; if used in combination with `resultVariable` command output is captured
279279
- `EXECUTOR`: a special step that is connected to some build-in executor; see [Available executors](#available-executors)
280280
- `USE_CASE`: call another use case
281281
- `PROMPT`: trigger a prompt for user input

src/services/use-cases/use-case-runner.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { temporaryDirectory } from 'tempy';
33
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
44
import { CONFIG, initConfig } from '../../config.js';
55
import { UseCase } from '../../types/use-case.js';
6+
import * as utils from '../../utils/index.js';
67
import { Logger, TestBed } from '../../utils/index.js';
78
import { TemplatesAccess } from '../access/templates-access.js';
89
import { RepositoriesRepository } from '../repositories.repository.js';
@@ -174,6 +175,43 @@ describe('UseCaseRunner', () => {
174175
expect(spy).not.toHaveBeenCalledWith('a === b', expect.any(Object));
175176
});
176177

178+
describe('type COMMAND', () => {
179+
it('should not capture output unless resultVariable is set', async () => {
180+
const spy = vi
181+
.spyOn(utils, 'execCommand')
182+
.mockReturnValue(Promise.resolve(''));
183+
184+
useCase.steps = [
185+
{
186+
type: 'COMMAND',
187+
command: '`do stuff`',
188+
},
189+
];
190+
191+
await sut.run(useCase);
192+
193+
expect(spy).toHaveBeenCalledWith('do stuff', expect.any(String), false);
194+
});
195+
196+
it('should capture the output if resultVariable is set', async () => {
197+
const spy = vi
198+
.spyOn(utils, 'execCommand')
199+
.mockReturnValue(Promise.resolve('hello world'));
200+
useCase.steps = [
201+
{
202+
type: 'COMMAND',
203+
command: '`do stuff`',
204+
resultVariable: 'myVar',
205+
},
206+
];
207+
208+
const context = await sut.run(useCase);
209+
210+
expect(spy).toHaveBeenCalledWith('do stuff', expect.any(String), true);
211+
expect(context.myVar).toBe('hello world');
212+
});
213+
});
214+
177215
describe('type FORMULA', () => {
178216
it('should error; formula missing', async () => {
179217
useCase.steps = [

src/services/use-cases/use-case-runner.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,16 @@ export class UseCaseRunner {
173173
step.command!,
174174
context
175175
);
176-
await execCommand(command, this.templatesAccess.getWorkspacePath());
176+
const result = await execCommand(
177+
command,
178+
this.templatesAccess.getWorkspacePath(),
179+
!!step.resultVariable
180+
);
181+
182+
if (step.resultVariable) {
183+
return { ...context, [step.resultVariable]: result };
184+
}
185+
177186
return context;
178187
});
179188
}

src/utils/processes.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { execSync } from 'node:child_process';
2+
import { afterEach, describe, expect, Mock, test, vi } from 'vitest';
3+
4+
import { execCommand } from './processes.js';
5+
6+
vi.mock('node:child_process', () => ({
7+
execSync: vi.fn(),
8+
}));
9+
10+
describe('processes', () => {
11+
afterEach(() => {
12+
vi.clearAllMocks();
13+
});
14+
15+
describe('execCommand', () => {
16+
test('should return the output if captureStdout is true', async () => {
17+
const command = 'ls -la';
18+
const cwd = '/';
19+
const expectedOutput = 'total 0';
20+
(execSync as Mock).mockReturnValue(expectedOutput);
21+
22+
const output = await execCommand(command, cwd, true);
23+
24+
expect(execSync).toHaveBeenCalledWith(command, {
25+
cwd,
26+
encoding: 'utf-8',
27+
stdio: 'pipe',
28+
});
29+
expect(output).toBe(expectedOutput);
30+
});
31+
32+
test('should run with stdio=inherit if captureStdout is false', async () => {
33+
const command = 'ls -la';
34+
const cwd = '/';
35+
(execSync as Mock).mockReturnValue('');
36+
37+
await execCommand(command, cwd, false);
38+
39+
expect(execSync).toHaveBeenCalledWith(command, {
40+
cwd,
41+
encoding: 'utf-8',
42+
stdio: 'inherit',
43+
});
44+
});
45+
});
46+
});

src/utils/processes.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ import { execSync } from 'node:child_process';
22

33
export const OS = process.platform;
44

5-
export async function execCommand(command: string, cwd: string) {
5+
export async function execCommand(
6+
command: string,
7+
cwd: string,
8+
captureStdout = false
9+
) {
610
return execSync(command, {
711
cwd,
8-
stdio: 'inherit',
12+
encoding: 'utf-8',
13+
stdio: captureStdout ? 'pipe' : 'inherit',
914
});
1015
}

0 commit comments

Comments
 (0)