Repository: sindresorhus/execa (js)

Issue:
feature request: shorter $.sync
Hello, and thanks for the library.

I love the `$.sync` command, but it is too long. I want to make scripts that are nice and short.

e.g.

```
// Bad
await $`foo`;
await $`bar`;
await $`baz`;

// Also bad
$.sync`foo`;
$.sync`bar`;
$.sync`baz`;
```

I propose a default alias for `$.sync` as `s`.

```
// Good!
s`foo`;
s`bar`;
s`baz`;
```

I know that I can do this for my own projects by wrapping `$.sync` and exporting it, but I think this feature should be for all users!

Pull request description:
Add `$.s` as an alias for `$.sync`
Fixes #581.

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ef4d80e..a594b3e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -26,4 +26,4 @@ jobs:
       - uses: codecov/codecov-action@v3
         if: matrix.os == 'ubuntu-latest' && matrix.node-version == 20
         with:
-          fail_ci_if_error: true
+          fail_ci_if_error: false
diff --git a/index.d.ts b/index.d.ts
index f37c7ea..0a07c4f 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -852,6 +852,56 @@ type Execa$<StdoutStderrType extends StdoutStderrAll = string> = {
 		templates: TemplateStringsArray,
 		...expressions: TemplateExpression[]
 	): ExecaSyncReturnValue<StdoutStderrType>;
+
+	/**
+	Same as $\`command\` but synchronous.
+
+	@returns A `childProcessResult` object
+	@throws A `childProcessResult` error
+
+	@example <caption>Basic</caption>
+	```
+	import {$} from 'execa';
+
+	const branch = $.s`git branch --show-current`;
+	$.s`dep deploy --branch=${branch}`;
+	```
+
+	@example <caption>Multiple arguments</caption>
+	```
+	import {$} from 'execa';
+
+	const args = ['unicorns', '&', 'rainbows!'];
+	const {stdout} = $.s`echo ${args}`;
+	console.log(stdout);
+	//=> 'unicorns & rainbows!'
+	```
+
+	@example <caption>With options</caption>
+	```
+	import {$} from 'execa';
+
+	$.s({stdio: 'inherit'})`echo unicorns`;
+	//=> 'unicorns'
+	```
+
+	@example <caption>Shared options</caption>
+	```
+	import {$} from 'execa';
+
+	const $$ = $({stdio: 'inherit'});
+
+	$$.s`echo unicorns`;
+	//=> 'unicorns'
+
+	$$.s`echo rainbows`;
+	//=> 'rainbows'
+	```
+	*/
+	s(
+		templates: TemplateStringsArray,
+		...expressions: TemplateExpression[]
+	): ExecaSyncReturnValue<StdoutStderrType>;
 };
 
 /**
diff --git a/index.js b/index.js
index a319ac5..8d830bb 100644
--- a/index.js
+++ b/index.js
@@ -263,6 +263,8 @@ function create$(options) {
 		return execaSync(file, args, normalizeScriptOptions(options));
 	};
 
+	$.s = $.sync;
+
 	return $;
 }
 
diff --git a/lib/verbose.js b/lib/verbose.js
index 5f5490e..03682f4 100644
--- a/lib/verbose.js
+++ b/lib/verbose.js
@@ -1,3 +1,4 @@
+import {writeFileSync} from 'node:fs';
 import {debuglog} from 'node:util';
 import process from 'node:process';
 
@@ -15,5 +16,7 @@ export const logCommand = (escapedCommand, {verbose}) => {
 		return;
 	}
 
-	process.stderr.write(`[${getTimestamp()}] ${escapedCommand}\n`);
+	// Write synchronously to ensure it is written before spawning the child process.
+	// This guarantees this line is written to `stderr` before the child process prints anything.
+	writeFileSync(process.stderr.fd, `[${getTimestamp()}] ${escapedCommand}\n`);
 };
diff --git a/readme.md b/readme.md
index 004c7a3..18dab0c 100644
--- a/readme.md
+++ b/readme.md
@@ -292,6 +292,7 @@ Same as [`execa()`](#execacommandcommand-options) but synchronous.
 Returns or throws a [`childProcessResult`](#childProcessResult).
 
 ### $.sync\`command\`
+### $.s\`command\`
 
 Same as [$\`command\`](#command) but synchronous.
 


Test patch:
diff --git a/index.test-d.ts b/index.test-d.ts
index 1e1f764..7d01cb9 100644
--- a/index.test-d.ts
+++ b/index.test-d.ts
@@ -292,6 +292,7 @@ expectType<ExecaChildProcess<Buffer>>(
 expectType<ExecaChildProcess>($`unicorns`);
 expectType<ExecaReturnValue>(await $`unicorns`);
 expectType<ExecaSyncReturnValue>($.sync`unicorns`);
+expectType<ExecaSyncReturnValue>($.s`unicorns`);
 
 expectType<ExecaChildProcess>($({encoding: 'utf8'})`unicorns`);
 expectType<ExecaReturnValue>(await $({encoding: 'utf8'})`unicorns`);
diff --git a/test/command.js b/test/command.js
index 552cb6c..ec7eb19 100644
--- a/test/command.js
+++ b/test/command.js
@@ -237,6 +237,11 @@ test('$.sync', t => {
 	t.is(stdout, 'foo\nbar');
 });
 
+test('$.sync can be called $.s', t => {
+	const {stdout} = $.s`echo.js foo bar`;
+	t.is(stdout, 'foo\nbar');
+});
+
 test('$.sync accepts options', t => {
 	const {stdout} = $({stripFinalNewline: true}).sync`noop.js foo`;
 	t.is(stdout, 'foo');
diff --git a/test/kill.js b/test/kill.js
index 42bd53f..66ff48b 100644
--- a/test/kill.js
+++ b/test/kill.js
@@ -167,7 +167,9 @@ const spawnAndKill = async (t, [signal, cleanup, detached, isKilled]) => {
 const exitIfWindows = process.platform === 'win32';
 test('spawnAndKill SIGTERM', spawnAndKill, ['SIGTERM', false, false, exitIfWindows]);
 test('spawnAndKill SIGKILL', spawnAndKill, ['SIGKILL', false, false, exitIfWindows]);
-test('spawnAndKill cleanup SIGTERM', spawnAndKill, ['SIGTERM', true, false, true]);
+// The `cleanup` option can introduce a race condition in this test
+// This is especially true when run concurrently, so we use `test.serial()`
+test.serial('spawnAndKill cleanup SIGTERM', spawnAndKill, ['SIGTERM', true, false, true]);
 test('spawnAndKill cleanup SIGKILL', spawnAndKill, ['SIGKILL', true, false, exitIfWindows]);
 test('spawnAndKill detached SIGTERM', spawnAndKill, ['SIGTERM', false, true, false]);
 test('spawnAndKill detached SIGKILL', spawnAndKill, ['SIGKILL', false, true, false]);