Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd5194c365 | |||
| 71cc64b6d9 |
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-05 - 3.3.7 - fix(smartshell)
|
||||||
|
avoid triple shell nesting, improve WSL path filtering, and use chunked log buffer to reduce memory usage
|
||||||
|
|
||||||
|
- Spawn uses the executor shell binary directly (e.g. /bin/bash) and removes extra bash -c wrapping to avoid triple shell nesting
|
||||||
|
- Only filter out /mnt/c/ and other WSL-specific PATH entries when running under WSL (adds _isWSL detection)
|
||||||
|
- Replace single concatenated Buffer with chunked buffering in ShellLog (lazy concatenation, logLength property) and update checks to use logLength
|
||||||
|
- Remove unused dependency "tree-kill" from package.json
|
||||||
|
|
||||||
## 2026-03-04 - 3.3.6 - fix(smartshell)
|
## 2026-03-04 - 3.3.6 - fix(smartshell)
|
||||||
use 'close' event on child processes to ensure exit handling and update dependency versions
|
use 'close' event on child processes to ensure exit handling and update dependency versions
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartshell",
|
"name": "@push.rocks/smartshell",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "3.3.6",
|
"version": "3.3.7",
|
||||||
"description": "A library for executing shell commands using promises.",
|
"description": "A library for executing shell commands using promises.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
"@push.rocks/smartexit": "^2.0.3",
|
"@push.rocks/smartexit": "^2.0.3",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@types/which": "^3.0.4",
|
"@types/which": "^3.0.4",
|
||||||
"tree-kill": "^1.2.2",
|
|
||||||
"which": "^6.0.1"
|
"which": "^6.0.1"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -20,9 +20,6 @@ importers:
|
|||||||
'@types/which':
|
'@types/which':
|
||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
version: 3.0.4
|
version: 3.0.4
|
||||||
tree-kill:
|
|
||||||
specifier: ^1.2.2
|
|
||||||
version: 1.2.2
|
|
||||||
which:
|
which:
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.1
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartshell',
|
name: '@push.rocks/smartshell',
|
||||||
version: '3.3.6',
|
version: '3.3.7',
|
||||||
description: 'A library for executing shell commands using promises.'
|
description: 'A library for executing shell commands using promises.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ export class ShellEnv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if running under Windows Subsystem for Linux
|
||||||
|
*/
|
||||||
|
private static _isWSL(): boolean {
|
||||||
|
return !!(
|
||||||
|
process.env.WSL_DISTRO_NAME ||
|
||||||
|
process.env.WSLENV ||
|
||||||
|
(process.platform === 'linux' && process.env.PATH?.includes('/mnt/c/'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* imports path into the shell from env if available and returns it with
|
* imports path into the shell from env if available and returns it with
|
||||||
*/
|
*/
|
||||||
@@ -39,18 +50,16 @@ export class ShellEnv {
|
|||||||
commandPaths = commandPaths.concat(process.env.SMARTSHELL_PATH.split(':'));
|
commandPaths = commandPaths.concat(process.env.SMARTSHELL_PATH.split(':'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// lets filter for unwanted paths
|
// Only filter out WSL-specific paths when actually running under WSL
|
||||||
// Windows WSL
|
if (ShellEnv._isWSL()) {
|
||||||
commandPaths = commandPaths.filter((commandPathArg) => {
|
commandPaths = commandPaths.filter((commandPathArg) => {
|
||||||
const filterResult =
|
return (
|
||||||
!commandPathArg.startsWith('/mnt/c/') &&
|
!commandPathArg.startsWith('/mnt/c/') &&
|
||||||
!commandPathArg.startsWith('Files/1E') &&
|
!commandPathArg.startsWith('Files/1E') &&
|
||||||
!commandPathArg.includes(' ');
|
!commandPathArg.includes(' ')
|
||||||
if (!filterResult) {
|
);
|
||||||
// console.log(`${commandPathArg} will be filtered!`);
|
});
|
||||||
}
|
}
|
||||||
return filterResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
commandResult = `PATH=${commandPaths.join(':')} && ${commandStringArg}`;
|
commandResult = `PATH=${commandPaths.join(':')} && ${commandStringArg}`;
|
||||||
return commandResult;
|
return commandResult;
|
||||||
@@ -89,14 +98,12 @@ export class ShellEnv {
|
|||||||
}
|
}
|
||||||
pathString += ` && `;
|
pathString += ` && `;
|
||||||
|
|
||||||
switch (this.executor) {
|
// For both bash and sh executors, build the command string directly.
|
||||||
case 'bash':
|
// The shell nesting is handled by spawn() using the appropriate shell binary.
|
||||||
commandResult = `bash -c '${pathString}${sourceString}${commandArg}'`;
|
// Previously bash executor wrapped in `bash -c '...'` which caused triple
|
||||||
break;
|
// shell nesting (Node spawn sh -> bash -c -> command). Now spawn() uses
|
||||||
case 'sh':
|
// shell: '/bin/bash' directly, so we don't need the extra wrapping.
|
||||||
commandResult = `${pathString}${sourceString}${commandArg}`;
|
commandResult = `${pathString}${sourceString}${commandArg}`;
|
||||||
break;
|
|
||||||
}
|
|
||||||
commandResult = this._setPath(commandResult);
|
commandResult = this._setPath(commandResult);
|
||||||
return commandResult;
|
return commandResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,41 @@ import * as plugins from './plugins.js';
|
|||||||
* making sure the process doesn't run out of memory
|
* making sure the process doesn't run out of memory
|
||||||
*/
|
*/
|
||||||
export class ShellLog {
|
export class ShellLog {
|
||||||
public logStore = Buffer.from('');
|
private chunks: Buffer[] = [];
|
||||||
|
private totalLength = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the accumulated log as a single Buffer.
|
||||||
|
* Concatenation happens lazily only when accessed.
|
||||||
|
*/
|
||||||
|
public get logStore(): Buffer {
|
||||||
|
if (this.chunks.length === 0) {
|
||||||
|
return Buffer.alloc(0);
|
||||||
|
}
|
||||||
|
if (this.chunks.length === 1) {
|
||||||
|
return this.chunks[0];
|
||||||
|
}
|
||||||
|
// Flatten chunks into a single buffer
|
||||||
|
const combined = Buffer.concat(this.chunks, this.totalLength);
|
||||||
|
// Replace chunks array with the single combined buffer for future access
|
||||||
|
this.chunks = [combined];
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the log store directly (used for truncation).
|
||||||
|
*/
|
||||||
|
public set logStore(value: Buffer) {
|
||||||
|
this.chunks = [value];
|
||||||
|
this.totalLength = value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current total length of buffered data without concatenating.
|
||||||
|
*/
|
||||||
|
public get logLength(): number {
|
||||||
|
return this.totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* log data to console
|
* log data to console
|
||||||
@@ -22,13 +56,9 @@ export class ShellLog {
|
|||||||
*/
|
*/
|
||||||
public addToBuffer(dataArg: string | Buffer): void {
|
public addToBuffer(dataArg: string | Buffer): void {
|
||||||
// make sure we have the data as Buffer
|
// make sure we have the data as Buffer
|
||||||
const dataBuffer: Buffer = (() => {
|
const dataBuffer: Buffer = Buffer.isBuffer(dataArg) ? dataArg : Buffer.from(dataArg);
|
||||||
if (!Buffer.isBuffer(dataArg)) {
|
this.chunks.push(dataBuffer);
|
||||||
return Buffer.from(dataArg);
|
this.totalLength += dataBuffer.length;
|
||||||
}
|
|
||||||
return dataArg;
|
|
||||||
})();
|
|
||||||
this.logStore = Buffer.concat([this.logStore, dataBuffer]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public logAndAdd(dataArg: string | Buffer): void {
|
public logAndAdd(dataArg: string | Buffer): void {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export class Smartshell {
|
|||||||
|
|
||||||
if (!bufferExceeded) {
|
if (!bufferExceeded) {
|
||||||
shellLogInstance.addToBuffer(data);
|
shellLogInstance.addToBuffer(data);
|
||||||
if (shellLogInstance.logStore.length > maxBuffer) {
|
if (shellLogInstance.logLength > maxBuffer) {
|
||||||
bufferExceeded = true;
|
bufferExceeded = true;
|
||||||
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ export class Smartshell {
|
|||||||
|
|
||||||
if (!bufferExceeded) {
|
if (!bufferExceeded) {
|
||||||
shellLogInstance.addToBuffer(data);
|
shellLogInstance.addToBuffer(data);
|
||||||
if (shellLogInstance.logStore.length > maxBuffer) {
|
if (shellLogInstance.logLength > maxBuffer) {
|
||||||
bufferExceeded = true;
|
bufferExceeded = true;
|
||||||
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
||||||
}
|
}
|
||||||
@@ -338,8 +338,12 @@ export class Smartshell {
|
|||||||
return await this._execCommandPty(options, commandToExecute, shellLogInstance);
|
return await this._execCommandPty(options, commandToExecute, shellLogInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the executor's shell binary directly to avoid triple shell nesting.
|
||||||
|
// Previously: Node spawn(shell:true) → /bin/sh → bash -c → command (3 layers)
|
||||||
|
// Now: Node spawn(shell:bash) → command (1 layer)
|
||||||
|
const shellBinary = this.shellEnv.executor === 'bash' ? '/bin/bash' : true;
|
||||||
const execChildProcess = cp.spawn(commandToExecute, [], {
|
const execChildProcess = cp.spawn(commandToExecute, [], {
|
||||||
shell: true,
|
shell: shellBinary,
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
env: options.env || process.env,
|
env: options.env || process.env,
|
||||||
detached: true, // Own process group — immune to terminal SIGINT, managed by smartexit
|
detached: true, // Own process group — immune to terminal SIGINT, managed by smartexit
|
||||||
@@ -405,7 +409,7 @@ export class Smartshell {
|
|||||||
|
|
||||||
if (!bufferExceeded) {
|
if (!bufferExceeded) {
|
||||||
shellLogInstance.addToBuffer(data);
|
shellLogInstance.addToBuffer(data);
|
||||||
if (shellLogInstance.logStore.length > maxBuffer) {
|
if (shellLogInstance.logLength > maxBuffer) {
|
||||||
bufferExceeded = true;
|
bufferExceeded = true;
|
||||||
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
||||||
}
|
}
|
||||||
@@ -426,7 +430,7 @@ export class Smartshell {
|
|||||||
|
|
||||||
if (!bufferExceeded) {
|
if (!bufferExceeded) {
|
||||||
shellLogInstance.addToBuffer(data);
|
shellLogInstance.addToBuffer(data);
|
||||||
if (shellLogInstance.logStore.length > maxBuffer) {
|
if (shellLogInstance.logLength > maxBuffer) {
|
||||||
bufferExceeded = true;
|
bufferExceeded = true;
|
||||||
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
shellLogInstance.logStore = Buffer.from('[Output truncated - exceeded maxBuffer]');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user