mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 02:24:18 +00:00
use execa
This commit is contained in:
parent
9c57a002f3
commit
affe6a4bc3
@ -4,10 +4,24 @@ import react from "@vitejs/plugin-react"
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
// build: {
|
||||
// rollupOptions: {
|
||||
// output: {
|
||||
// format: "es"
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
// build: {
|
||||
// rollupOptions: {
|
||||
// output: {
|
||||
// format: "es"
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
renderer: {
|
||||
resolve: {
|
||||
|
@ -38,6 +38,7 @@
|
||||
"human-format": "^1.2.0",
|
||||
"less": "^4.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-stream": "^2.0.0",
|
||||
"node-7z": "^3.0.0",
|
||||
"open": "8.4.2",
|
||||
"progress-stream": "^2.0.0",
|
||||
@ -47,6 +48,7 @@
|
||||
"react-spinners": "^0.13.8",
|
||||
"request": "^2.88.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"signal-exit": "^4.1.0",
|
||||
"unzipper": "^0.10.14",
|
||||
"upath": "^2.0.1",
|
||||
"uuid": "^9.0.1",
|
||||
|
955
src/main/lib/execa/index.d.ts
vendored
Executable file
955
src/main/lib/execa/index.d.ts
vendored
Executable file
@ -0,0 +1,955 @@
|
||||
import {type Buffer} from 'node:buffer';
|
||||
import {type ChildProcess} from 'node:child_process';
|
||||
import {type Stream, type Readable as ReadableStream, type Writable as WritableStream} from 'node:stream';
|
||||
|
||||
export type StdioOption =
|
||||
| 'pipe'
|
||||
| 'overlapped'
|
||||
| 'ipc'
|
||||
| 'ignore'
|
||||
| 'inherit'
|
||||
| Stream
|
||||
| number
|
||||
| undefined;
|
||||
|
||||
type EncodingOption =
|
||||
| 'utf8'
|
||||
// eslint-disable-next-line unicorn/text-encoding-identifier-case
|
||||
| 'utf-8'
|
||||
| 'utf16le'
|
||||
| 'utf-16le'
|
||||
| 'ucs2'
|
||||
| 'ucs-2'
|
||||
| 'latin1'
|
||||
| 'binary'
|
||||
| 'ascii'
|
||||
| 'hex'
|
||||
| 'base64'
|
||||
| 'base64url'
|
||||
| 'buffer'
|
||||
| null
|
||||
| undefined;
|
||||
type DefaultEncodingOption = 'utf8';
|
||||
type BufferEncodingOption = 'buffer' | null;
|
||||
|
||||
export type CommonOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = {
|
||||
/**
|
||||
Kill the spawned process when the parent process exits unless either:
|
||||
- the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached)
|
||||
- the parent process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit
|
||||
|
||||
@default true
|
||||
*/
|
||||
readonly cleanup?: boolean;
|
||||
|
||||
/**
|
||||
Prefer locally installed binaries when looking for a binary to execute.
|
||||
|
||||
If you `$ npm install foo`, you can then `execa('foo')`.
|
||||
|
||||
@default `true` with `$`, `false` otherwise
|
||||
*/
|
||||
readonly preferLocal?: boolean;
|
||||
|
||||
/**
|
||||
Preferred path to find locally installed binaries in (use with `preferLocal`).
|
||||
|
||||
@default process.cwd()
|
||||
*/
|
||||
readonly localDir?: string | URL;
|
||||
|
||||
/**
|
||||
Path to the Node.js executable to use in child processes.
|
||||
|
||||
This can be either an absolute path or a path relative to the `cwd` option.
|
||||
|
||||
Requires `preferLocal` to be `true`.
|
||||
|
||||
For example, this can be used together with [`get-node`](https://github.com/ehmicky/get-node) to run a specific Node.js version in a child process.
|
||||
|
||||
@default process.execPath
|
||||
*/
|
||||
readonly execPath?: string;
|
||||
|
||||
/**
|
||||
Buffer the output from the spawned process. When set to `false`, you must read the output of `stdout` and `stderr` (or `all` if the `all` option is `true`). Otherwise the returned promise will not be resolved/rejected.
|
||||
|
||||
If the spawned process fails, `error.stdout`, `error.stderr`, and `error.all` will contain the buffered data.
|
||||
|
||||
@default true
|
||||
*/
|
||||
readonly buffer?: boolean;
|
||||
|
||||
/**
|
||||
Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
|
||||
|
||||
@default `inherit` with `$`, `pipe` otherwise
|
||||
*/
|
||||
readonly stdin?: StdioOption;
|
||||
|
||||
/**
|
||||
Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
|
||||
|
||||
@default 'pipe'
|
||||
*/
|
||||
readonly stdout?: StdioOption;
|
||||
|
||||
/**
|
||||
Same options as [`stdio`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio).
|
||||
|
||||
@default 'pipe'
|
||||
*/
|
||||
readonly stderr?: StdioOption;
|
||||
|
||||
/**
|
||||
Setting this to `false` resolves the promise with the error instead of rejecting it.
|
||||
|
||||
@default true
|
||||
*/
|
||||
readonly reject?: boolean;
|
||||
|
||||
/**
|
||||
Add an `.all` property on the promise and the resolved value. The property contains the output of the process with `stdout` and `stderr` interleaved.
|
||||
|
||||
@default false
|
||||
*/
|
||||
readonly all?: boolean;
|
||||
|
||||
/**
|
||||
Strip the final [newline character](https://en.wikipedia.org/wiki/Newline) from the output.
|
||||
|
||||
@default true
|
||||
*/
|
||||
readonly stripFinalNewline?: boolean;
|
||||
|
||||
/**
|
||||
Set to `false` if you don't want to extend the environment variables when providing the `env` property.
|
||||
|
||||
@default true
|
||||
*/
|
||||
readonly extendEnv?: boolean;
|
||||
|
||||
/**
|
||||
Current working directory of the child process.
|
||||
|
||||
@default process.cwd()
|
||||
*/
|
||||
readonly cwd?: string | URL;
|
||||
|
||||
/**
|
||||
Environment key-value pairs. Extends automatically from `process.env`. Set `extendEnv` to `false` if you don't want this.
|
||||
|
||||
@default process.env
|
||||
*/
|
||||
readonly env?: NodeJS.ProcessEnv;
|
||||
|
||||
/**
|
||||
Explicitly set the value of `argv[0]` sent to the child process. This will be set to `command` or `file` if not specified.
|
||||
*/
|
||||
readonly argv0?: string;
|
||||
|
||||
/**
|
||||
Child's [stdio](https://nodejs.org/api/child_process.html#child_process_options_stdio) configuration.
|
||||
|
||||
@default 'pipe'
|
||||
*/
|
||||
readonly stdio?: 'pipe' | 'overlapped' | 'ignore' | 'inherit' | readonly StdioOption[];
|
||||
|
||||
/**
|
||||
Specify the kind of serialization used for sending messages between processes when using the `stdio: 'ipc'` option or `execaNode()`:
|
||||
- `json`: Uses `JSON.stringify()` and `JSON.parse()`.
|
||||
- `advanced`: Uses [`v8.serialize()`](https://nodejs.org/api/v8.html#v8_v8_serialize_value)
|
||||
|
||||
[More info.](https://nodejs.org/api/child_process.html#child_process_advanced_serialization)
|
||||
|
||||
@default 'json'
|
||||
*/
|
||||
readonly serialization?: 'json' | 'advanced';
|
||||
|
||||
/**
|
||||
Prepare child to run independently of its parent process. Specific behavior [depends on the platform](https://nodejs.org/api/child_process.html#child_process_options_detached).
|
||||
|
||||
@default false
|
||||
*/
|
||||
readonly detached?: boolean;
|
||||
|
||||
/**
|
||||
Sets the user identity of the process.
|
||||
*/
|
||||
readonly uid?: number;
|
||||
|
||||
/**
|
||||
Sets the group identity of the process.
|
||||
*/
|
||||
readonly gid?: number;
|
||||
|
||||
/**
|
||||
If `true`, runs `command` inside of a shell. Uses `/bin/sh` on UNIX and `cmd.exe` on Windows. A different shell can be specified as a string. The shell should understand the `-c` switch on UNIX or `/d /s /c` on Windows.
|
||||
|
||||
We recommend against using this option since it is:
|
||||
- not cross-platform, encouraging shell-specific syntax.
|
||||
- slower, because of the additional shell interpretation.
|
||||
- unsafe, potentially allowing command injection.
|
||||
|
||||
@default false
|
||||
*/
|
||||
readonly shell?: boolean | string;
|
||||
|
||||
/**
|
||||
Specify the character encoding used to decode the `stdout` and `stderr` output. If set to `'buffer'` or `null`, then `stdout` and `stderr` will be a `Buffer` instead of a string.
|
||||
|
||||
@default 'utf8'
|
||||
*/
|
||||
readonly encoding?: EncodingType;
|
||||
|
||||
/**
|
||||
If `timeout` is greater than `0`, the parent will send the signal identified by the `killSignal` property (the default is `SIGTERM`) if the child runs longer than `timeout` milliseconds.
|
||||
|
||||
@default 0
|
||||
*/
|
||||
readonly timeout?: number;
|
||||
|
||||
/**
|
||||
Largest amount of data in bytes allowed on `stdout` or `stderr`. Default: 100 MB.
|
||||
|
||||
@default 100_000_000
|
||||
*/
|
||||
readonly maxBuffer?: number;
|
||||
|
||||
/**
|
||||
Signal value to be used when the spawned process will be killed.
|
||||
|
||||
@default 'SIGTERM'
|
||||
*/
|
||||
readonly killSignal?: string | number;
|
||||
|
||||
/**
|
||||
You can abort the spawned process using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
|
||||
|
||||
When `AbortController.abort()` is called, [`.isCanceled`](https://github.com/sindresorhus/execa#iscanceled) becomes `true`.
|
||||
|
||||
@example
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
const abortController = new AbortController();
|
||||
const subprocess = execa('node', [], {signal: abortController.signal});
|
||||
|
||||
setTimeout(() => {
|
||||
abortController.abort();
|
||||
}, 1000);
|
||||
|
||||
try {
|
||||
await subprocess;
|
||||
} catch (error) {
|
||||
console.log(subprocess.killed); // true
|
||||
console.log(error.isCanceled); // true
|
||||
}
|
||||
```
|
||||
*/
|
||||
readonly signal?: AbortSignal;
|
||||
|
||||
/**
|
||||
If `true`, no quoting or escaping of arguments is done on Windows. Ignored on other platforms. This is set to `true` automatically when the `shell` option is `true`.
|
||||
|
||||
@default false
|
||||
*/
|
||||
readonly windowsVerbatimArguments?: boolean;
|
||||
|
||||
/**
|
||||
On Windows, do not create a new console window. Please note this also prevents `CTRL-C` [from working](https://github.com/nodejs/node/issues/29837) on Windows.
|
||||
|
||||
@default true
|
||||
*/
|
||||
readonly windowsHide?: boolean;
|
||||
|
||||
/**
|
||||
Print each command on `stderr` before executing it.
|
||||
|
||||
This can also be enabled by setting the `NODE_DEBUG=execa` environment variable in the current process.
|
||||
|
||||
@default false
|
||||
*/
|
||||
readonly verbose?: boolean;
|
||||
};
|
||||
|
||||
export type Options<EncodingType extends EncodingOption = DefaultEncodingOption> = {
|
||||
/**
|
||||
Write some input to the `stdin` of your binary.
|
||||
|
||||
If the input is a file, use the `inputFile` option instead.
|
||||
*/
|
||||
readonly input?: string | Buffer | ReadableStream;
|
||||
|
||||
/**
|
||||
Use a file as input to the the `stdin` of your binary.
|
||||
|
||||
If the input is not a file, use the `input` option instead.
|
||||
*/
|
||||
readonly inputFile?: string;
|
||||
} & CommonOptions<EncodingType>;
|
||||
|
||||
export type SyncOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = {
|
||||
/**
|
||||
Write some input to the `stdin` of your binary.
|
||||
|
||||
If the input is a file, use the `inputFile` option instead.
|
||||
*/
|
||||
readonly input?: string | Buffer;
|
||||
|
||||
/**
|
||||
Use a file as input to the the `stdin` of your binary.
|
||||
|
||||
If the input is not a file, use the `input` option instead.
|
||||
*/
|
||||
readonly inputFile?: string;
|
||||
} & CommonOptions<EncodingType>;
|
||||
|
||||
export type NodeOptions<EncodingType extends EncodingOption = DefaultEncodingOption> = {
|
||||
/**
|
||||
The Node.js executable to use.
|
||||
|
||||
@default process.execPath
|
||||
*/
|
||||
readonly nodePath?: string;
|
||||
|
||||
/**
|
||||
List of [CLI options](https://nodejs.org/api/cli.html#cli_options) passed to the Node.js executable.
|
||||
|
||||
@default process.execArgv
|
||||
*/
|
||||
readonly nodeOptions?: string[];
|
||||
} & Options<EncodingType>;
|
||||
|
||||
type StdoutStderrAll = string | Buffer | undefined;
|
||||
|
||||
export type ExecaReturnBase<StdoutStderrType extends StdoutStderrAll> = {
|
||||
/**
|
||||
The file and arguments that were run, for logging purposes.
|
||||
|
||||
This is not escaped and should not be executed directly as a process, including using `execa()` or `execaCommand()`.
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
Same as `command` but escaped.
|
||||
|
||||
This is meant to be copy and pasted into a shell, for debugging purposes.
|
||||
Since the escaping is fairly basic, this should not be executed directly as a process, including using `execa()` or `execaCommand()`.
|
||||
*/
|
||||
escapedCommand: string;
|
||||
|
||||
/**
|
||||
The numeric exit code of the process that was run.
|
||||
*/
|
||||
exitCode: number;
|
||||
|
||||
/**
|
||||
The output of the process on stdout.
|
||||
*/
|
||||
stdout: StdoutStderrType;
|
||||
|
||||
/**
|
||||
The output of the process on stderr.
|
||||
*/
|
||||
stderr: StdoutStderrType;
|
||||
|
||||
/**
|
||||
Whether the process failed to run.
|
||||
*/
|
||||
failed: boolean;
|
||||
|
||||
/**
|
||||
Whether the process timed out.
|
||||
*/
|
||||
timedOut: boolean;
|
||||
|
||||
/**
|
||||
Whether the process was killed.
|
||||
*/
|
||||
killed: boolean;
|
||||
|
||||
/**
|
||||
The name of the signal that was used to terminate the process. For example, `SIGFPE`.
|
||||
|
||||
If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`.
|
||||
*/
|
||||
signal?: string;
|
||||
|
||||
/**
|
||||
A human-friendly description of the signal that was used to terminate the process. For example, `Floating point arithmetic error`.
|
||||
|
||||
If a signal terminated the process, this property is defined and included in the error message. Otherwise it is `undefined`. It is also `undefined` when the signal is very uncommon which should seldomly happen.
|
||||
*/
|
||||
signalDescription?: string;
|
||||
|
||||
/**
|
||||
The `cwd` of the command if provided in the command options. Otherwise it is `process.cwd()`.
|
||||
*/
|
||||
cwd: string;
|
||||
};
|
||||
|
||||
export type ExecaSyncReturnValue<StdoutStderrType extends StdoutStderrAll = string> = {
|
||||
} & ExecaReturnBase<StdoutStderrType>;
|
||||
|
||||
/**
|
||||
Result of a child process execution. On success this is a plain object. On failure this is also an `Error` instance.
|
||||
|
||||
The child process fails when:
|
||||
- its exit code is not `0`
|
||||
- it was killed with a signal
|
||||
- timing out
|
||||
- being canceled
|
||||
- there's not enough memory or there are already too many child processes
|
||||
*/
|
||||
export type ExecaReturnValue<StdoutStderrType extends StdoutStderrAll = string> = {
|
||||
/**
|
||||
The output of the process with `stdout` and `stderr` interleaved.
|
||||
|
||||
This is `undefined` if either:
|
||||
- the `all` option is `false` (default value)
|
||||
- `execaSync()` was used
|
||||
*/
|
||||
all?: StdoutStderrType;
|
||||
|
||||
/**
|
||||
Whether the process was canceled.
|
||||
|
||||
You can cancel the spawned process using the [`signal`](https://github.com/sindresorhus/execa#signal-1) option.
|
||||
*/
|
||||
isCanceled: boolean;
|
||||
} & ExecaSyncReturnValue<StdoutStderrType>;
|
||||
|
||||
export type ExecaSyncError<StdoutStderrType extends StdoutStderrAll = string> = {
|
||||
/**
|
||||
Error message when the child process failed to run. In addition to the underlying error message, it also contains some information related to why the child process errored.
|
||||
|
||||
The child process stderr then stdout are appended to the end, separated with newlines and not interleaved.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
This is the same as the `message` property except it does not include the child process stdout/stderr.
|
||||
*/
|
||||
shortMessage: string;
|
||||
|
||||
/**
|
||||
Original error message. This is the same as the `message` property except it includes neither the child process stdout/stderr nor some additional information added by Execa.
|
||||
|
||||
This is `undefined` unless the child process exited due to an `error` event or a timeout.
|
||||
*/
|
||||
originalMessage?: string;
|
||||
} & Error & ExecaReturnBase<StdoutStderrType>;
|
||||
|
||||
export type ExecaError<StdoutStderrType extends StdoutStderrAll = string> = {
|
||||
/**
|
||||
The output of the process with `stdout` and `stderr` interleaved.
|
||||
|
||||
This is `undefined` if either:
|
||||
- the `all` option is `false` (default value)
|
||||
- `execaSync()` was used
|
||||
*/
|
||||
all?: StdoutStderrType;
|
||||
|
||||
/**
|
||||
Whether the process was canceled.
|
||||
*/
|
||||
isCanceled: boolean;
|
||||
} & ExecaSyncError<StdoutStderrType>;
|
||||
|
||||
export type KillOptions = {
|
||||
/**
|
||||
Milliseconds to wait for the child process to terminate before sending `SIGKILL`.
|
||||
|
||||
Can be disabled with `false`.
|
||||
|
||||
@default 5000
|
||||
*/
|
||||
forceKillAfterTimeout?: number | false;
|
||||
};
|
||||
|
||||
export type ExecaChildPromise<StdoutStderrType extends StdoutStderrAll> = {
|
||||
/**
|
||||
Stream combining/interleaving [`stdout`](https://nodejs.org/api/child_process.html#child_process_subprocess_stdout) and [`stderr`](https://nodejs.org/api/child_process.html#child_process_subprocess_stderr).
|
||||
|
||||
This is `undefined` if either:
|
||||
- the `all` option is `false` (the default value)
|
||||
- both `stdout` and `stderr` options are set to [`'inherit'`, `'ipc'`, `Stream` or `integer`](https://nodejs.org/dist/latest-v6.x/docs/api/child_process.html#child_process_options_stdio)
|
||||
*/
|
||||
all?: ReadableStream;
|
||||
|
||||
catch<ResultType = never>(
|
||||
onRejected?: (reason: ExecaError<StdoutStderrType>) => ResultType | PromiseLike<ResultType>
|
||||
): Promise<ExecaReturnValue<StdoutStderrType> | ResultType>;
|
||||
|
||||
/**
|
||||
Same as the original [`child_process#kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal), except if `signal` is `SIGTERM` (the default value) and the child process is not terminated after 5 seconds, force it by sending `SIGKILL`. Note that this graceful termination does not work on Windows, because Windows [doesn't support signals](https://nodejs.org/api/process.html#process_signal_events) (`SIGKILL` and `SIGTERM` has the same effect of force-killing the process immediately.) If you want to achieve graceful termination on Windows, you have to use other means, such as [`taskkill`](https://github.com/sindresorhus/taskkill).
|
||||
*/
|
||||
kill(signal?: string, options?: KillOptions): void;
|
||||
|
||||
/**
|
||||
Similar to [`childProcess.kill()`](https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal). This used to be preferred when cancelling the child process execution as the error is more descriptive and [`childProcessResult.isCanceled`](#iscanceled) is set to `true`. But now this is deprecated and you should either use `.kill()` or the `signal` option when creating the child process.
|
||||
*/
|
||||
cancel(): void;
|
||||
|
||||
/**
|
||||
[Pipe](https://nodejs.org/api/stream.html#readablepipedestination-options) the child process's `stdout` to `target`, which can be:
|
||||
- Another `execa()` return value
|
||||
- A writable stream
|
||||
- A file path string
|
||||
|
||||
If the `target` is another `execa()` return value, it is returned. Otherwise, the original `execa()` return value is returned. This allows chaining `pipeStdout()` then `await`ing the final result.
|
||||
|
||||
The `stdout` option] must be kept as `pipe`, its default value.
|
||||
*/
|
||||
pipeStdout?<Target extends ExecaChildPromise<StdoutStderrAll>>(target: Target): Target;
|
||||
pipeStdout?(target: WritableStream | string): ExecaChildProcess<StdoutStderrType>;
|
||||
|
||||
/**
|
||||
Like `pipeStdout()` but piping the child process's `stderr` instead.
|
||||
|
||||
The `stderr` option must be kept as `pipe`, its default value.
|
||||
*/
|
||||
pipeStderr?<Target extends ExecaChildPromise<StdoutStderrAll>>(target: Target): Target;
|
||||
pipeStderr?(target: WritableStream | string): ExecaChildProcess<StdoutStderrType>;
|
||||
|
||||
/**
|
||||
Combines both `pipeStdout()` and `pipeStderr()`.
|
||||
|
||||
Either the `stdout` option or the `stderr` option must be kept as `pipe`, their default value. Also, the `all` option must be set to `true`.
|
||||
*/
|
||||
pipeAll?<Target extends ExecaChildPromise<StdoutStderrAll>>(target: Target): Target;
|
||||
pipeAll?(target: WritableStream | string): ExecaChildProcess<StdoutStderrType>;
|
||||
};
|
||||
|
||||
export type ExecaChildProcess<StdoutStderrType extends StdoutStderrAll = string> = ChildProcess &
|
||||
ExecaChildPromise<StdoutStderrType> &
|
||||
Promise<ExecaReturnValue<StdoutStderrType>>;
|
||||
|
||||
/**
|
||||
Executes a command using `file ...arguments`. `arguments` are specified as an array of strings. Returns a `childProcess`.
|
||||
|
||||
Arguments are automatically escaped. They can contain any character, including spaces.
|
||||
|
||||
This is the preferred method when executing single commands.
|
||||
|
||||
@param file - The program/script to execute.
|
||||
@param arguments - Arguments to pass to `file` on execution.
|
||||
@returns An `ExecaChildProcess` that is both:
|
||||
- a `Promise` resolving or rejecting with a `childProcessResult`.
|
||||
- a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) with some additional methods and properties.
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example <caption>Promise interface</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
const {stdout} = await execa('echo', ['unicorns']);
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Redirect output to a file</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
// Similar to `echo unicorns > stdout.txt` in Bash
|
||||
await execa('echo', ['unicorns']).pipeStdout('stdout.txt');
|
||||
|
||||
// Similar to `echo unicorns 2> stdout.txt` in Bash
|
||||
await execa('echo', ['unicorns']).pipeStderr('stderr.txt');
|
||||
|
||||
// Similar to `echo unicorns &> stdout.txt` in Bash
|
||||
await execa('echo', ['unicorns'], {all: true}).pipeAll('all.txt');
|
||||
```
|
||||
|
||||
@example <caption>Redirect input from a file</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
// Similar to `cat < stdin.txt` in Bash
|
||||
const {stdout} = await execa('cat', {inputFile: 'stdin.txt'});
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Save and pipe output from a child process</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
const {stdout} = await execa('echo', ['unicorns']).pipeStdout(process.stdout);
|
||||
// Prints `unicorns`
|
||||
console.log(stdout);
|
||||
// Also returns 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Pipe multiple processes</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
// Similar to `echo unicorns | cat` in Bash
|
||||
const {stdout} = await execa('echo', ['unicorns']).pipeStdout(execa('cat'));
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Handling errors</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
// Catching an error
|
||||
try {
|
||||
await execa('unknown', ['command']);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
/*
|
||||
{
|
||||
message: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'spawn unknown',
|
||||
path: 'unknown',
|
||||
spawnargs: ['command'],
|
||||
originalMessage: 'spawn unknown ENOENT',
|
||||
shortMessage: 'Command failed with ENOENT: unknown command spawn unknown ENOENT',
|
||||
command: 'unknown command',
|
||||
escapedCommand: 'unknown command',
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
failed: true,
|
||||
timedOut: false,
|
||||
isCanceled: false,
|
||||
killed: false,
|
||||
cwd: '/path/to/cwd'
|
||||
}
|
||||
\*\/
|
||||
}
|
||||
```
|
||||
|
||||
@example <caption>Graceful termination</caption>
|
||||
```
|
||||
const subprocess = execa('node');
|
||||
|
||||
setTimeout(() => {
|
||||
subprocess.kill('SIGTERM', {
|
||||
forceKillAfterTimeout: 2000
|
||||
});
|
||||
}, 1000);
|
||||
```
|
||||
*/
|
||||
export function execa(
|
||||
file: string,
|
||||
arguments?: readonly string[],
|
||||
options?: Options
|
||||
): ExecaChildProcess;
|
||||
export function execa(
|
||||
file: string,
|
||||
arguments?: readonly string[],
|
||||
options?: Options<BufferEncodingOption>
|
||||
): ExecaChildProcess<Buffer>;
|
||||
export function execa(file: string, options?: Options): ExecaChildProcess;
|
||||
export function execa(file: string, options?: Options<BufferEncodingOption>): ExecaChildProcess<Buffer>;
|
||||
|
||||
/**
|
||||
Same as `execa()` but synchronous.
|
||||
|
||||
@param file - The program/script to execute.
|
||||
@param arguments - Arguments to pass to `file` on execution.
|
||||
@returns A `childProcessResult` object
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example <caption>Promise interface</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
const {stdout} = execaSync('echo', ['unicorns']);
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Redirect input from a file</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
// Similar to `cat < stdin.txt` in Bash
|
||||
const {stdout} = execaSync('cat', {inputFile: 'stdin.txt'});
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Handling errors</caption>
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
// Catching an error
|
||||
try {
|
||||
execaSync('unknown', ['command']);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
/*
|
||||
{
|
||||
message: 'Command failed with ENOENT: unknown command spawnSync unknown ENOENT',
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'spawnSync unknown',
|
||||
path: 'unknown',
|
||||
spawnargs: ['command'],
|
||||
originalMessage: 'spawnSync unknown ENOENT',
|
||||
shortMessage: 'Command failed with ENOENT: unknown command spawnSync unknown ENOENT',
|
||||
command: 'unknown command',
|
||||
escapedCommand: 'unknown command',
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
failed: true,
|
||||
timedOut: false,
|
||||
isCanceled: false,
|
||||
killed: false,
|
||||
cwd: '/path/to/cwd'
|
||||
}
|
||||
\*\/
|
||||
}
|
||||
```
|
||||
*/
|
||||
export function execaSync(
|
||||
file: string,
|
||||
arguments?: readonly string[],
|
||||
options?: SyncOptions
|
||||
): ExecaSyncReturnValue;
|
||||
export function execaSync(
|
||||
file: string,
|
||||
arguments?: readonly string[],
|
||||
options?: SyncOptions<BufferEncodingOption>
|
||||
): ExecaSyncReturnValue<Buffer>;
|
||||
export function execaSync(file: string, options?: SyncOptions): ExecaSyncReturnValue;
|
||||
export function execaSync(
|
||||
file: string,
|
||||
options?: SyncOptions<BufferEncodingOption>
|
||||
): ExecaSyncReturnValue<Buffer>;
|
||||
|
||||
/**
|
||||
Executes a command. The `command` string includes both the `file` and its `arguments`. Returns a `childProcess`.
|
||||
|
||||
Arguments are automatically escaped. They can contain any character, but spaces must be escaped with a backslash like `execaCommand('echo has\\ space')`.
|
||||
|
||||
This is the preferred method when executing a user-supplied `command` string, such as in a REPL.
|
||||
|
||||
@param command - The program/script to execute and its arguments.
|
||||
@returns An `ExecaChildProcess` that is both:
|
||||
- a `Promise` resolving or rejecting with a `childProcessResult`.
|
||||
- a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) with some additional methods and properties.
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example
|
||||
```
|
||||
import {execaCommand} from 'execa';
|
||||
|
||||
const {stdout} = await execaCommand('echo unicorns');
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
*/
|
||||
export function execaCommand(command: string, options?: Options): ExecaChildProcess;
|
||||
export function execaCommand(command: string, options?: Options<BufferEncodingOption>): ExecaChildProcess<Buffer>;
|
||||
|
||||
/**
|
||||
Same as `execaCommand()` but synchronous.
|
||||
|
||||
@param command - The program/script to execute and its arguments.
|
||||
@returns A `childProcessResult` object
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example
|
||||
```
|
||||
import {execaCommandSync} from 'execa';
|
||||
|
||||
const {stdout} = execaCommandSync('echo unicorns');
|
||||
console.log(stdout);
|
||||
//=> 'unicorns'
|
||||
```
|
||||
*/
|
||||
export function execaCommandSync(command: string, options?: SyncOptions): ExecaSyncReturnValue;
|
||||
export function execaCommandSync(command: string, options?: SyncOptions<BufferEncodingOption>): ExecaSyncReturnValue<Buffer>;
|
||||
|
||||
type TemplateExpression =
|
||||
| string
|
||||
| number
|
||||
| ExecaReturnValue<string | Buffer>
|
||||
| ExecaSyncReturnValue<string | Buffer>
|
||||
| Array<string | number | ExecaReturnValue<string | Buffer> | ExecaSyncReturnValue<string | Buffer>>;
|
||||
|
||||
type Execa$<StdoutStderrType extends StdoutStderrAll = string> = {
|
||||
/**
|
||||
Returns a new instance of `$` but with different default `options`. Consecutive calls are merged to previous ones.
|
||||
|
||||
This can be used to either:
|
||||
- Set options for a specific command: `` $(options)`command` ``
|
||||
- Share options for multiple commands: `` const $$ = $(options); $$`command`; $$`otherCommand` ``
|
||||
|
||||
@param options - Options to set
|
||||
@returns A new instance of `$` with those `options` set
|
||||
|
||||
@example
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const $$ = $({stdio: 'inherit'});
|
||||
|
||||
await $$`echo unicorns`;
|
||||
//=> 'unicorns'
|
||||
|
||||
await $$`echo rainbows`;
|
||||
//=> 'rainbows'
|
||||
```
|
||||
*/
|
||||
(options: Options<undefined>): Execa$<StdoutStderrType>;
|
||||
(options: Options): Execa$;
|
||||
(options: Options<BufferEncodingOption>): Execa$<Buffer>;
|
||||
(
|
||||
templates: TemplateStringsArray,
|
||||
...expressions: TemplateExpression[]
|
||||
): ExecaChildProcess<StdoutStderrType>;
|
||||
|
||||
/**
|
||||
Same as $\`command\` but synchronous.
|
||||
|
||||
@returns A `childProcessResult` object
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example <caption>Basic</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const branch = $.sync`git branch --show-current`;
|
||||
$.sync`dep deploy --branch=${branch}`;
|
||||
```
|
||||
|
||||
@example <caption>Multiple arguments</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const args = ['unicorns', '&', 'rainbows!'];
|
||||
const {stdout} = $.sync`echo ${args}`;
|
||||
console.log(stdout);
|
||||
//=> 'unicorns & rainbows!'
|
||||
```
|
||||
|
||||
@example <caption>With options</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
$.sync({stdio: 'inherit'})`echo unicorns`;
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Shared options</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const $$ = $({stdio: 'inherit'});
|
||||
|
||||
$$.sync`echo unicorns`;
|
||||
//=> 'unicorns'
|
||||
|
||||
$$.sync`echo rainbows`;
|
||||
//=> 'rainbows'
|
||||
```
|
||||
*/
|
||||
sync(
|
||||
templates: TemplateStringsArray,
|
||||
...expressions: TemplateExpression[]
|
||||
): ExecaSyncReturnValue<StdoutStderrType>;
|
||||
};
|
||||
|
||||
/**
|
||||
Executes a command. The `command` string includes both the `file` and its `arguments`. Returns a `childProcess`.
|
||||
|
||||
Arguments are automatically escaped. They can contain any character, but spaces must use `${}` like `` $`echo ${'has space'}` ``.
|
||||
|
||||
This is the preferred method when executing multiple commands in a script file.
|
||||
|
||||
The `command` string can inject any `${value}` with the following types: string, number, `childProcess` or an array of those types. For example: `` $`echo one ${'two'} ${3} ${['four', 'five']}` ``. For `${childProcess}`, the process's `stdout` is used.
|
||||
|
||||
@returns An `ExecaChildProcess` that is both:
|
||||
- a `Promise` resolving or rejecting with a `childProcessResult`.
|
||||
- a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) with some additional methods and properties.
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example <caption>Basic</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const branch = await $`git branch --show-current`;
|
||||
await $`dep deploy --branch=${branch}`;
|
||||
```
|
||||
|
||||
@example <caption>Multiple arguments</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const args = ['unicorns', '&', 'rainbows!'];
|
||||
const {stdout} = await $`echo ${args}`;
|
||||
console.log(stdout);
|
||||
//=> 'unicorns & rainbows!'
|
||||
```
|
||||
|
||||
@example <caption>With options</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
await $({stdio: 'inherit'})`echo unicorns`;
|
||||
//=> 'unicorns'
|
||||
```
|
||||
|
||||
@example <caption>Shared options</caption>
|
||||
```
|
||||
import {$} from 'execa';
|
||||
|
||||
const $$ = $({stdio: 'inherit'});
|
||||
|
||||
await $$`echo unicorns`;
|
||||
//=> 'unicorns'
|
||||
|
||||
await $$`echo rainbows`;
|
||||
//=> 'rainbows'
|
||||
```
|
||||
*/
|
||||
export const $: Execa$;
|
||||
|
||||
/**
|
||||
Execute a Node.js script as a child process.
|
||||
|
||||
Arguments are automatically escaped. They can contain any character, including spaces.
|
||||
|
||||
This is the preferred method when executing Node.js files.
|
||||
|
||||
Like [`child_process#fork()`](https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options):
|
||||
- the current Node version and options are used. This can be overridden using the `nodePath` and `nodeOptions` options.
|
||||
- the `shell` option cannot be used
|
||||
- an extra channel [`ipc`](https://nodejs.org/api/child_process.html#child_process_options_stdio) is passed to `stdio`
|
||||
|
||||
@param scriptPath - Node.js script to execute.
|
||||
@param arguments - Arguments to pass to `scriptPath` on execution.
|
||||
@returns An `ExecaChildProcess` that is both:
|
||||
- a `Promise` resolving or rejecting with a `childProcessResult`.
|
||||
- a [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess) with some additional methods and properties.
|
||||
@throws A `childProcessResult` error
|
||||
|
||||
@example
|
||||
```
|
||||
import {execa} from 'execa';
|
||||
|
||||
await execaNode('scriptPath', ['argument']);
|
||||
```
|
||||
*/
|
||||
export function execaNode(
|
||||
scriptPath: string,
|
||||
arguments?: readonly string[],
|
||||
options?: NodeOptions
|
||||
): ExecaChildProcess;
|
||||
export function execaNode(
|
||||
scriptPath: string,
|
||||
arguments?: readonly string[],
|
||||
options?: NodeOptions<BufferEncodingOption>
|
||||
): ExecaChildProcess<Buffer>;
|
||||
export function execaNode(scriptPath: string, options?: NodeOptions): ExecaChildProcess;
|
||||
export function execaNode(scriptPath: string, options?: NodeOptions<BufferEncodingOption>): ExecaChildProcess<Buffer>;
|
309
src/main/lib/execa/index.js
Executable file
309
src/main/lib/execa/index.js
Executable file
@ -0,0 +1,309 @@
|
||||
import {Buffer} from 'node:buffer';
|
||||
import path from 'node:path';
|
||||
import childProcess from 'node:child_process';
|
||||
import process from 'node:process';
|
||||
import crossSpawn from 'cross-spawn';
|
||||
import stripFinalNewline from '../strip-final-newline';
|
||||
import {npmRunPathEnv} from '../npm-run-path';
|
||||
import onetime from '../onetime';
|
||||
import {makeError} from './lib/error.js';
|
||||
import {normalizeStdio, normalizeStdioNode} from './lib/stdio.js';
|
||||
import {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} from './lib/kill.js';
|
||||
import {addPipeMethods} from './lib/pipe.js';
|
||||
import {handleInput, getSpawnedResult, makeAllStream, handleInputSync} from './lib/stream.js';
|
||||
import {mergePromise, getSpawnedPromise} from './lib/promise.js';
|
||||
import {joinCommand, parseCommand, parseTemplates, getEscapedCommand} from './lib/command.js';
|
||||
import {logCommand, verboseDefault} from './lib/verbose.js';
|
||||
|
||||
const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
|
||||
|
||||
const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => {
|
||||
const env = extendEnv ? {...process.env, ...envOption} : envOption;
|
||||
|
||||
if (preferLocal) {
|
||||
return npmRunPathEnv({env, cwd: localDir, execPath});
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
const handleArguments = (file, args, options = {}) => {
|
||||
const parsed = crossSpawn._parse(file, args, options);
|
||||
file = parsed.command;
|
||||
args = parsed.args;
|
||||
options = parsed.options;
|
||||
|
||||
options = {
|
||||
maxBuffer: DEFAULT_MAX_BUFFER,
|
||||
buffer: true,
|
||||
stripFinalNewline: true,
|
||||
extendEnv: true,
|
||||
preferLocal: false,
|
||||
localDir: options.cwd || process.cwd(),
|
||||
execPath: process.execPath,
|
||||
encoding: 'utf8',
|
||||
reject: true,
|
||||
cleanup: true,
|
||||
all: false,
|
||||
windowsHide: true,
|
||||
verbose: verboseDefault,
|
||||
...options,
|
||||
};
|
||||
|
||||
options.env = getEnv(options);
|
||||
|
||||
options.stdio = normalizeStdio(options);
|
||||
|
||||
if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') {
|
||||
// #116
|
||||
args.unshift('/q');
|
||||
}
|
||||
|
||||
return {file, args, options, parsed};
|
||||
};
|
||||
|
||||
const handleOutput = (options, value, error) => {
|
||||
if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
|
||||
// When `execaSync()` errors, we normalize it to '' to mimic `execa()`
|
||||
return error === undefined ? undefined : '';
|
||||
}
|
||||
|
||||
if (options.stripFinalNewline) {
|
||||
return stripFinalNewline(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export function execa(file, args, options) {
|
||||
const parsed = handleArguments(file, args, options);
|
||||
const command = joinCommand(file, args);
|
||||
const escapedCommand = getEscapedCommand(file, args);
|
||||
logCommand(escapedCommand, parsed.options);
|
||||
|
||||
validateTimeout(parsed.options);
|
||||
|
||||
let spawned;
|
||||
try {
|
||||
spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
|
||||
} catch (error) {
|
||||
// Ensure the returned error is always both a promise and a child process
|
||||
const dummySpawned = new childProcess.ChildProcess();
|
||||
const errorPromise = Promise.reject(makeError({
|
||||
error,
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
all: '',
|
||||
command,
|
||||
escapedCommand,
|
||||
parsed,
|
||||
timedOut: false,
|
||||
isCanceled: false,
|
||||
killed: false,
|
||||
}));
|
||||
mergePromise(dummySpawned, errorPromise);
|
||||
return dummySpawned;
|
||||
}
|
||||
|
||||
const spawnedPromise = getSpawnedPromise(spawned);
|
||||
const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise);
|
||||
const processDone = setExitHandler(spawned, parsed.options, timedPromise);
|
||||
|
||||
const context = {isCanceled: false};
|
||||
|
||||
spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned));
|
||||
spawned.cancel = spawnedCancel.bind(null, spawned, context);
|
||||
|
||||
const handlePromise = async () => {
|
||||
const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone);
|
||||
const stdout = handleOutput(parsed.options, stdoutResult);
|
||||
const stderr = handleOutput(parsed.options, stderrResult);
|
||||
const all = handleOutput(parsed.options, allResult);
|
||||
|
||||
if (error || exitCode !== 0 || signal !== null) {
|
||||
const returnedError = makeError({
|
||||
error,
|
||||
exitCode,
|
||||
signal,
|
||||
stdout,
|
||||
stderr,
|
||||
all,
|
||||
command,
|
||||
escapedCommand,
|
||||
parsed,
|
||||
timedOut,
|
||||
isCanceled: context.isCanceled || (parsed.options.signal ? parsed.options.signal.aborted : false),
|
||||
killed: spawned.killed,
|
||||
});
|
||||
|
||||
if (!parsed.options.reject) {
|
||||
return returnedError;
|
||||
}
|
||||
|
||||
throw returnedError;
|
||||
}
|
||||
|
||||
return {
|
||||
command,
|
||||
escapedCommand,
|
||||
exitCode: 0,
|
||||
stdout,
|
||||
stderr,
|
||||
all,
|
||||
failed: false,
|
||||
timedOut: false,
|
||||
isCanceled: false,
|
||||
killed: false,
|
||||
};
|
||||
};
|
||||
|
||||
const handlePromiseOnce = onetime(handlePromise);
|
||||
|
||||
handleInput(spawned, parsed.options);
|
||||
|
||||
spawned.all = makeAllStream(spawned, parsed.options);
|
||||
|
||||
addPipeMethods(spawned);
|
||||
mergePromise(spawned, handlePromiseOnce);
|
||||
return spawned;
|
||||
}
|
||||
|
||||
export function execaSync(file, args, options) {
|
||||
const parsed = handleArguments(file, args, options);
|
||||
const command = joinCommand(file, args);
|
||||
const escapedCommand = getEscapedCommand(file, args);
|
||||
logCommand(escapedCommand, parsed.options);
|
||||
|
||||
const input = handleInputSync(parsed.options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = childProcess.spawnSync(parsed.file, parsed.args, {...parsed.options, input});
|
||||
} catch (error) {
|
||||
throw makeError({
|
||||
error,
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
all: '',
|
||||
command,
|
||||
escapedCommand,
|
||||
parsed,
|
||||
timedOut: false,
|
||||
isCanceled: false,
|
||||
killed: false,
|
||||
});
|
||||
}
|
||||
|
||||
const stdout = handleOutput(parsed.options, result.stdout, result.error);
|
||||
const stderr = handleOutput(parsed.options, result.stderr, result.error);
|
||||
|
||||
if (result.error || result.status !== 0 || result.signal !== null) {
|
||||
const error = makeError({
|
||||
stdout,
|
||||
stderr,
|
||||
error: result.error,
|
||||
signal: result.signal,
|
||||
exitCode: result.status,
|
||||
command,
|
||||
escapedCommand,
|
||||
parsed,
|
||||
timedOut: result.error && result.error.code === 'ETIMEDOUT',
|
||||
isCanceled: false,
|
||||
killed: result.signal !== null,
|
||||
});
|
||||
|
||||
if (!parsed.options.reject) {
|
||||
return error;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
command,
|
||||
escapedCommand,
|
||||
exitCode: 0,
|
||||
stdout,
|
||||
stderr,
|
||||
failed: false,
|
||||
timedOut: false,
|
||||
isCanceled: false,
|
||||
killed: false,
|
||||
};
|
||||
}
|
||||
|
||||
const normalizeScriptStdin = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined
|
||||
? {stdin: 'inherit'}
|
||||
: {};
|
||||
|
||||
const normalizeScriptOptions = (options = {}) => ({
|
||||
preferLocal: true,
|
||||
...normalizeScriptStdin(options),
|
||||
...options,
|
||||
});
|
||||
|
||||
function create$(options) {
|
||||
function $(templatesOrOptions, ...expressions) {
|
||||
if (!Array.isArray(templatesOrOptions)) {
|
||||
return create$({...options, ...templatesOrOptions});
|
||||
}
|
||||
|
||||
const [file, ...args] = parseTemplates(templatesOrOptions, expressions);
|
||||
return execa(file, args, normalizeScriptOptions(options));
|
||||
}
|
||||
|
||||
$.sync = (templates, ...expressions) => {
|
||||
if (!Array.isArray(templates)) {
|
||||
throw new TypeError('Please use $(options).sync`command` instead of $.sync(options)`command`.');
|
||||
}
|
||||
|
||||
const [file, ...args] = parseTemplates(templates, expressions);
|
||||
return execaSync(file, args, normalizeScriptOptions(options));
|
||||
};
|
||||
|
||||
return $;
|
||||
}
|
||||
|
||||
export const $ = create$();
|
||||
|
||||
export function execaCommand(command, options) {
|
||||
const [file, ...args] = parseCommand(command);
|
||||
return execa(file, args, options);
|
||||
}
|
||||
|
||||
export function execaCommandSync(command, options) {
|
||||
const [file, ...args] = parseCommand(command);
|
||||
return execaSync(file, args, options);
|
||||
}
|
||||
|
||||
export function execaNode(scriptPath, args, options = {}) {
|
||||
if (args && !Array.isArray(args) && typeof args === 'object') {
|
||||
options = args;
|
||||
args = [];
|
||||
}
|
||||
|
||||
const stdio = normalizeStdioNode(options);
|
||||
const defaultExecArgv = process.execArgv.filter(arg => !arg.startsWith('--inspect'));
|
||||
|
||||
const {
|
||||
nodePath = process.execPath,
|
||||
nodeOptions = defaultExecArgv,
|
||||
} = options;
|
||||
|
||||
return execa(
|
||||
nodePath,
|
||||
[
|
||||
...nodeOptions,
|
||||
scriptPath,
|
||||
...(Array.isArray(args) ? args : []),
|
||||
],
|
||||
{
|
||||
...options,
|
||||
stdin: undefined,
|
||||
stdout: undefined,
|
||||
stderr: undefined,
|
||||
stdio,
|
||||
shell: false,
|
||||
},
|
||||
);
|
||||
}
|
119
src/main/lib/execa/lib/command.js
Executable file
119
src/main/lib/execa/lib/command.js
Executable file
@ -0,0 +1,119 @@
|
||||
import {Buffer} from 'node:buffer';
|
||||
import {ChildProcess} from 'node:child_process';
|
||||
|
||||
const normalizeArgs = (file, args = []) => {
|
||||
if (!Array.isArray(args)) {
|
||||
return [file];
|
||||
}
|
||||
|
||||
return [file, ...args];
|
||||
};
|
||||
|
||||
const NO_ESCAPE_REGEXP = /^[\w.-]+$/;
|
||||
|
||||
const escapeArg = arg => {
|
||||
if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
return `"${arg.replaceAll('"', '\\"')}"`;
|
||||
};
|
||||
|
||||
export const joinCommand = (file, args) => normalizeArgs(file, args).join(' ');
|
||||
|
||||
export const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
|
||||
|
||||
const SPACES_REGEXP = / +/g;
|
||||
|
||||
// Handle `execaCommand()`
|
||||
export const parseCommand = command => {
|
||||
const tokens = [];
|
||||
for (const token of command.trim().split(SPACES_REGEXP)) {
|
||||
// Allow spaces to be escaped by a backslash if not meant as a delimiter
|
||||
const previousToken = tokens.at(-1);
|
||||
if (previousToken && previousToken.endsWith('\\')) {
|
||||
// Merge previous token with current one
|
||||
tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`;
|
||||
} else {
|
||||
tokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
const parseExpression = expression => {
|
||||
const typeOfExpression = typeof expression;
|
||||
|
||||
if (typeOfExpression === 'string') {
|
||||
return expression;
|
||||
}
|
||||
|
||||
if (typeOfExpression === 'number') {
|
||||
return String(expression);
|
||||
}
|
||||
|
||||
if (
|
||||
typeOfExpression === 'object'
|
||||
&& expression !== null
|
||||
&& !(expression instanceof ChildProcess)
|
||||
&& 'stdout' in expression
|
||||
) {
|
||||
const typeOfStdout = typeof expression.stdout;
|
||||
|
||||
if (typeOfStdout === 'string') {
|
||||
return expression.stdout;
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(expression.stdout)) {
|
||||
return expression.stdout.toString();
|
||||
}
|
||||
|
||||
throw new TypeError(`Unexpected "${typeOfStdout}" stdout in template expression`);
|
||||
}
|
||||
|
||||
throw new TypeError(`Unexpected "${typeOfExpression}" in template expression`);
|
||||
};
|
||||
|
||||
const concatTokens = (tokens, nextTokens, isNew) => isNew || tokens.length === 0 || nextTokens.length === 0
|
||||
? [...tokens, ...nextTokens]
|
||||
: [
|
||||
...tokens.slice(0, -1),
|
||||
`${tokens.at(-1)}${nextTokens[0]}`,
|
||||
...nextTokens.slice(1),
|
||||
];
|
||||
|
||||
const parseTemplate = ({templates, expressions, tokens, index, template}) => {
|
||||
const templateString = template ?? templates.raw[index];
|
||||
const templateTokens = templateString.split(SPACES_REGEXP).filter(Boolean);
|
||||
const newTokens = concatTokens(
|
||||
tokens,
|
||||
templateTokens,
|
||||
templateString.startsWith(' '),
|
||||
);
|
||||
|
||||
if (index === expressions.length) {
|
||||
return newTokens;
|
||||
}
|
||||
|
||||
const expression = expressions[index];
|
||||
const expressionTokens = Array.isArray(expression)
|
||||
? expression.map(expression => parseExpression(expression))
|
||||
: [parseExpression(expression)];
|
||||
return concatTokens(
|
||||
newTokens,
|
||||
expressionTokens,
|
||||
templateString.endsWith(' '),
|
||||
);
|
||||
};
|
||||
|
||||
export const parseTemplates = (templates, expressions) => {
|
||||
let tokens = [];
|
||||
|
||||
for (const [index, template] of templates.entries()) {
|
||||
tokens = parseTemplate({templates, expressions, tokens, index, template});
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
87
src/main/lib/execa/lib/error.js
Executable file
87
src/main/lib/execa/lib/error.js
Executable file
@ -0,0 +1,87 @@
|
||||
import process from 'node:process';
|
||||
import {signalsByName} from '../../human-signals';
|
||||
|
||||
const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => {
|
||||
if (timedOut) {
|
||||
return `timed out after ${timeout} milliseconds`;
|
||||
}
|
||||
|
||||
if (isCanceled) {
|
||||
return 'was canceled';
|
||||
}
|
||||
|
||||
if (errorCode !== undefined) {
|
||||
return `failed with ${errorCode}`;
|
||||
}
|
||||
|
||||
if (signal !== undefined) {
|
||||
return `was killed with ${signal} (${signalDescription})`;
|
||||
}
|
||||
|
||||
if (exitCode !== undefined) {
|
||||
return `failed with exit code ${exitCode}`;
|
||||
}
|
||||
|
||||
return 'failed';
|
||||
};
|
||||
|
||||
export const makeError = ({
|
||||
stdout,
|
||||
stderr,
|
||||
all,
|
||||
error,
|
||||
signal,
|
||||
exitCode,
|
||||
command,
|
||||
escapedCommand,
|
||||
timedOut,
|
||||
isCanceled,
|
||||
killed,
|
||||
parsed: {options: {timeout, cwd = process.cwd()}},
|
||||
}) => {
|
||||
// `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`.
|
||||
// We normalize them to `undefined`
|
||||
exitCode = exitCode === null ? undefined : exitCode;
|
||||
signal = signal === null ? undefined : signal;
|
||||
const signalDescription = signal === undefined ? undefined : signalsByName[signal].description;
|
||||
|
||||
const errorCode = error && error.code;
|
||||
|
||||
const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled});
|
||||
const execaMessage = `Command ${prefix}: ${command}`;
|
||||
const isError = Object.prototype.toString.call(error) === '[object Error]';
|
||||
const shortMessage = isError ? `${execaMessage}\n${error.message}` : execaMessage;
|
||||
const message = [shortMessage, stderr, stdout].filter(Boolean).join('\n');
|
||||
|
||||
if (isError) {
|
||||
error.originalMessage = error.message;
|
||||
error.message = message;
|
||||
} else {
|
||||
error = new Error(message);
|
||||
}
|
||||
|
||||
error.shortMessage = shortMessage;
|
||||
error.command = command;
|
||||
error.escapedCommand = escapedCommand;
|
||||
error.exitCode = exitCode;
|
||||
error.signal = signal;
|
||||
error.signalDescription = signalDescription;
|
||||
error.stdout = stdout;
|
||||
error.stderr = stderr;
|
||||
error.cwd = cwd;
|
||||
|
||||
if (all !== undefined) {
|
||||
error.all = all;
|
||||
}
|
||||
|
||||
if ('bufferedData' in error) {
|
||||
delete error.bufferedData;
|
||||
}
|
||||
|
||||
error.failed = true;
|
||||
error.timedOut = Boolean(timedOut);
|
||||
error.isCanceled = isCanceled;
|
||||
error.killed = killed && !timedOut;
|
||||
|
||||
return error;
|
||||
};
|
102
src/main/lib/execa/lib/kill.js
Executable file
102
src/main/lib/execa/lib/kill.js
Executable file
@ -0,0 +1,102 @@
|
||||
import os from 'node:os';
|
||||
import {onExit} from 'signal-exit';
|
||||
|
||||
const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5;
|
||||
|
||||
// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior
|
||||
export const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => {
|
||||
const killResult = kill(signal);
|
||||
setKillTimeout(kill, signal, options, killResult);
|
||||
return killResult;
|
||||
};
|
||||
|
||||
const setKillTimeout = (kill, signal, options, killResult) => {
|
||||
if (!shouldForceKill(signal, options, killResult)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = getForceKillAfterTimeout(options);
|
||||
const t = setTimeout(() => {
|
||||
kill('SIGKILL');
|
||||
}, timeout);
|
||||
|
||||
// Guarded because there's no `.unref()` when `execa` is used in the renderer
|
||||
// process in Electron. This cannot be tested since we don't run tests in
|
||||
// Electron.
|
||||
// istanbul ignore else
|
||||
if (t.unref) {
|
||||
t.unref();
|
||||
}
|
||||
};
|
||||
|
||||
const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => isSigterm(signal) && forceKillAfterTimeout !== false && killResult;
|
||||
|
||||
const isSigterm = signal => signal === os.constants.signals.SIGTERM
|
||||
|| (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM');
|
||||
|
||||
const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => {
|
||||
if (forceKillAfterTimeout === true) {
|
||||
return DEFAULT_FORCE_KILL_TIMEOUT;
|
||||
}
|
||||
|
||||
if (!Number.isFinite(forceKillAfterTimeout) || forceKillAfterTimeout < 0) {
|
||||
throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`);
|
||||
}
|
||||
|
||||
return forceKillAfterTimeout;
|
||||
};
|
||||
|
||||
// `childProcess.cancel()`
|
||||
export const spawnedCancel = (spawned, context) => {
|
||||
const killResult = spawned.kill();
|
||||
|
||||
if (killResult) {
|
||||
context.isCanceled = true;
|
||||
}
|
||||
};
|
||||
|
||||
const timeoutKill = (spawned, signal, reject) => {
|
||||
spawned.kill(signal);
|
||||
reject(Object.assign(new Error('Timed out'), {timedOut: true, signal}));
|
||||
};
|
||||
|
||||
// `timeout` option handling
|
||||
export const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => {
|
||||
if (timeout === 0 || timeout === undefined) {
|
||||
return spawnedPromise;
|
||||
}
|
||||
|
||||
let timeoutId;
|
||||
const timeoutPromise = new Promise((resolve, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutKill(spawned, killSignal, reject);
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
const safeSpawnedPromise = spawnedPromise.finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
return Promise.race([timeoutPromise, safeSpawnedPromise]);
|
||||
};
|
||||
|
||||
export const validateTimeout = ({timeout}) => {
|
||||
if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
|
||||
throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`);
|
||||
}
|
||||
};
|
||||
|
||||
// `cleanup` option handling
|
||||
export const setExitHandler = async (spawned, {cleanup, detached}, timedPromise) => {
|
||||
if (!cleanup || detached) {
|
||||
return timedPromise;
|
||||
}
|
||||
|
||||
const removeExitHandler = onExit(() => {
|
||||
spawned.kill();
|
||||
});
|
||||
|
||||
return timedPromise.finally(() => {
|
||||
removeExitHandler();
|
||||
});
|
||||
};
|
42
src/main/lib/execa/lib/pipe.js
Executable file
42
src/main/lib/execa/lib/pipe.js
Executable file
@ -0,0 +1,42 @@
|
||||
import {createWriteStream} from 'node:fs';
|
||||
import {ChildProcess} from 'node:child_process';
|
||||
import {isWritableStream} from '../../is-stream';
|
||||
|
||||
const isExecaChildProcess = target => target instanceof ChildProcess && typeof target.then === 'function';
|
||||
|
||||
const pipeToTarget = (spawned, streamName, target) => {
|
||||
if (typeof target === 'string') {
|
||||
spawned[streamName].pipe(createWriteStream(target));
|
||||
return spawned;
|
||||
}
|
||||
|
||||
if (isWritableStream(target)) {
|
||||
spawned[streamName].pipe(target);
|
||||
return spawned;
|
||||
}
|
||||
|
||||
if (!isExecaChildProcess(target)) {
|
||||
throw new TypeError('The second argument must be a string, a stream or an Execa child process.');
|
||||
}
|
||||
|
||||
if (!isWritableStream(target.stdin)) {
|
||||
throw new TypeError('The target child process\'s stdin must be available.');
|
||||
}
|
||||
|
||||
spawned[streamName].pipe(target.stdin);
|
||||
return target;
|
||||
};
|
||||
|
||||
export const addPipeMethods = spawned => {
|
||||
if (spawned.stdout !== null) {
|
||||
spawned.pipeStdout = pipeToTarget.bind(undefined, spawned, 'stdout');
|
||||
}
|
||||
|
||||
if (spawned.stderr !== null) {
|
||||
spawned.pipeStderr = pipeToTarget.bind(undefined, spawned, 'stderr');
|
||||
}
|
||||
|
||||
if (spawned.all !== undefined) {
|
||||
spawned.pipeAll = pipeToTarget.bind(undefined, spawned, 'all');
|
||||
}
|
||||
};
|
36
src/main/lib/execa/lib/promise.js
Executable file
36
src/main/lib/execa/lib/promise.js
Executable file
@ -0,0 +1,36 @@
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
const nativePromisePrototype = (async () => {})().constructor.prototype;
|
||||
|
||||
const descriptors = ['then', 'catch', 'finally'].map(property => [
|
||||
property,
|
||||
Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property),
|
||||
]);
|
||||
|
||||
// The return value is a mixin of `childProcess` and `Promise`
|
||||
export const mergePromise = (spawned, promise) => {
|
||||
for (const [property, descriptor] of descriptors) {
|
||||
// Starting the main `promise` is deferred to avoid consuming streams
|
||||
const value = typeof promise === 'function'
|
||||
? (...args) => Reflect.apply(descriptor.value, promise(), args)
|
||||
: descriptor.value.bind(promise);
|
||||
|
||||
Reflect.defineProperty(spawned, property, {...descriptor, value});
|
||||
}
|
||||
};
|
||||
|
||||
// Use promises instead of `child_process` events
|
||||
export const getSpawnedPromise = spawned => new Promise((resolve, reject) => {
|
||||
spawned.on('exit', (exitCode, signal) => {
|
||||
resolve({exitCode, signal});
|
||||
});
|
||||
|
||||
spawned.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (spawned.stdin) {
|
||||
spawned.stdin.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
49
src/main/lib/execa/lib/stdio.js
Executable file
49
src/main/lib/execa/lib/stdio.js
Executable file
@ -0,0 +1,49 @@
|
||||
const aliases = ['stdin', 'stdout', 'stderr'];
|
||||
|
||||
const hasAlias = options => aliases.some(alias => options[alias] !== undefined);
|
||||
|
||||
export const normalizeStdio = options => {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {stdio} = options;
|
||||
|
||||
if (stdio === undefined) {
|
||||
return aliases.map(alias => options[alias]);
|
||||
}
|
||||
|
||||
if (hasAlias(options)) {
|
||||
throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`);
|
||||
}
|
||||
|
||||
if (typeof stdio === 'string') {
|
||||
return stdio;
|
||||
}
|
||||
|
||||
if (!Array.isArray(stdio)) {
|
||||
throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``);
|
||||
}
|
||||
|
||||
const length = Math.max(stdio.length, aliases.length);
|
||||
return Array.from({length}, (value, index) => stdio[index]);
|
||||
};
|
||||
|
||||
// `ipc` is pushed unless it is already present
|
||||
export const normalizeStdioNode = options => {
|
||||
const stdio = normalizeStdio(options);
|
||||
|
||||
if (stdio === 'ipc') {
|
||||
return 'ipc';
|
||||
}
|
||||
|
||||
if (stdio === undefined || typeof stdio === 'string') {
|
||||
return [stdio, stdio, stdio, 'ipc'];
|
||||
}
|
||||
|
||||
if (stdio.includes('ipc')) {
|
||||
return stdio;
|
||||
}
|
||||
|
||||
return [...stdio, 'ipc'];
|
||||
};
|
133
src/main/lib/execa/lib/stream.js
Executable file
133
src/main/lib/execa/lib/stream.js
Executable file
@ -0,0 +1,133 @@
|
||||
import {createReadStream, readFileSync} from 'node:fs';
|
||||
import {setTimeout} from 'node:timers/promises';
|
||||
import {isStream} from '../../is-stream';
|
||||
import getStream, {getStreamAsBuffer} from '../../get-stream';
|
||||
import mergeStream from 'merge-stream';
|
||||
|
||||
const validateInputOptions = input => {
|
||||
if (input !== undefined) {
|
||||
throw new TypeError('The `input` and `inputFile` options cannot be both set.');
|
||||
}
|
||||
};
|
||||
|
||||
const getInputSync = ({input, inputFile}) => {
|
||||
if (typeof inputFile !== 'string') {
|
||||
return input;
|
||||
}
|
||||
|
||||
validateInputOptions(input);
|
||||
return readFileSync(inputFile);
|
||||
};
|
||||
|
||||
// `input` and `inputFile` option in sync mode
|
||||
export const handleInputSync = options => {
|
||||
const input = getInputSync(options);
|
||||
|
||||
if (isStream(input)) {
|
||||
throw new TypeError('The `input` option cannot be a stream in sync mode');
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
const getInput = ({input, inputFile}) => {
|
||||
if (typeof inputFile !== 'string') {
|
||||
return input;
|
||||
}
|
||||
|
||||
validateInputOptions(input);
|
||||
return createReadStream(inputFile);
|
||||
};
|
||||
|
||||
// `input` and `inputFile` option in async mode
|
||||
export const handleInput = (spawned, options) => {
|
||||
const input = getInput(options);
|
||||
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStream(input)) {
|
||||
input.pipe(spawned.stdin);
|
||||
} else {
|
||||
spawned.stdin.end(input);
|
||||
}
|
||||
};
|
||||
|
||||
// `all` interleaves `stdout` and `stderr`
|
||||
export const makeAllStream = (spawned, {all}) => {
|
||||
if (!all || (!spawned.stdout && !spawned.stderr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mixed = mergeStream();
|
||||
|
||||
if (spawned.stdout) {
|
||||
mixed.add(spawned.stdout);
|
||||
}
|
||||
|
||||
if (spawned.stderr) {
|
||||
mixed.add(spawned.stderr);
|
||||
}
|
||||
|
||||
return mixed;
|
||||
};
|
||||
|
||||
// On failure, `result.stdout|stderr|all` should contain the currently buffered stream
|
||||
const getBufferedData = async (stream, streamPromise) => {
|
||||
// When `buffer` is `false`, `streamPromise` is `undefined` and there is no buffered data to retrieve
|
||||
if (!stream || streamPromise === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the `all` stream to receive the last chunk before destroying the stream
|
||||
await setTimeout(0);
|
||||
|
||||
stream.destroy();
|
||||
|
||||
try {
|
||||
return await streamPromise;
|
||||
} catch (error) {
|
||||
return error.bufferedData;
|
||||
}
|
||||
};
|
||||
|
||||
const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => {
|
||||
if (!stream || !buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unicorn/text-encoding-identifier-case
|
||||
if (encoding === 'utf8' || encoding === 'utf-8') {
|
||||
return getStream(stream, {maxBuffer});
|
||||
}
|
||||
|
||||
if (encoding === null || encoding === 'buffer') {
|
||||
return getStreamAsBuffer(stream, {maxBuffer});
|
||||
}
|
||||
|
||||
return applyEncoding(stream, maxBuffer, encoding);
|
||||
};
|
||||
|
||||
const applyEncoding = async (stream, maxBuffer, encoding) => {
|
||||
const buffer = await getStreamAsBuffer(stream, {maxBuffer});
|
||||
return buffer.toString(encoding);
|
||||
};
|
||||
|
||||
// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all)
|
||||
export const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => {
|
||||
const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer});
|
||||
const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer});
|
||||
const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2});
|
||||
|
||||
try {
|
||||
return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]);
|
||||
} catch (error) {
|
||||
return Promise.all([
|
||||
{error, signal: error.signal, timedOut: error.timedOut},
|
||||
getBufferedData(stdout, stdoutPromise),
|
||||
getBufferedData(stderr, stderrPromise),
|
||||
getBufferedData(all, allPromise),
|
||||
]);
|
||||
}
|
||||
};
|
19
src/main/lib/execa/lib/verbose.js
Executable file
19
src/main/lib/execa/lib/verbose.js
Executable file
@ -0,0 +1,19 @@
|
||||
import {debuglog} from 'node:util';
|
||||
import process from 'node:process';
|
||||
|
||||
export const verboseDefault = debuglog('execa').enabled;
|
||||
|
||||
const padField = (field, padding) => String(field).padStart(padding, '0');
|
||||
|
||||
const getTimestamp = () => {
|
||||
const date = new Date();
|
||||
return `${padField(date.getHours(), 2)}:${padField(date.getMinutes(), 2)}:${padField(date.getSeconds(), 2)}.${padField(date.getMilliseconds(), 3)}`;
|
||||
};
|
||||
|
||||
export const logCommand = (escapedCommand, {verbose}) => {
|
||||
if (!verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.stderr.write(`[${getTimestamp()}] ${escapedCommand}\n`);
|
||||
};
|
84
src/main/lib/get-stream/array-buffer.js
Normal file
84
src/main/lib/get-stream/array-buffer.js
Normal file
@ -0,0 +1,84 @@
|
||||
import {getStreamContents} from './contents.js';
|
||||
import {noop, throwObjectStream, getLengthProp} from './utils.js';
|
||||
|
||||
export async function getStreamAsArrayBuffer(stream, options) {
|
||||
return getStreamContents(stream, arrayBufferMethods, options);
|
||||
}
|
||||
|
||||
const initArrayBuffer = () => ({contents: new ArrayBuffer(0)});
|
||||
|
||||
const useTextEncoder = chunk => textEncoder.encode(chunk);
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
const useUint8Array = chunk => new Uint8Array(chunk);
|
||||
|
||||
const useUint8ArrayWithOffset = chunk => new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
||||
|
||||
const truncateArrayBufferChunk = (convertedChunk, chunkSize) => convertedChunk.slice(0, chunkSize);
|
||||
|
||||
// `contents` is an increasingly growing `Uint8Array`.
|
||||
const addArrayBufferChunk = (convertedChunk, {contents, length: previousLength}, length) => {
|
||||
const newContents = hasArrayBufferResize() ? resizeArrayBuffer(contents, length) : resizeArrayBufferSlow(contents, length);
|
||||
new Uint8Array(newContents).set(convertedChunk, previousLength);
|
||||
return newContents;
|
||||
};
|
||||
|
||||
// Without `ArrayBuffer.resize()`, `contents` size is always a power of 2.
|
||||
// This means its last bytes are zeroes (not stream data), which need to be
|
||||
// trimmed at the end with `ArrayBuffer.slice()`.
|
||||
const resizeArrayBufferSlow = (contents, length) => {
|
||||
if (length <= contents.byteLength) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
const arrayBuffer = new ArrayBuffer(getNewContentsLength(length));
|
||||
new Uint8Array(arrayBuffer).set(new Uint8Array(contents), 0);
|
||||
return arrayBuffer;
|
||||
};
|
||||
|
||||
// With `ArrayBuffer.resize()`, `contents` size matches exactly the size of
|
||||
// the stream data. It does not include extraneous zeroes to trim at the end.
|
||||
// The underlying `ArrayBuffer` does allocate a number of bytes that is a power
|
||||
// of 2, but those bytes are only visible after calling `ArrayBuffer.resize()`.
|
||||
const resizeArrayBuffer = (contents, length) => {
|
||||
if (length <= contents.maxByteLength) {
|
||||
contents.resize(length);
|
||||
return contents;
|
||||
}
|
||||
|
||||
const arrayBuffer = new ArrayBuffer(length, {maxByteLength: getNewContentsLength(length)});
|
||||
new Uint8Array(arrayBuffer).set(new Uint8Array(contents), 0);
|
||||
return arrayBuffer;
|
||||
};
|
||||
|
||||
// Retrieve the closest `length` that is both >= and a power of 2
|
||||
const getNewContentsLength = length => SCALE_FACTOR ** Math.ceil(Math.log(length) / Math.log(SCALE_FACTOR));
|
||||
|
||||
const SCALE_FACTOR = 2;
|
||||
|
||||
const finalizeArrayBuffer = ({contents, length}) => hasArrayBufferResize() ? contents : contents.slice(0, length);
|
||||
|
||||
// `ArrayBuffer.slice()` is slow. When `ArrayBuffer.resize()` is available
|
||||
// (Node >=20.0.0, Safari >=16.4 and Chrome), we can use it instead.
|
||||
// eslint-disable-next-line no-warning-comments
|
||||
// TODO: remove after dropping support for Node 20.
|
||||
// eslint-disable-next-line no-warning-comments
|
||||
// TODO: use `ArrayBuffer.transferToFixedLength()` instead once it is available
|
||||
const hasArrayBufferResize = () => 'resize' in ArrayBuffer.prototype;
|
||||
|
||||
const arrayBufferMethods = {
|
||||
init: initArrayBuffer,
|
||||
convertChunk: {
|
||||
string: useTextEncoder,
|
||||
buffer: useUint8Array,
|
||||
arrayBuffer: useUint8Array,
|
||||
dataView: useUint8ArrayWithOffset,
|
||||
typedArray: useUint8ArrayWithOffset,
|
||||
others: throwObjectStream,
|
||||
},
|
||||
getSize: getLengthProp,
|
||||
truncateChunk: truncateArrayBufferChunk,
|
||||
addChunk: addArrayBufferChunk,
|
||||
getFinalChunk: noop,
|
||||
finalize: finalizeArrayBuffer,
|
||||
};
|
32
src/main/lib/get-stream/array.js
Normal file
32
src/main/lib/get-stream/array.js
Normal file
@ -0,0 +1,32 @@
|
||||
import {getStreamContents} from './contents.js';
|
||||
import {identity, noop, getContentsProp} from './utils.js';
|
||||
|
||||
export async function getStreamAsArray(stream, options) {
|
||||
return getStreamContents(stream, arrayMethods, options);
|
||||
}
|
||||
|
||||
const initArray = () => ({contents: []});
|
||||
|
||||
const increment = () => 1;
|
||||
|
||||
const addArrayChunk = (convertedChunk, {contents}) => {
|
||||
contents.push(convertedChunk);
|
||||
return contents;
|
||||
};
|
||||
|
||||
const arrayMethods = {
|
||||
init: initArray,
|
||||
convertChunk: {
|
||||
string: identity,
|
||||
buffer: identity,
|
||||
arrayBuffer: identity,
|
||||
dataView: identity,
|
||||
typedArray: identity,
|
||||
others: identity,
|
||||
},
|
||||
getSize: increment,
|
||||
truncateChunk: noop,
|
||||
addChunk: addArrayChunk,
|
||||
getFinalChunk: noop,
|
||||
finalize: getContentsProp,
|
||||
};
|
20
src/main/lib/get-stream/buffer.js
Normal file
20
src/main/lib/get-stream/buffer.js
Normal file
@ -0,0 +1,20 @@
|
||||
import {getStreamAsArrayBuffer} from './array-buffer.js';
|
||||
|
||||
export async function getStreamAsBuffer(stream, options) {
|
||||
if (!('Buffer' in globalThis)) {
|
||||
throw new Error('getStreamAsBuffer() is only supported in Node.js');
|
||||
}
|
||||
|
||||
try {
|
||||
return arrayBufferToNodeBuffer(await getStreamAsArrayBuffer(stream, options));
|
||||
} catch (error) {
|
||||
if (error.bufferedData !== undefined) {
|
||||
error.bufferedData = arrayBufferToNodeBuffer(error.bufferedData);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line n/prefer-global/buffer
|
||||
const arrayBufferToNodeBuffer = arrayBuffer => globalThis.Buffer.from(arrayBuffer);
|
101
src/main/lib/get-stream/contents.js
Normal file
101
src/main/lib/get-stream/contents.js
Normal file
@ -0,0 +1,101 @@
|
||||
export const getStreamContents = async (stream, {init, convertChunk, getSize, truncateChunk, addChunk, getFinalChunk, finalize}, {maxBuffer = Number.POSITIVE_INFINITY} = {}) => {
|
||||
if (!isAsyncIterable(stream)) {
|
||||
throw new Error('The first argument must be a Readable, a ReadableStream, or an async iterable.');
|
||||
}
|
||||
|
||||
const state = init();
|
||||
state.length = 0;
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
const chunkType = getChunkType(chunk);
|
||||
const convertedChunk = convertChunk[chunkType](chunk, state);
|
||||
appendChunk({convertedChunk, state, getSize, truncateChunk, addChunk, maxBuffer});
|
||||
}
|
||||
|
||||
appendFinalChunk({state, convertChunk, getSize, truncateChunk, addChunk, getFinalChunk, maxBuffer});
|
||||
return finalize(state);
|
||||
} catch (error) {
|
||||
error.bufferedData = finalize(state);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const appendFinalChunk = ({state, getSize, truncateChunk, addChunk, getFinalChunk, maxBuffer}) => {
|
||||
const convertedChunk = getFinalChunk(state);
|
||||
if (convertedChunk !== undefined) {
|
||||
appendChunk({convertedChunk, state, getSize, truncateChunk, addChunk, maxBuffer});
|
||||
}
|
||||
};
|
||||
|
||||
const appendChunk = ({convertedChunk, state, getSize, truncateChunk, addChunk, maxBuffer}) => {
|
||||
const chunkSize = getSize(convertedChunk);
|
||||
const newLength = state.length + chunkSize;
|
||||
|
||||
if (newLength <= maxBuffer) {
|
||||
addNewChunk(convertedChunk, state, addChunk, newLength);
|
||||
return;
|
||||
}
|
||||
|
||||
const truncatedChunk = truncateChunk(convertedChunk, maxBuffer - state.length);
|
||||
|
||||
if (truncatedChunk !== undefined) {
|
||||
addNewChunk(truncatedChunk, state, addChunk, maxBuffer);
|
||||
}
|
||||
|
||||
throw new MaxBufferError();
|
||||
};
|
||||
|
||||
const addNewChunk = (convertedChunk, state, addChunk, newLength) => {
|
||||
state.contents = addChunk(convertedChunk, state, newLength);
|
||||
state.length = newLength;
|
||||
};
|
||||
|
||||
const isAsyncIterable = stream => typeof stream === 'object' && stream !== null && typeof stream[Symbol.asyncIterator] === 'function';
|
||||
|
||||
const getChunkType = chunk => {
|
||||
const typeOfChunk = typeof chunk;
|
||||
|
||||
if (typeOfChunk === 'string') {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
if (typeOfChunk !== 'object' || chunk === null) {
|
||||
return 'others';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line n/prefer-global/buffer
|
||||
if (globalThis.Buffer?.isBuffer(chunk)) {
|
||||
return 'buffer';
|
||||
}
|
||||
|
||||
const prototypeName = objectToString.call(chunk);
|
||||
|
||||
if (prototypeName === '[object ArrayBuffer]') {
|
||||
return 'arrayBuffer';
|
||||
}
|
||||
|
||||
if (prototypeName === '[object DataView]') {
|
||||
return 'dataView';
|
||||
}
|
||||
|
||||
if (
|
||||
Number.isInteger(chunk.byteLength)
|
||||
&& Number.isInteger(chunk.byteOffset)
|
||||
&& objectToString.call(chunk.buffer) === '[object ArrayBuffer]'
|
||||
) {
|
||||
return 'typedArray';
|
||||
}
|
||||
|
||||
return 'others';
|
||||
};
|
||||
|
||||
const {toString: objectToString} = Object.prototype;
|
||||
|
||||
export class MaxBufferError extends Error {
|
||||
name = 'MaxBufferError';
|
||||
|
||||
constructor() {
|
||||
super('maxBuffer exceeded');
|
||||
}
|
||||
}
|
119
src/main/lib/get-stream/index.d.ts
vendored
Normal file
119
src/main/lib/get-stream/index.d.ts
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
import {type Readable} from 'node:stream';
|
||||
import {type Buffer} from 'node:buffer';
|
||||
|
||||
export class MaxBufferError extends Error {
|
||||
readonly name: 'MaxBufferError';
|
||||
constructor();
|
||||
}
|
||||
|
||||
type TextStreamItem = string | Buffer | ArrayBuffer | ArrayBufferView;
|
||||
export type AnyStream<SteamItem = TextStreamItem> = Readable | ReadableStream<SteamItem> | AsyncIterable<SteamItem>;
|
||||
|
||||
export type Options = {
|
||||
/**
|
||||
Maximum length of the stream. If exceeded, the promise will be rejected with a `MaxBufferError`.
|
||||
|
||||
Depending on the [method](#api), the length is measured with [`string.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length), [`buffer.length`](https://nodejs.org/api/buffer.html#buflength), [`arrayBuffer.byteLength`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/byteLength) or [`array.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length).
|
||||
|
||||
@default Infinity
|
||||
*/
|
||||
readonly maxBuffer?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
Get the given `stream` as a string.
|
||||
|
||||
@returns The stream's contents as a promise.
|
||||
|
||||
@example
|
||||
```
|
||||
import fs from 'node:fs';
|
||||
import getStream from 'get-stream';
|
||||
|
||||
const stream = fs.createReadStream('unicorn.txt');
|
||||
|
||||
console.log(await getStream(stream));
|
||||
// ,,))))))));,
|
||||
// __)))))))))))))),
|
||||
// \|/ -\(((((''''((((((((.
|
||||
// -*-==//////(('' . `)))))),
|
||||
// /|\ ))| o ;-. '((((( ,(,
|
||||
// ( `| / ) ;))))' ,_))^;(~
|
||||
// | | | ,))((((_ _____------~~~-. %,;(;(>';'~
|
||||
// o_); ; )))(((` ~---~ `:: \ %%~~)(v;(`('~
|
||||
// ; ''''```` `: `:::|\,__,%% );`'; ~
|
||||
// | _ ) / `:|`----' `-'
|
||||
// ______/\/~ | / /
|
||||
// /~;;.____/;;' / ___--,-( `;;;/
|
||||
// / // _;______;'------~~~~~ /;;/\ /
|
||||
// // | | / ; \;;,\
|
||||
// (<_ | ; /',/-----' _>
|
||||
// \_| ||_ //~;~~~~~~~~~
|
||||
// `\_| (,~~
|
||||
// \~\
|
||||
// ~~
|
||||
```
|
||||
|
||||
@example
|
||||
```
|
||||
import getStream from 'get-stream';
|
||||
|
||||
const {body: readableStream} = await fetch('https://example.com');
|
||||
console.log(await getStream(readableStream));
|
||||
```
|
||||
|
||||
@example
|
||||
```
|
||||
import {opendir} from 'node:fs/promises';
|
||||
import {getStreamAsArray} from 'get-stream';
|
||||
|
||||
const asyncIterable = await opendir(directory);
|
||||
console.log(await getStreamAsArray(asyncIterable));
|
||||
```
|
||||
*/
|
||||
export default function getStream(stream: AnyStream, options?: Options): Promise<string>;
|
||||
|
||||
/**
|
||||
Get the given `stream` as a Node.js [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer).
|
||||
|
||||
@returns The stream's contents as a promise.
|
||||
|
||||
@example
|
||||
```
|
||||
import {getStreamAsBuffer} from 'get-stream';
|
||||
|
||||
const stream = fs.createReadStream('unicorn.png');
|
||||
console.log(await getStreamAsBuffer(stream));
|
||||
```
|
||||
*/
|
||||
export function getStreamAsBuffer(stream: AnyStream, options?: Options): Promise<Buffer>;
|
||||
|
||||
/**
|
||||
Get the given `stream` as an [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer).
|
||||
|
||||
@returns The stream's contents as a promise.
|
||||
|
||||
@example
|
||||
```
|
||||
import {getStreamAsArrayBuffer} from 'get-stream';
|
||||
|
||||
const {body: readableStream} = await fetch('https://example.com');
|
||||
console.log(await getStreamAsArrayBuffer(readableStream));
|
||||
```
|
||||
*/
|
||||
export function getStreamAsArrayBuffer(stream: AnyStream, options?: Options): Promise<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
Get the given `stream` as an array. Unlike [other methods](#api), this supports [streams of objects](https://nodejs.org/api/stream.html#object-mode).
|
||||
|
||||
@returns The stream's contents as a promise.
|
||||
|
||||
@example
|
||||
```
|
||||
import {getStreamAsArray} from 'get-stream';
|
||||
|
||||
const {body: readableStream} = await fetch('https://example.com');
|
||||
console.log(await getStreamAsArray(readableStream));
|
||||
```
|
||||
*/
|
||||
export function getStreamAsArray<Item>(stream: AnyStream<Item>, options?: Options): Promise<Item[]>;
|
5
src/main/lib/get-stream/index.js
Normal file
5
src/main/lib/get-stream/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
export {getStreamAsArray} from './array.js';
|
||||
export {getStreamAsArrayBuffer} from './array-buffer.js';
|
||||
export {getStreamAsBuffer} from './buffer.js';
|
||||
export {getStreamAsString as default} from './string.js';
|
||||
export {MaxBufferError} from './contents.js';
|
98
src/main/lib/get-stream/index.test-d.ts
Normal file
98
src/main/lib/get-stream/index.test-d.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import {Buffer} from 'node:buffer';
|
||||
import {open} from 'node:fs/promises';
|
||||
import {type Readable} from 'node:stream';
|
||||
import fs from 'node:fs';
|
||||
import {expectType, expectError, expectAssignable, expectNotAssignable} from 'tsd';
|
||||
import getStream, {getStreamAsBuffer, getStreamAsArrayBuffer, getStreamAsArray, MaxBufferError, type Options, type AnyStream} from './index.js';
|
||||
|
||||
const nodeStream = fs.createReadStream('foo') as Readable;
|
||||
|
||||
const fileHandle = await open('test');
|
||||
const readableStream = fileHandle.readableWebStream();
|
||||
|
||||
const asyncIterable = <T>(value: T): AsyncGenerator<T> => (async function * () {
|
||||
yield value;
|
||||
})();
|
||||
const stringAsyncIterable = asyncIterable('');
|
||||
const bufferAsyncIterable = asyncIterable(Buffer.from(''));
|
||||
const arrayBufferAsyncIterable = asyncIterable(new ArrayBuffer(0));
|
||||
const dataViewAsyncIterable = asyncIterable(new DataView(new ArrayBuffer(0)));
|
||||
const typedArrayAsyncIterable = asyncIterable(new Uint8Array([]));
|
||||
const objectItem = {test: true};
|
||||
const objectAsyncIterable = asyncIterable(objectItem);
|
||||
|
||||
expectType<string>(await getStream(nodeStream));
|
||||
expectType<string>(await getStream(nodeStream, {maxBuffer: 10}));
|
||||
expectType<string>(await getStream(readableStream));
|
||||
expectType<string>(await getStream(stringAsyncIterable));
|
||||
expectType<string>(await getStream(bufferAsyncIterable));
|
||||
expectType<string>(await getStream(arrayBufferAsyncIterable));
|
||||
expectType<string>(await getStream(dataViewAsyncIterable));
|
||||
expectType<string>(await getStream(typedArrayAsyncIterable));
|
||||
expectError(await getStream(objectAsyncIterable));
|
||||
expectError(await getStream({}));
|
||||
expectError(await getStream(nodeStream, {maxBuffer: '10'}));
|
||||
expectError(await getStream(nodeStream, {unknownOption: 10}));
|
||||
expectError(await getStream(nodeStream, {maxBuffer: 10}, {}));
|
||||
|
||||
expectType<Buffer>(await getStreamAsBuffer(nodeStream));
|
||||
expectType<Buffer>(await getStreamAsBuffer(nodeStream, {maxBuffer: 10}));
|
||||
expectType<Buffer>(await getStreamAsBuffer(readableStream));
|
||||
expectType<Buffer>(await getStreamAsBuffer(stringAsyncIterable));
|
||||
expectType<Buffer>(await getStreamAsBuffer(bufferAsyncIterable));
|
||||
expectType<Buffer>(await getStreamAsBuffer(arrayBufferAsyncIterable));
|
||||
expectType<Buffer>(await getStreamAsBuffer(dataViewAsyncIterable));
|
||||
expectType<Buffer>(await getStreamAsBuffer(typedArrayAsyncIterable));
|
||||
expectError(await getStreamAsBuffer(objectAsyncIterable));
|
||||
expectError(await getStreamAsBuffer({}));
|
||||
expectError(await getStreamAsBuffer(nodeStream, {maxBuffer: '10'}));
|
||||
expectError(await getStreamAsBuffer(nodeStream, {unknownOption: 10}));
|
||||
expectError(await getStreamAsBuffer(nodeStream, {maxBuffer: 10}, {}));
|
||||
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(nodeStream));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(nodeStream, {maxBuffer: 10}));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(readableStream));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(stringAsyncIterable));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(bufferAsyncIterable));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(arrayBufferAsyncIterable));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(dataViewAsyncIterable));
|
||||
expectType<ArrayBuffer>(await getStreamAsArrayBuffer(typedArrayAsyncIterable));
|
||||
expectError(await getStreamAsArrayBuffer(objectAsyncIterable));
|
||||
expectError(await getStreamAsArrayBuffer({}));
|
||||
expectError(await getStreamAsArrayBuffer(nodeStream, {maxBuffer: '10'}));
|
||||
expectError(await getStreamAsArrayBuffer(nodeStream, {unknownOption: 10}));
|
||||
expectError(await getStreamAsArrayBuffer(nodeStream, {maxBuffer: 10}, {}));
|
||||
|
||||
expectType<any[]>(await getStreamAsArray(nodeStream));
|
||||
expectType<any[]>(await getStreamAsArray(nodeStream, {maxBuffer: 10}));
|
||||
expectType<any[]>(await getStreamAsArray(readableStream));
|
||||
expectType<Uint8Array[]>(await getStreamAsArray(readableStream as ReadableStream<Uint8Array>));
|
||||
expectType<string[]>(await getStreamAsArray(stringAsyncIterable));
|
||||
expectType<Buffer[]>(await getStreamAsArray(bufferAsyncIterable));
|
||||
expectType<ArrayBuffer[]>(await getStreamAsArray(arrayBufferAsyncIterable));
|
||||
expectType<DataView[]>(await getStreamAsArray(dataViewAsyncIterable));
|
||||
expectType<Uint8Array[]>(await getStreamAsArray(typedArrayAsyncIterable));
|
||||
expectType<Array<typeof objectItem>>(await getStreamAsArray(objectAsyncIterable));
|
||||
expectError(await getStreamAsArray({}));
|
||||
expectError(await getStreamAsArray(nodeStream, {maxBuffer: '10'}));
|
||||
expectError(await getStreamAsArray(nodeStream, {unknownOption: 10}));
|
||||
expectError(await getStreamAsArray(nodeStream, {maxBuffer: 10}, {}));
|
||||
|
||||
expectAssignable<AnyStream>(nodeStream);
|
||||
expectAssignable<AnyStream>(readableStream);
|
||||
expectAssignable<AnyStream>(stringAsyncIterable);
|
||||
expectAssignable<AnyStream>(bufferAsyncIterable);
|
||||
expectAssignable<AnyStream>(arrayBufferAsyncIterable);
|
||||
expectAssignable<AnyStream>(dataViewAsyncIterable);
|
||||
expectAssignable<AnyStream>(typedArrayAsyncIterable);
|
||||
expectAssignable<AnyStream<unknown>>(objectAsyncIterable);
|
||||
expectNotAssignable<AnyStream>(objectAsyncIterable);
|
||||
expectAssignable<AnyStream<string>>(stringAsyncIterable);
|
||||
expectNotAssignable<AnyStream<string>>(bufferAsyncIterable);
|
||||
expectNotAssignable<AnyStream>({});
|
||||
|
||||
expectAssignable<Options>({maxBuffer: 10});
|
||||
expectNotAssignable<Options>({maxBuffer: '10'});
|
||||
expectNotAssignable<Options>({unknownOption: 10});
|
||||
|
||||
expectType<MaxBufferError>(new MaxBufferError());
|
36
src/main/lib/get-stream/string.js
Normal file
36
src/main/lib/get-stream/string.js
Normal file
@ -0,0 +1,36 @@
|
||||
import {getStreamContents} from './contents.js';
|
||||
import {identity, getContentsProp, throwObjectStream, getLengthProp} from './utils.js';
|
||||
|
||||
export async function getStreamAsString(stream, options) {
|
||||
return getStreamContents(stream, stringMethods, options);
|
||||
}
|
||||
|
||||
const initString = () => ({contents: '', textDecoder: new TextDecoder()});
|
||||
|
||||
const useTextDecoder = (chunk, {textDecoder}) => textDecoder.decode(chunk, {stream: true});
|
||||
|
||||
const addStringChunk = (convertedChunk, {contents}) => contents + convertedChunk;
|
||||
|
||||
const truncateStringChunk = (convertedChunk, chunkSize) => convertedChunk.slice(0, chunkSize);
|
||||
|
||||
const getFinalStringChunk = ({textDecoder}) => {
|
||||
const finalChunk = textDecoder.decode();
|
||||
return finalChunk === '' ? undefined : finalChunk;
|
||||
};
|
||||
|
||||
const stringMethods = {
|
||||
init: initString,
|
||||
convertChunk: {
|
||||
string: identity,
|
||||
buffer: useTextDecoder,
|
||||
arrayBuffer: useTextDecoder,
|
||||
dataView: useTextDecoder,
|
||||
typedArray: useTextDecoder,
|
||||
others: throwObjectStream,
|
||||
},
|
||||
getSize: getLengthProp,
|
||||
truncateChunk: truncateStringChunk,
|
||||
addChunk: addStringChunk,
|
||||
getFinalChunk: getFinalStringChunk,
|
||||
finalize: getContentsProp,
|
||||
};
|
11
src/main/lib/get-stream/utils.js
Normal file
11
src/main/lib/get-stream/utils.js
Normal file
@ -0,0 +1,11 @@
|
||||
export const identity = value => value;
|
||||
|
||||
export const noop = () => undefined;
|
||||
|
||||
export const getContentsProp = ({contents}) => contents;
|
||||
|
||||
export const throwObjectStream = chunk => {
|
||||
throw new Error(`Streams in object mode are not supported: ${String(chunk)}`);
|
||||
};
|
||||
|
||||
export const getLengthProp = convertedChunk => convertedChunk.length;
|
275
src/main/lib/human-signals/core.js
Normal file
275
src/main/lib/human-signals/core.js
Normal file
@ -0,0 +1,275 @@
|
||||
/* eslint-disable max-lines */
|
||||
// List of known process signals with information about them
|
||||
export const SIGNALS = [
|
||||
{
|
||||
name: 'SIGHUP',
|
||||
number: 1,
|
||||
action: 'terminate',
|
||||
description: 'Terminal closed',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGINT',
|
||||
number: 2,
|
||||
action: 'terminate',
|
||||
description: 'User interruption with CTRL-C',
|
||||
standard: 'ansi',
|
||||
},
|
||||
{
|
||||
name: 'SIGQUIT',
|
||||
number: 3,
|
||||
action: 'core',
|
||||
description: 'User interruption with CTRL-\\',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGILL',
|
||||
number: 4,
|
||||
action: 'core',
|
||||
description: 'Invalid machine instruction',
|
||||
standard: 'ansi',
|
||||
},
|
||||
{
|
||||
name: 'SIGTRAP',
|
||||
number: 5,
|
||||
action: 'core',
|
||||
description: 'Debugger breakpoint',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGABRT',
|
||||
number: 6,
|
||||
action: 'core',
|
||||
description: 'Aborted',
|
||||
standard: 'ansi',
|
||||
},
|
||||
{
|
||||
name: 'SIGIOT',
|
||||
number: 6,
|
||||
action: 'core',
|
||||
description: 'Aborted',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGBUS',
|
||||
number: 7,
|
||||
action: 'core',
|
||||
description:
|
||||
'Bus error due to misaligned, non-existing address or paging error',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGEMT',
|
||||
number: 7,
|
||||
action: 'terminate',
|
||||
description: 'Command should be emulated but is not implemented',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGFPE',
|
||||
number: 8,
|
||||
action: 'core',
|
||||
description: 'Floating point arithmetic error',
|
||||
standard: 'ansi',
|
||||
},
|
||||
{
|
||||
name: 'SIGKILL',
|
||||
number: 9,
|
||||
action: 'terminate',
|
||||
description: 'Forced termination',
|
||||
standard: 'posix',
|
||||
forced: true,
|
||||
},
|
||||
{
|
||||
name: 'SIGUSR1',
|
||||
number: 10,
|
||||
action: 'terminate',
|
||||
description: 'Application-specific signal',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGSEGV',
|
||||
number: 11,
|
||||
action: 'core',
|
||||
description: 'Segmentation fault',
|
||||
standard: 'ansi',
|
||||
},
|
||||
{
|
||||
name: 'SIGUSR2',
|
||||
number: 12,
|
||||
action: 'terminate',
|
||||
description: 'Application-specific signal',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGPIPE',
|
||||
number: 13,
|
||||
action: 'terminate',
|
||||
description: 'Broken pipe or socket',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGALRM',
|
||||
number: 14,
|
||||
action: 'terminate',
|
||||
description: 'Timeout or timer',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGTERM',
|
||||
number: 15,
|
||||
action: 'terminate',
|
||||
description: 'Termination',
|
||||
standard: 'ansi',
|
||||
},
|
||||
{
|
||||
name: 'SIGSTKFLT',
|
||||
number: 16,
|
||||
action: 'terminate',
|
||||
description: 'Stack is empty or overflowed',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGCHLD',
|
||||
number: 17,
|
||||
action: 'ignore',
|
||||
description: 'Child process terminated, paused or unpaused',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGCLD',
|
||||
number: 17,
|
||||
action: 'ignore',
|
||||
description: 'Child process terminated, paused or unpaused',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGCONT',
|
||||
number: 18,
|
||||
action: 'unpause',
|
||||
description: 'Unpaused',
|
||||
standard: 'posix',
|
||||
forced: true,
|
||||
},
|
||||
{
|
||||
name: 'SIGSTOP',
|
||||
number: 19,
|
||||
action: 'pause',
|
||||
description: 'Paused',
|
||||
standard: 'posix',
|
||||
forced: true,
|
||||
},
|
||||
{
|
||||
name: 'SIGTSTP',
|
||||
number: 20,
|
||||
action: 'pause',
|
||||
description: 'Paused using CTRL-Z or "suspend"',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGTTIN',
|
||||
number: 21,
|
||||
action: 'pause',
|
||||
description: 'Background process cannot read terminal input',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGBREAK',
|
||||
number: 21,
|
||||
action: 'terminate',
|
||||
description: 'User interruption with CTRL-BREAK',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGTTOU',
|
||||
number: 22,
|
||||
action: 'pause',
|
||||
description: 'Background process cannot write to terminal output',
|
||||
standard: 'posix',
|
||||
},
|
||||
{
|
||||
name: 'SIGURG',
|
||||
number: 23,
|
||||
action: 'ignore',
|
||||
description: 'Socket received out-of-band data',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGXCPU',
|
||||
number: 24,
|
||||
action: 'core',
|
||||
description: 'Process timed out',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGXFSZ',
|
||||
number: 25,
|
||||
action: 'core',
|
||||
description: 'File too big',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGVTALRM',
|
||||
number: 26,
|
||||
action: 'terminate',
|
||||
description: 'Timeout or timer',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGPROF',
|
||||
number: 27,
|
||||
action: 'terminate',
|
||||
description: 'Timeout or timer',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGWINCH',
|
||||
number: 28,
|
||||
action: 'ignore',
|
||||
description: 'Terminal window size changed',
|
||||
standard: 'bsd',
|
||||
},
|
||||
{
|
||||
name: 'SIGIO',
|
||||
number: 29,
|
||||
action: 'terminate',
|
||||
description: 'I/O is available',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGPOLL',
|
||||
number: 29,
|
||||
action: 'terminate',
|
||||
description: 'Watched event',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGINFO',
|
||||
number: 29,
|
||||
action: 'ignore',
|
||||
description: 'Request for process information',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGPWR',
|
||||
number: 30,
|
||||
action: 'terminate',
|
||||
description: 'Device running out of power',
|
||||
standard: 'systemv',
|
||||
},
|
||||
{
|
||||
name: 'SIGSYS',
|
||||
number: 31,
|
||||
action: 'core',
|
||||
description: 'Invalid system call',
|
||||
standard: 'other',
|
||||
},
|
||||
{
|
||||
name: 'SIGUNUSED',
|
||||
number: 31,
|
||||
action: 'terminate',
|
||||
description: 'Invalid system call',
|
||||
standard: 'other',
|
||||
},
|
||||
]
|
||||
/* eslint-enable max-lines */
|
70
src/main/lib/human-signals/index.js
Normal file
70
src/main/lib/human-signals/index.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { constants } from 'node:os'
|
||||
|
||||
import { SIGRTMAX } from './realtime.js'
|
||||
import { getSignals } from './signals.js'
|
||||
|
||||
// Retrieve `signalsByName`, an object mapping signal name to signal properties.
|
||||
// We make sure the object is sorted by `number`.
|
||||
const getSignalsByName = () => {
|
||||
const signals = getSignals()
|
||||
return Object.fromEntries(signals.map(getSignalByName))
|
||||
}
|
||||
|
||||
const getSignalByName = ({
|
||||
name,
|
||||
number,
|
||||
description,
|
||||
supported,
|
||||
action,
|
||||
forced,
|
||||
standard,
|
||||
}) => [name, { name, number, description, supported, action, forced, standard }]
|
||||
|
||||
export const signalsByName = getSignalsByName()
|
||||
|
||||
// Retrieve `signalsByNumber`, an object mapping signal number to signal
|
||||
// properties.
|
||||
// We make sure the object is sorted by `number`.
|
||||
const getSignalsByNumber = () => {
|
||||
const signals = getSignals()
|
||||
const length = SIGRTMAX + 1
|
||||
const signalsA = Array.from({ length }, (value, number) =>
|
||||
getSignalByNumber(number, signals),
|
||||
)
|
||||
return Object.assign({}, ...signalsA)
|
||||
}
|
||||
|
||||
const getSignalByNumber = (number, signals) => {
|
||||
const signal = findSignalByNumber(number, signals)
|
||||
|
||||
if (signal === undefined) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const { name, description, supported, action, forced, standard } = signal
|
||||
return {
|
||||
[number]: {
|
||||
name,
|
||||
number,
|
||||
description,
|
||||
supported,
|
||||
action,
|
||||
forced,
|
||||
standard,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Several signals might end up sharing the same number because of OS-specific
|
||||
// numbers, in which case those prevail.
|
||||
const findSignalByNumber = (number, signals) => {
|
||||
const signal = signals.find(({ name }) => constants.signals[name] === number)
|
||||
|
||||
if (signal !== undefined) {
|
||||
return signal
|
||||
}
|
||||
|
||||
return signals.find((signalA) => signalA.number === number)
|
||||
}
|
||||
|
||||
export const signalsByNumber = getSignalsByNumber()
|
73
src/main/lib/human-signals/index.ts
Normal file
73
src/main/lib/human-signals/index.ts
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* What is the default action for this signal when it is not handled.
|
||||
*/
|
||||
export type SignalAction = 'terminate' | 'core' | 'ignore' | 'pause' | 'unpause'
|
||||
|
||||
/**
|
||||
* Which standard defined that signal.
|
||||
*/
|
||||
export type SignalStandard = 'ansi' | 'posix' | 'bsd' | 'systemv' | 'other'
|
||||
|
||||
/**
|
||||
* Standard name of the signal, for example 'SIGINT'.
|
||||
*/
|
||||
export type SignalName = `SIG${string}`
|
||||
|
||||
/**
|
||||
* Code number of the signal, for example 2.
|
||||
* While most number are cross-platform, some are different between different
|
||||
* OS.
|
||||
*/
|
||||
export type SignalNumber = number
|
||||
|
||||
export interface Signal {
|
||||
/**
|
||||
* Standard name of the signal, for example 'SIGINT'.
|
||||
*/
|
||||
name: SignalName
|
||||
|
||||
/**
|
||||
* Code number of the signal, for example 2.
|
||||
* While most number are cross-platform, some are different between different
|
||||
* OS.
|
||||
*/
|
||||
number: SignalNumber
|
||||
|
||||
/**
|
||||
* Human-friendly description for the signal, for example
|
||||
* 'User interruption with CTRL-C'.
|
||||
*/
|
||||
description: string
|
||||
|
||||
/**
|
||||
* Whether the current OS can handle this signal in Node.js using
|
||||
* `process.on(name, handler)`. The list of supported signals is OS-specific.
|
||||
*/
|
||||
supported: boolean
|
||||
|
||||
/**
|
||||
* What is the default action for this signal when it is not handled.
|
||||
*/
|
||||
action: SignalAction
|
||||
|
||||
/**
|
||||
* Whether the signal's default action cannot be prevented.
|
||||
* This is true for SIGTERM, SIGKILL and SIGSTOP.
|
||||
*/
|
||||
forced: boolean
|
||||
|
||||
/**
|
||||
* Which standard defined that signal.
|
||||
*/
|
||||
standard: SignalStandard
|
||||
}
|
||||
|
||||
/**
|
||||
* Object whose keys are signal names and values are signal objects.
|
||||
*/
|
||||
export declare const signalsByName: { [signalName: SignalName]: Signal }
|
||||
|
||||
/**
|
||||
* Object whose keys are signal numbers and values are signal objects.
|
||||
*/
|
||||
export declare const signalsByNumber: { [signalNumber: SignalNumber]: Signal }
|
16
src/main/lib/human-signals/realtime.js
Normal file
16
src/main/lib/human-signals/realtime.js
Normal file
@ -0,0 +1,16 @@
|
||||
// List of realtime signals with information about them
|
||||
export const getRealtimeSignals = () => {
|
||||
const length = SIGRTMAX - SIGRTMIN + 1
|
||||
return Array.from({ length }, getRealtimeSignal)
|
||||
}
|
||||
|
||||
const getRealtimeSignal = (value, index) => ({
|
||||
name: `SIGRT${index + 1}`,
|
||||
number: SIGRTMIN + index,
|
||||
action: 'terminate',
|
||||
description: 'Application-specific signal (realtime)',
|
||||
standard: 'posix',
|
||||
})
|
||||
|
||||
const SIGRTMIN = 34
|
||||
export const SIGRTMAX = 64
|
34
src/main/lib/human-signals/signals.js
Normal file
34
src/main/lib/human-signals/signals.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { constants } from 'node:os'
|
||||
|
||||
import { SIGNALS } from './core.js'
|
||||
import { getRealtimeSignals } from './realtime.js'
|
||||
|
||||
// Retrieve list of know signals (including realtime) with information about
|
||||
// them
|
||||
export const getSignals = () => {
|
||||
const realtimeSignals = getRealtimeSignals()
|
||||
const signals = [...SIGNALS, ...realtimeSignals].map(normalizeSignal)
|
||||
return signals
|
||||
}
|
||||
|
||||
// Normalize signal:
|
||||
// - `number`: signal numbers are OS-specific. This is taken into account by
|
||||
// `os.constants.signals`. However we provide a default `number` since some
|
||||
// signals are not defined for some OS.
|
||||
// - `forced`: set default to `false`
|
||||
// - `supported`: set value
|
||||
const normalizeSignal = ({
|
||||
name,
|
||||
number: defaultNumber,
|
||||
description,
|
||||
action,
|
||||
forced = false,
|
||||
standard,
|
||||
}) => {
|
||||
const {
|
||||
signals: { [name]: constantSignal },
|
||||
} = constants
|
||||
const supported = constantSignal !== undefined
|
||||
const number = supported ? constantSignal : defaultNumber
|
||||
return { name, number, description, supported, action, forced, standard }
|
||||
}
|
81
src/main/lib/is-stream/index.d.ts
vendored
Normal file
81
src/main/lib/is-stream/index.d.ts
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
Stream,
|
||||
Writable as WritableStream,
|
||||
Readable as ReadableStream,
|
||||
Duplex as DuplexStream,
|
||||
Transform as TransformStream,
|
||||
} from 'node:stream';
|
||||
|
||||
/**
|
||||
@returns Whether `stream` is a [`Stream`](https://nodejs.org/api/stream.html#stream_stream).
|
||||
|
||||
@example
|
||||
```
|
||||
import fs from 'node:fs';
|
||||
import {isStream} from 'is-stream';
|
||||
|
||||
isStream(fs.createReadStream('unicorn.png'));
|
||||
//=> true
|
||||
|
||||
isStream({});
|
||||
//=> false
|
||||
```
|
||||
*/
|
||||
export function isStream(stream: unknown): stream is Stream;
|
||||
|
||||
/**
|
||||
@returns Whether `stream` is a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable).
|
||||
|
||||
@example
|
||||
```
|
||||
import fs from 'node:fs';
|
||||
import {isWritableStream} from 'is-stream';
|
||||
|
||||
isWritableStream(fs.createWriteStrem('unicorn.txt'));
|
||||
//=> true
|
||||
```
|
||||
*/
|
||||
export function isWritableStream(stream: unknown): stream is WritableStream;
|
||||
|
||||
/**
|
||||
@returns Whether `stream` is a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable).
|
||||
|
||||
@example
|
||||
```
|
||||
import fs from 'node:fs';
|
||||
import {isReadableStream} from 'is-stream';
|
||||
|
||||
isReadableStream(fs.createReadStream('unicorn.png'));
|
||||
//=> true
|
||||
```
|
||||
*/
|
||||
export function isReadableStream(stream: unknown): stream is ReadableStream;
|
||||
|
||||
/**
|
||||
@returns Whether `stream` is a [`stream.Duplex`](https://nodejs.org/api/stream.html#stream_class_stream_duplex).
|
||||
|
||||
@example
|
||||
```
|
||||
import {Duplex as DuplexStream} from 'node:stream';
|
||||
import {isDuplexStream} from 'is-stream';
|
||||
|
||||
isDuplexStream(new DuplexStream());
|
||||
//=> true
|
||||
```
|
||||
*/
|
||||
export function isDuplexStream(stream: unknown): stream is DuplexStream;
|
||||
|
||||
/**
|
||||
@returns Whether `stream` is a [`stream.Transform`](https://nodejs.org/api/stream.html#stream_class_stream_transform).
|
||||
|
||||
@example
|
||||
```
|
||||
import fs from 'node:fs';
|
||||
import StringifyStream from 'streaming-json-stringify';
|
||||
import {isTransformStream} from 'is-stream';
|
||||
|
||||
isTransformStream(StringifyStream());
|
||||
//=> true
|
||||
```
|
||||
*/
|
||||
export function isTransformStream(stream: unknown): stream is TransformStream;
|
29
src/main/lib/is-stream/index.js
Normal file
29
src/main/lib/is-stream/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
export function isStream(stream) {
|
||||
return stream !== null
|
||||
&& typeof stream === 'object'
|
||||
&& typeof stream.pipe === 'function';
|
||||
}
|
||||
|
||||
export function isWritableStream(stream) {
|
||||
return isStream(stream)
|
||||
&& stream.writable !== false
|
||||
&& typeof stream._write === 'function'
|
||||
&& typeof stream._writableState === 'object';
|
||||
}
|
||||
|
||||
export function isReadableStream(stream) {
|
||||
return isStream(stream)
|
||||
&& stream.readable !== false
|
||||
&& typeof stream._read === 'function'
|
||||
&& typeof stream._readableState === 'object';
|
||||
}
|
||||
|
||||
export function isDuplexStream(stream) {
|
||||
return isWritableStream(stream)
|
||||
&& isReadableStream(stream);
|
||||
}
|
||||
|
||||
export function isTransformStream(stream) {
|
||||
return isDuplexStream(stream)
|
||||
&& typeof stream._transform === 'function';
|
||||
}
|
71
src/main/lib/mimic-function/index.js
Normal file
71
src/main/lib/mimic-function/index.js
Normal file
@ -0,0 +1,71 @@
|
||||
const copyProperty = (to, from, property, ignoreNonConfigurable) => {
|
||||
// `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
|
||||
// `Function#prototype` is non-writable and non-configurable so can never be modified.
|
||||
if (property === 'length' || property === 'prototype') {
|
||||
return;
|
||||
}
|
||||
|
||||
// `Function#arguments` and `Function#caller` should not be copied. They were reported to be present in `Reflect.ownKeys` for some devices in React Native (#41), so we explicitly ignore them here.
|
||||
if (property === 'arguments' || property === 'caller') {
|
||||
return;
|
||||
}
|
||||
|
||||
const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
|
||||
const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
|
||||
|
||||
if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(to, property, fromDescriptor);
|
||||
};
|
||||
|
||||
// `Object.defineProperty()` throws if the property exists, is not configurable and either:
|
||||
// - one its descriptors is changed
|
||||
// - it is non-writable and its value is changed
|
||||
const canCopyProperty = function (toDescriptor, fromDescriptor) {
|
||||
return toDescriptor === undefined || toDescriptor.configurable || (
|
||||
toDescriptor.writable === fromDescriptor.writable
|
||||
&& toDescriptor.enumerable === fromDescriptor.enumerable
|
||||
&& toDescriptor.configurable === fromDescriptor.configurable
|
||||
&& (toDescriptor.writable || toDescriptor.value === fromDescriptor.value)
|
||||
);
|
||||
};
|
||||
|
||||
const changePrototype = (to, from) => {
|
||||
const fromPrototype = Object.getPrototypeOf(from);
|
||||
if (fromPrototype === Object.getPrototypeOf(to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(to, fromPrototype);
|
||||
};
|
||||
|
||||
const wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/\n${fromBody}`;
|
||||
|
||||
const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString');
|
||||
const toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, 'name');
|
||||
|
||||
// We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
|
||||
// We use `bind()` instead of a closure for the same reason.
|
||||
// Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
|
||||
const changeToString = (to, from, name) => {
|
||||
const withName = name === '' ? '' : `with ${name.trim()}() `;
|
||||
const newToString = wrappedToString.bind(null, withName, from.toString());
|
||||
// Ensure `to.toString.toString` is non-enumerable and has the same `same`
|
||||
Object.defineProperty(newToString, 'name', toStringName);
|
||||
Object.defineProperty(to, 'toString', { ...toStringDescriptor, value: newToString });
|
||||
};
|
||||
|
||||
export default function mimicFunction(to, from, { ignoreNonConfigurable = false } = {}) {
|
||||
const { name } = to;
|
||||
|
||||
for (const property of Reflect.ownKeys(from)) {
|
||||
copyProperty(to, from, property, ignoreNonConfigurable);
|
||||
}
|
||||
|
||||
changePrototype(to, from);
|
||||
changeToString(to, from, name);
|
||||
|
||||
return to;
|
||||
}
|
84
src/main/lib/npm-run-path/index.d.ts
vendored
Normal file
84
src/main/lib/npm-run-path/index.d.ts
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
export interface RunPathOptions {
|
||||
/**
|
||||
Working directory.
|
||||
|
||||
@default process.cwd()
|
||||
*/
|
||||
readonly cwd?: string | URL;
|
||||
|
||||
/**
|
||||
PATH to be appended. Default: [`PATH`](https://github.com/sindresorhus/path-key).
|
||||
|
||||
Set it to an empty string to exclude the default PATH.
|
||||
*/
|
||||
readonly path?: string;
|
||||
|
||||
/**
|
||||
Path to the Node.js executable to use in child processes if that is different from the current one. Its directory is pushed to the front of PATH.
|
||||
|
||||
This can be either an absolute path or a path relative to the `cwd` option.
|
||||
|
||||
@default process.execPath
|
||||
*/
|
||||
readonly execPath?: string | URL;
|
||||
}
|
||||
|
||||
export type ProcessEnv = Record<string, string | undefined>;
|
||||
|
||||
export interface EnvOptions {
|
||||
/**
|
||||
The working directory.
|
||||
|
||||
@default process.cwd()
|
||||
*/
|
||||
readonly cwd?: string | URL;
|
||||
|
||||
/**
|
||||
Accepts an object of environment variables, like `process.env`, and modifies the PATH using the correct [PATH key](https://github.com/sindresorhus/path-key). Use this if you're modifying the PATH for use in the `child_process` options.
|
||||
*/
|
||||
readonly env?: ProcessEnv;
|
||||
|
||||
/**
|
||||
The path to the current Node.js executable. Its directory is pushed to the front of PATH.
|
||||
|
||||
This can be either an absolute path or a path relative to the `cwd` option.
|
||||
|
||||
@default process.execPath
|
||||
*/
|
||||
readonly execPath?: string | URL;
|
||||
}
|
||||
|
||||
/**
|
||||
Get your [PATH](https://en.wikipedia.org/wiki/PATH_(variable)) prepended with locally installed binaries.
|
||||
|
||||
@returns The augmented path string.
|
||||
|
||||
@example
|
||||
```
|
||||
import childProcess from 'node:child_process';
|
||||
import {npmRunPath} from 'npm-run-path';
|
||||
|
||||
console.log(process.env.PATH);
|
||||
//=> '/usr/local/bin'
|
||||
|
||||
console.log(npmRunPath());
|
||||
//=> '/Users/sindresorhus/dev/foo/node_modules/.bin:/Users/sindresorhus/dev/node_modules/.bin:/Users/sindresorhus/node_modules/.bin:/Users/node_modules/.bin:/node_modules/.bin:/usr/local/bin'
|
||||
```
|
||||
*/
|
||||
export function npmRunPath(options?: RunPathOptions): string;
|
||||
|
||||
/**
|
||||
@returns The augmented [`process.env`](https://nodejs.org/api/process.html#process_process_env) object.
|
||||
|
||||
@example
|
||||
```
|
||||
import childProcess from 'node:child_process';
|
||||
import {npmRunPathEnv} from 'npm-run-path';
|
||||
|
||||
// `foo` is a locally installed binary
|
||||
childProcess.execFileSync('foo', {
|
||||
env: npmRunPathEnv()
|
||||
});
|
||||
```
|
||||
*/
|
||||
export function npmRunPathEnv(options?: EnvOptions): ProcessEnv;
|
51
src/main/lib/npm-run-path/index.js
Normal file
51
src/main/lib/npm-run-path/index.js
Normal file
@ -0,0 +1,51 @@
|
||||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
import url from 'node:url';
|
||||
|
||||
function pathKey(options = {}) {
|
||||
const {
|
||||
env = process.env,
|
||||
platform = process.platform
|
||||
} = options;
|
||||
|
||||
if (platform !== 'win32') {
|
||||
return 'PATH';
|
||||
}
|
||||
|
||||
return Object.keys(env).reverse().find(key => key.toUpperCase() === 'PATH') || 'Path';
|
||||
}
|
||||
|
||||
export function npmRunPath(options = {}) {
|
||||
const {
|
||||
cwd = process.cwd(),
|
||||
path: path_ = process.env[pathKey()],
|
||||
execPath = process.execPath,
|
||||
} = options;
|
||||
|
||||
let previous;
|
||||
const execPathString = execPath instanceof URL ? url.fileURLToPath(execPath) : execPath;
|
||||
const cwdString = cwd instanceof URL ? url.fileURLToPath(cwd) : cwd;
|
||||
let cwdPath = path.resolve(cwdString);
|
||||
const result = [];
|
||||
|
||||
while (previous !== cwdPath) {
|
||||
result.push(path.join(cwdPath, 'node_modules/.bin'));
|
||||
previous = cwdPath;
|
||||
cwdPath = path.resolve(cwdPath, '..');
|
||||
}
|
||||
|
||||
// Ensure the running `node` binary is used.
|
||||
result.push(path.resolve(cwdString, execPathString, '..'));
|
||||
|
||||
return [...result, path_].join(path.delimiter);
|
||||
}
|
||||
|
||||
export function npmRunPathEnv({ env = process.env, ...options } = {}) {
|
||||
env = { ...env };
|
||||
|
||||
const path = pathKey({ env });
|
||||
options.path = env[path];
|
||||
env[path] = npmRunPath(options);
|
||||
|
||||
return env;
|
||||
}
|
59
src/main/lib/onetime/index.d.ts
vendored
Normal file
59
src/main/lib/onetime/index.d.ts
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
export type Options = {
|
||||
/**
|
||||
Throw an error when called more than once.
|
||||
|
||||
@default false
|
||||
*/
|
||||
readonly throw?: boolean;
|
||||
};
|
||||
|
||||
declare const onetime: {
|
||||
/**
|
||||
Ensure a function is only called once. When called multiple times it will return the return value from the first call.
|
||||
|
||||
@param fn - The function that should only be called once.
|
||||
@returns A function that only calls `fn` once.
|
||||
|
||||
@example
|
||||
```
|
||||
import onetime from 'onetime';
|
||||
|
||||
let index = 0;
|
||||
|
||||
const foo = onetime(() => ++index);
|
||||
|
||||
foo(); //=> 1
|
||||
foo(); //=> 1
|
||||
foo(); //=> 1
|
||||
|
||||
onetime.callCount(foo); //=> 3
|
||||
```
|
||||
*/
|
||||
<ArgumentsType extends unknown[], ReturnType>(
|
||||
fn: (...arguments_: ArgumentsType) => ReturnType,
|
||||
options?: Options
|
||||
): (...arguments_: ArgumentsType) => ReturnType;
|
||||
|
||||
/**
|
||||
Get the number of times `fn` has been called.
|
||||
|
||||
@param fn - The function to get call count from.
|
||||
@returns A number representing how many times `fn` has been called.
|
||||
|
||||
@example
|
||||
```
|
||||
import onetime from 'onetime';
|
||||
|
||||
const foo = onetime(() => {});
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
|
||||
console.log(onetime.callCount(foo));
|
||||
//=> 3
|
||||
```
|
||||
*/
|
||||
callCount(fn: (...arguments_: any[]) => unknown): number;
|
||||
};
|
||||
|
||||
export default onetime;
|
41
src/main/lib/onetime/index.js
Normal file
41
src/main/lib/onetime/index.js
Normal file
@ -0,0 +1,41 @@
|
||||
import mimicFunction from '../mimic-function';
|
||||
|
||||
const calledFunctions = new WeakMap();
|
||||
|
||||
const onetime = (function_, options = {}) => {
|
||||
if (typeof function_ !== 'function') {
|
||||
throw new TypeError('Expected a function');
|
||||
}
|
||||
|
||||
let returnValue;
|
||||
let callCount = 0;
|
||||
const functionName = function_.displayName || function_.name || '<anonymous>';
|
||||
|
||||
const onetime = function (...arguments_) {
|
||||
calledFunctions.set(onetime, ++callCount);
|
||||
|
||||
if (callCount === 1) {
|
||||
returnValue = function_.apply(this, arguments_);
|
||||
function_ = undefined;
|
||||
} else if (options.throw === true) {
|
||||
throw new Error(`Function \`${functionName}\` can only be called once`);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
mimicFunction(onetime, function_);
|
||||
calledFunctions.set(onetime, callCount);
|
||||
|
||||
return onetime;
|
||||
};
|
||||
|
||||
onetime.callCount = function_ => {
|
||||
if (!calledFunctions.has(function_)) {
|
||||
throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`);
|
||||
}
|
||||
|
||||
return calledFunctions.get(function_);
|
||||
};
|
||||
|
||||
export default onetime;
|
18
src/main/lib/strip-final-newline/index.d.ts
vendored
Normal file
18
src/main/lib/strip-final-newline/index.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
Strip the final [newline character](https://en.wikipedia.org/wiki/Newline) from a string or Uint8Array.
|
||||
|
||||
@returns The input without any final newline.
|
||||
|
||||
@example
|
||||
```
|
||||
import stripFinalNewline from 'strip-final-newline';
|
||||
|
||||
stripFinalNewline('foo\nbar\n\n');
|
||||
//=> 'foo\nbar\n'
|
||||
|
||||
const uint8Array = new TextEncoder().encode('foo\nbar\n\n')
|
||||
new TextDecoder().decode(stripFinalNewline(uint8Array));
|
||||
//=> 'foo\nbar\n'
|
||||
```
|
||||
*/
|
||||
export default function stripFinalNewline<T extends string | Uint8Array>(input: T): T;
|
26
src/main/lib/strip-final-newline/index.js
Normal file
26
src/main/lib/strip-final-newline/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
export default function stripFinalNewline(input) {
|
||||
if (typeof input === 'string') {
|
||||
return stripFinalNewlineString(input);
|
||||
}
|
||||
|
||||
if (!(ArrayBuffer.isView(input) && input.BYTES_PER_ELEMENT === 1)) {
|
||||
throw new Error('Input must be a string or a Uint8Array');
|
||||
}
|
||||
|
||||
return stripFinalNewlineBinary(input);
|
||||
}
|
||||
|
||||
const stripFinalNewlineString = input =>
|
||||
input.at(-1) === LF
|
||||
? input.slice(0, input.at(-2) === CR ? -2 : -1)
|
||||
: input;
|
||||
|
||||
const stripFinalNewlineBinary = input =>
|
||||
input.at(-1) === LF_BINARY
|
||||
? input.subarray(0, input.at(-2) === CR_BINARY ? -2 : -1)
|
||||
: input;
|
||||
|
||||
const LF = '\n';
|
||||
const LF_BINARY = LF.codePointAt(0);
|
||||
const CR = '\r';
|
||||
const CR_BINARY = CR.codePointAt(0);
|
@ -1,7 +1,7 @@
|
||||
import path from "node:path"
|
||||
import fs from "node:fs"
|
||||
import ChildProcess from "node:child_process"
|
||||
import upath from "upath"
|
||||
import { execa } from "../../lib/execa"
|
||||
|
||||
import sendToRender from "../../utils/sendToRender"
|
||||
import Vars from "../../vars"
|
||||
@ -24,8 +24,7 @@ export default async (manifest, step) => {
|
||||
|
||||
console.log(`[${manifest.id}] steps.git_clone() | Cloning ${step.url}...`)
|
||||
|
||||
const command = [
|
||||
gitCMD,
|
||||
const args = [
|
||||
"clone",
|
||||
//`--depth ${step.depth ?? 1}`,
|
||||
//"--filter=blob:none",
|
||||
@ -36,23 +35,10 @@ export default async (manifest, step) => {
|
||||
final_path,
|
||||
]
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ChildProcess.exec(
|
||||
command.join(" "),
|
||||
{
|
||||
shell: true,
|
||||
cwd: final_path,
|
||||
},
|
||||
(error, out) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
console.log(out)
|
||||
return resolve()
|
||||
}
|
||||
)
|
||||
await execa(gitCMD, args, {
|
||||
cwd: final_path,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
return manifest
|
||||
|
@ -1,6 +1,6 @@
|
||||
import path from "node:path"
|
||||
import fs from "node:fs"
|
||||
import ChildProcess from "node:child_process"
|
||||
import { execa } from "../../lib/execa"
|
||||
|
||||
import sendToRender from "../../utils/sendToRender"
|
||||
|
||||
@ -20,23 +20,11 @@ export default async (manifest, step) => {
|
||||
|
||||
fs.mkdirSync(_path, { recursive: true })
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ChildProcess.exec(
|
||||
`${gitCMD} pull`,
|
||||
{
|
||||
cwd: _path,
|
||||
shell: true,
|
||||
},
|
||||
(error, out) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
console.log(out)
|
||||
|
||||
return resolve()
|
||||
}
|
||||
)
|
||||
await execa(gitCMD, ["pull"], {
|
||||
cwd: _path,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
return manifest
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import path from "node:path"
|
||||
import fs from "node:fs"
|
||||
import ChildProcess from "node:child_process"
|
||||
import { execa } from "../../lib/execa"
|
||||
|
||||
import sendToRender from "../../utils/sendToRender"
|
||||
|
||||
@ -21,26 +21,13 @@ export default async (manifest, step) => {
|
||||
statusText: `Fetching from origin...`,
|
||||
})
|
||||
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Fetching from origin...`)
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Fetching from origin`)
|
||||
|
||||
// fetch from origin
|
||||
await new Promise((resolve, reject) => {
|
||||
ChildProcess.exec(
|
||||
`${gitCMD} fetch origin`,
|
||||
{
|
||||
cwd: _path,
|
||||
shell: true,
|
||||
},
|
||||
(error, out) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
console.log(out)
|
||||
return resolve()
|
||||
}
|
||||
)
|
||||
await execa(gitCMD, ["fetch", "origin"], {
|
||||
cwd: _path,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
sendToRender(`pkg:update:status`, {
|
||||
@ -48,26 +35,12 @@ export default async (manifest, step) => {
|
||||
statusText: `Cleaning untracked files...`,
|
||||
})
|
||||
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Cleaning...`)
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Cleaning`)
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ChildProcess.exec(
|
||||
`${gitCMD} clean -df`,
|
||||
{
|
||||
cwd: _path,
|
||||
shell: true,
|
||||
},
|
||||
(error, out) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
console.log(out)
|
||||
|
||||
return resolve()
|
||||
}
|
||||
)
|
||||
await execa(gitCMD, ["clean", "-df"], {
|
||||
cwd: _path,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
sendToRender(`pkg:update:status`, {
|
||||
@ -75,25 +48,26 @@ export default async (manifest, step) => {
|
||||
statusText: `Reset from ${from}`,
|
||||
})
|
||||
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Reseting to ${from}...`)
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Reseting to ${from}`)
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ChildProcess.exec(
|
||||
`${gitCMD} reset --hard ${from}`,
|
||||
{
|
||||
cwd: _path,
|
||||
shell: true,
|
||||
},
|
||||
(error, out) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
console.log(out)
|
||||
|
||||
return resolve()
|
||||
}
|
||||
)
|
||||
await execa(gitCMD, ["reset", "--hard", from], {
|
||||
cwd: _path,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
sendToRender(`pkg:update:status`, {
|
||||
id: manifest.id,
|
||||
statusText: `Checkout to HEAD`,
|
||||
})
|
||||
|
||||
console.log(`[${manifest.id}] steps.git_reset() | Checkout to head`)
|
||||
|
||||
await execa(gitCMD, ["checkout", "HEAD"], {
|
||||
cwd: _path,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
return manifest
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user