Skip to content

Commit 3de6601

Browse files
committed
Fix Node.js deprecation warnings about passing args to child_process
1 parent b513e88 commit 3de6601

File tree

4 files changed

+59
-38
lines changed

4 files changed

+59
-38
lines changed

bin/sass.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,31 @@
22

33
import * as child_process from 'child_process';
44
import * as path from 'path';
5+
56
import {compilerCommand} from '../lib/src/compiler-path';
67

78
// TODO npm/cmd-shim#152 and yarnpkg/berry#6422 - If and when the package
89
// managers support it, we should make this a proper shell script rather than a
910
// JS wrapper.
1011

1112
try {
12-
child_process.execFileSync(
13-
compilerCommand[0],
14-
[...compilerCommand.slice(1), ...process.argv.slice(2)],
15-
{
16-
// Node blocks launching .bat and .cmd without a shell due to CVE-2024-27980
17-
shell: ['.bat', '.cmd'].includes(
18-
path.extname(compilerCommand[0]).toLowerCase(),
19-
),
20-
stdio: 'inherit',
21-
windowsHide: true,
22-
},
23-
);
13+
let command = compilerCommand[0];
14+
let args = [...compilerCommand.slice(1), ...process.argv.slice(2)];
15+
const options: child_process.ExecFileSyncOptions = {
16+
stdio: 'inherit',
17+
windowsHide: true,
18+
};
19+
20+
// Node forbids launching .bat and .cmd without a shell due to CVE-2024-27980,
21+
// and DEP0190 forbids passing an argument list *with* shell: true. To work
22+
// around this, we have to manually concatenate the arguments.
23+
if (['.bat', '.cmd'].includes(path.extname(command).toLowerCase())) {
24+
command = `${command} ${args.join(' ')}`;
25+
args = [];
26+
options.shell = true;
27+
}
28+
29+
child_process.execFileSync(command, args, options);
2430
} catch (error) {
2531
if (error.code) {
2632
throw error;

lib/src/compiler/async.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5-
import {spawn} from 'child_process';
5+
import * as child_process from 'child_process';
6+
67
import {Observable} from 'rxjs';
78
import {takeUntil} from 'rxjs/operators';
89

@@ -36,21 +37,28 @@ const initFlag = Symbol();
3637
/** An asynchronous wrapper for the embedded Sass compiler */
3738
export class AsyncCompiler {
3839
/** The underlying process that's being wrapped. */
39-
private readonly process = spawn(
40-
compilerCommand[0],
41-
[...compilerCommand.slice(1), '--embedded'],
42-
{
40+
private readonly process = (() => {
41+
let command = compilerCommand[0];
42+
let args = [...compilerCommand.slice(1), '--embedded'];
43+
const options: child_process.SpawnOptions = {
4344
// Use the command's cwd so the compiler survives the removal of the
4445
// current working directory.
4546
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1438712923
4647
cwd: path.dirname(compilerCommand[0]),
47-
// Node blocks launching .bat and .cmd without a shell due to CVE-2024-27980
48-
shell: ['.bat', '.cmd'].includes(
49-
path.extname(compilerCommand[0]).toLowerCase(),
50-
),
5148
windowsHide: true,
52-
},
53-
);
49+
};
50+
51+
// Node forbids launching .bat and .cmd without a shell due to CVE-2024-27980,
52+
// and DEP0190 forbids passing an argument list *with* shell: true. To work
53+
// around this, we have to manually concatenate the arguments.
54+
if (['.bat', '.cmd'].includes(path.extname(command).toLowerCase())) {
55+
command = `${command} ${args!.join(' ')}`;
56+
args = [];
57+
options.shell = true;
58+
}
59+
60+
return child_process.spawn(command, args, options);
61+
})();
5462

5563
/** The next compilation ID. */
5664
private compilationId = 1;
@@ -73,17 +81,17 @@ export class AsyncCompiler {
7381

7482
/** The buffers emitted by the child process's stdout. */
7583
private readonly stdout$ = new Observable<Buffer>(observer => {
76-
this.process.stdout.on('data', buffer => observer.next(buffer));
84+
this.process.stdout!.on('data', buffer => observer.next(buffer));
7785
}).pipe(takeUntil(this.exit$));
7886

7987
/** The buffers emitted by the child process's stderr. */
8088
private readonly stderr$ = new Observable<Buffer>(observer => {
81-
this.process.stderr.on('data', buffer => observer.next(buffer));
89+
this.process.stderr!.on('data', buffer => observer.next(buffer));
8290
}).pipe(takeUntil(this.exit$));
8391

8492
/** Writes `buffer` to the child process's stdin. */
8593
private writeStdin(buffer: Buffer): void {
86-
this.process.stdin.write(buffer);
94+
this.process.stdin!.write(buffer);
8795
}
8896

8997
/** Guards against using a disposed compiler. */
@@ -190,7 +198,7 @@ export class AsyncCompiler {
190198
async dispose(): Promise<void> {
191199
this.disposed = true;
192200
await Promise.all(this.compilations);
193-
this.process.stdin.end();
201+
this.process.stdin!.end();
194202
await this.exit$;
195203
}
196204
}

lib/src/compiler/sync.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,28 @@ const initFlag = Symbol();
3636
/** A synchronous wrapper for the embedded Sass compiler */
3737
export class Compiler {
3838
/** The underlying process that's being wrapped. */
39-
private readonly process = new SyncChildProcess(
40-
compilerCommand[0],
41-
[...compilerCommand.slice(1), '--embedded'],
42-
{
39+
private readonly process = (() => {
40+
let command = compilerCommand[0];
41+
let args = [...compilerCommand.slice(1), '--embedded'];
42+
const options: child_process.SpawnOptions = {
4343
// Use the command's cwd so the compiler survives the removal of the
4444
// current working directory.
4545
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1438712923
4646
cwd: path.dirname(compilerCommand[0]),
47-
// Node blocks launching .bat and .cmd without a shell due to CVE-2024-27980
48-
shell: ['.bat', '.cmd'].includes(
49-
path.extname(compilerCommand[0]).toLowerCase(),
50-
),
5147
windowsHide: true,
52-
},
53-
);
48+
};
49+
50+
// Node forbids launching .bat and .cmd without a shell due to CVE-2024-27980,
51+
// and DEP0190 forbids passing an argument list *with* shell: true. To work
52+
// around this, we have to manually concatenate the arguments.
53+
if (['.bat', '.cmd'].includes(path.extname(command).toLowerCase())) {
54+
command = `${command} ${args!.join(' ')}`;
55+
args = [];
56+
options.shell = true;
57+
}
58+
59+
return new SyncChildProcess(command, args, options);
60+
})();
5461

5562
/** The next compilation ID. */
5663
private compilationId = 1;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sass-embedded",
3-
"version": "1.93.2",
3+
"version": "1.93.3-dev",
44
"protocol-version": "3.2.0",
55
"compiler-version": "1.93.2",
66
"description": "Node.js library that communicates with Embedded Dart Sass using the Embedded Sass protocol",

0 commit comments

Comments
 (0)