diff --git a/docs/README.md b/docs/README.md index 91fdba6b..a28495bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ - [Overview](./overview.md) - introduces how the repository and scripts work - [Setup](./setup.md) - what you need to get involved with contributing to `dugite` - API - - [`GitProcess.exec`](api/exec.md) - - [`GitProcess.spawn`](api/spawn.md) - - [`GitProcess.parseError`](api/parseError.md) + - [`exec`](api/exec.md) + - [`spawn`](api/spawn.md) + - [`parseError`](api/parseError.md) - [Releases](./releases.md) - details about how releases are made diff --git a/docs/api/exec.md b/docs/api/exec.md index 7827b5f6..3a6f0beb 100644 --- a/docs/api/exec.md +++ b/docs/api/exec.md @@ -1,39 +1,48 @@ -# `GitProcess.exec` +# `exec` This is the easiest and safest way to work with Git. It provides a Promise-based API that wraps the standard output, standard error and exit code from the process, and also provides some error handling for common scenarios. ```ts -GitProcess.exec(args: string[], path: string, options?: IGitExecutionOptions):Promise +exec(args: string[], path: string, options?: IGitExecutionOptions): Promise ``` - `args` - an array of arguments that should be passed to `git` when executing the process - `path` - the directory to execute the command in - `options` - additional parameters to pass - - `env`: a collection of key-value pairs which are assigned to the created - process + - `env`: a `Record` of key-value pairs which are assigned to the created process - `stdin`: a string or `Buffer` which is written to the created process - `stdinEncoding`: if you need to set the encoding of the input - `processCallback`: a callback to inject additional code which will be invoked after the child process is spawned + - `encoding`: control output encoding (`BufferEncoding` or `'buffer'`) + - `signal`: an `AbortSignal` for cancellation support + - `killSignal`: custom signal to use when killing the process + - `maxBuffer`: maximum buffer size (defaults to `Infinity`) `IGitResult` has these fields: - - `stdout` - a string representing the standard process output from invoking Git - - `stderr` - a string representing the standard error process output from invoking Git + - `stdout` - the standard process output from invoking Git (string or Buffer depending on `encoding`) + - `stderr` - the standard error process output from invoking Git (string or Buffer depending on `encoding`) - `exitCode` - zero if the process completed without error, non-zero indicates an error +For typed results, use `IGitStringResult` (when `encoding` is a string encoding) or `IGitBufferResult` (when `encoding: 'buffer'`). + +On execution failures, an `ExecError` is thrown which includes `stdout`, `stderr`, and additional properties like `code`, `signal`, and `killed`. + ## Example ```ts +import { exec, IGitExecutionOptions } from 'dugite' + const path = 'C:/path/to/repo/' const options: IGitExecutionOptions = { // enable diagnostics env: { - 'GIT_HTTP_USER_AGENT': 'dugite/2.12.0', + 'GIT_HTTP_USER_AGENT': 'dugite/3.0.0', 'GIT_TRACE': '1', 'GIT_CURL_VERBOSE': '1' }, @@ -44,6 +53,37 @@ const options: IGitExecutionOptions = { } } -const result = await GitProcess.exec([ 'pull', 'origin' ], path, options) +const result = await exec([ 'pull', 'origin' ], path, options) +``` + +## Cancellation + +Use `AbortController` to cancel long-running Git operations: + +```ts +import { exec } from 'dugite' + +const controller = new AbortController() + +const resultPromise = exec(['clone', 'https://github.com/example/repo'], '/path/to/dir', { + signal: controller.signal, +}) + +// Cancel if needed +controller.abort() + +try { + const result = await resultPromise +} catch (error) { + // Handle cancellation +} +``` + +You can also use `AbortSignal.timeout()` for automatic cancellation: + +```ts +const result = await exec(['clone', 'https://github.com/example/repo'], '/path/to/dir', { + signal: AbortSignal.timeout(5000), // Auto-cancel after 5 seconds +}) ``` diff --git a/docs/api/parseError.md b/docs/api/parseError.md index 56c53c72..86b7e104 100644 --- a/docs/api/parseError.md +++ b/docs/api/parseError.md @@ -1,4 +1,4 @@ -# `GitProcess.parseError` +# `parseError` Parsing error messages from Git is an essential part of detecting errors raised by Git. `dugite` comes with a collection of known errors, and you can @@ -8,9 +8,11 @@ error messages. For example: ```ts -const result = await GitProcess.exec([ 'pull', 'origin', branch ], path, options) +import { exec, parseError, GitError } from 'dugite' + +const result = await exec([ 'pull', 'origin', branch ], path, options) if (result.exitCode !== 0) { - const error = GitProcess.parseError(result.stderr) + const error = parseError(result.stderr) if (error) { if (error === GitError.HTTPSAuthenticationFailed) { // invalid credentials diff --git a/docs/api/spawn.md b/docs/api/spawn.md index cf9e178e..287a7487 100644 --- a/docs/api/spawn.md +++ b/docs/api/spawn.md @@ -1,27 +1,28 @@ -# `GitProcess.spawn` +# `spawn` -This method exposes the raw child process for callers to manipulate directly. +This function exposes the raw child process for callers to manipulate directly. Because of this, it is the responsibility of the caller to ensure that the child process handles success and error scenarios in their application. ```ts -GitProcess.spawn(args: string[], path: string, options?: IGitSpawnExecutionOptions): ChildProcess +spawn(args: string[], path: string, options?: IGitSpawnOptions): ChildProcess ``` - `args` - an array of arguments that should be passed to `git` when executing the process - `path` - the directory to execute the command in - `options` - additional parameters to pass - - `env`: a collection of key-value pairs which are assigned to the created - process + - `env`: a `Record` of key-value pairs which are assigned to the created process ## Example -This example mimics the Promise-based API of `GitProcess.exec` with some simple handling for the exit code: +This example mimics the Promise-based API of `exec` with some simple handling for the exit code: ```ts +import { spawn } from 'dugite' + return new Promise((resolve, reject) => { - const process = GitProcess.spawn([ 'status', '--porcelain=v2', '--untracked-files=all' ], directory) + const process = spawn([ 'status', '--porcelain=v2', '--untracked-files=all' ], directory) const output = new Array() process.stdout.on('data', (chunk) => { if (chunk instanceof Buffer) { diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 04da18a8..c151e28f 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -2,7 +2,7 @@ There are some ways you can change the behaviour of `dugite` by providing environment variables. These are grouped into two categories - when you -install `dugite` into a package, and when you spawn Git using `GitProcess`. +install `dugite` into a package, and when you spawn Git using `exec` or `spawn`. ## Installation diff --git a/docs/overview.md b/docs/overview.md index 073cdbb0..564dc13c 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -2,11 +2,14 @@ `dugite` is a wrapper on top of the Git command line interface, with some helpers to make it easy to consume in your Node applications. The important parts: -- `GitProcess` - the core of the library - `GitProcess.exec` is how you - interact with Git +- `exec` - the core function of the library - how you interact with Git +- `spawn` - exposes the raw child process for callers to manipulate directly +- `parseError` - parse error messages from Git to detect known errors - `IGitResult` - the abstraction for a result returned by Git - contains - exit code and standard output/error text + exit code and standard output/error text (which can be `string` or `Buffer`) - `IGitExecutionOptions` - additional overrides to change the behaviour - of `GitProcess` (see [API extensibility](./api-extensibility.md) for - more information) + of `exec` +- `IGitSpawnOptions` - additional overrides to change the behaviour + of `spawn` - `GitError` - a collection of known error codes that `dugite` can understand +- `ExecError` - error class thrown by `exec` on execution failures, includes stdout/stderr diff --git a/docs/releases.md b/docs/releases.md index e088c8bf..0ca4fbb5 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,58 +2,19 @@ ## Update Git -The most important part of the release process is updating the embedded Git package. This can be done using this one-liner: +Run the [update-git.yml workflow](https://github.com/desktop/dugite/actions/workflows/update-git.yml) to update the embedded Git version. -```sh -yarn update-embedded-git -``` +This workflow will: -This script: +- retrieve the latest `dugite-native` release from the GitHub API +- get the checksums embedded in the release +- generate the `script/embedded-git.json` payload to be used at install time +- open a pull request with the dugite-native upgrade changes. -- retrieves the latest `dugite-native` release from the GitHub API -- gets the checksums embedded in the release -- generates the `script/embedded-git.json` payload to be used at install time - -### Note - -If you don't want the latest dugite-native release for some reason, you can edit the release URL in `script/update-embedded-git.js` to point to a different GitHub release URL. - -```js -const url = `https://api.github.com/repos/desktop/dugite-native/releases/23544533` -``` +You must then approve and merge the pull request before continuing to the release process. ## Release/Publishing -Before running the commands in 'Publishing to NPM', -create a new release branch of the form `releases/x.x.x` - -After running commands in 'Publishing to NPM', the release branch should be pushed. Now, you need to get it reviewed and merged. - -After that, don't forget publish the release on the repo. - -- Go to https://github.com/desktop/dugite/releases -- Click click `Draft a New Release` -- Fill in form -- Hit `Publish release` - -## Publishing to NPM - -Releases are done to NPM, and are currently limited to the core team. - -```sh -# to ensure everything is up-to-date -yarn - -yarn build - -# if you have not run `yarn build` before, a couple of you cloning auth test will fail. -yarn test - -# you might need to do a different sort of version bump here -npm version minor +Run the [publish.yml workflow](https://github.com/desktop/dugite/actions/workflows/publish.yml) to publish a new release. The workflow will take care of bumping the version number, publishing the package to NPM, and creating a GitHub release. -# this will also run the test suite and fail if any errors found -# this will also run `git push --follow-tags` at the end -# remember to `npm login` -npm publish -``` +Releasing with version 'minor' is typically the way to go (it'll bump from x.y.z to x.(y+1).0), but you can also choose 'patch' (x.y.(z+1)) or 'major' ((x+1).0.0) if you need to. diff --git a/docs/setup.md b/docs/setup.md index be3a85ed..d8aac0fe 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -2,7 +2,7 @@ This is what you need to install: -- [NodeJS](https://nodejs.org) v12 or higher +- [NodeJS](https://nodejs.org) v20 or higher Then open a shell and clone the repository to your local machine.