feat(smartshell): add cwd-aware execution options, structured strict-mode errors, and safer process tree termination

This commit is contained in:
2026-05-09 13:48:16 +00:00
parent e61b352576
commit d65e1ed4f6
17 changed files with 3830 additions and 4812 deletions
+82 -2
View File
@@ -1,5 +1,8 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartshell from '../ts/index.js';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
tap.test('execSpawn should execute commands with args array (shell:false)', async () => {
const testSmartshell = new smartshell.Smartshell({
@@ -13,6 +16,41 @@ tap.test('execSpawn should execute commands with args array (shell:false)', asyn
expect(result.stdout).toContain('Hello World');
});
tap.test('execSpawn should run in the configured cwd', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'smartshell-cwd-'));
try {
const result = await testSmartshell.execSpawn('node', ['-e', 'console.log(process.cwd())'], {
cwd: tmpDir,
silent: true,
});
expect(result.exitCode).toEqual(0);
expect(result.stdout.trim()).toEqual(tmpDir);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
tap.test('execSilent should run shell commands in the configured cwd', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'smartshell-shell-cwd-'));
try {
const result = await testSmartshell.execSilent('pwd', { cwd: tmpDir });
expect(result.exitCode).toEqual(0);
expect(result.stdout.trim()).toEqual(tmpDir);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
tap.test('execSpawn should handle command not found errors', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
@@ -24,7 +62,7 @@ tap.test('execSpawn should handle command not found errors', async () => {
await testSmartshell.execSpawn('nonexistentcommand123', ['arg1']);
} catch (error) {
errorThrown = true;
expect(error.code).toEqual('ENOENT');
expect((error as NodeJS.ErrnoException).code).toEqual('ENOENT');
}
expect(errorThrown).toBeTrue();
});
@@ -99,6 +137,48 @@ tap.test('execSpawn with timeout should terminate process', async () => {
expect(result.signal).toBeTruthy(); // Should have been killed by signal
});
tap.test('execSpawn timeout should terminate the spawned process tree', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const markerPath = path.join(os.tmpdir(), `smartshell-spawn-timeout-${process.pid}-${Date.now()}`);
try {
const result = await testSmartshell.execSpawn(
'bash',
['-c', `(sleep 0.6; touch "${markerPath}") & wait`],
{ timeout: 100, silent: true },
);
await new Promise((resolve) => setTimeout(resolve, 800));
expect(result.exitCode).not.toEqual(0);
expect(fs.existsSync(markerPath)).toBeFalse();
} finally {
fs.rmSync(markerPath, { force: true });
}
});
tap.test('exec timeout should terminate the shell process tree', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const markerPath = path.join(os.tmpdir(), `smartshell-shell-timeout-${process.pid}-${Date.now()}`);
try {
const result = await testSmartshell.execSilent(`(sleep 0.6; touch "${markerPath}") & wait`, {
timeout: 100,
});
await new Promise((resolve) => setTimeout(resolve, 800));
expect(result.exitCode).not.toEqual(0);
expect(fs.existsSync(markerPath)).toBeFalse();
} finally {
fs.rmSync(markerPath, { force: true });
}
});
tap.test('execSpawn with maxBuffer should truncate output', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
@@ -147,4 +227,4 @@ tap.test('execSpawn with signal should report signal in result', async () => {
expect(result.signal).toEqual('SIGTERM');
});
export default tap.start();
export default tap.start();