{"id":20542260,"url":"https://github.com/darkobits/chex","last_synced_at":"2026-04-21T14:01:45.462Z","repository":{"id":35882543,"uuid":"219929431","full_name":"darkobits/chex","owner":"darkobits","description":"Verify that an executable is installed and satisfies a semver version range.","archived":false,"fork":false,"pushed_at":"2023-06-22T18:11:46.000Z","size":4232,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-24T09:30:33.058Z","etag":null,"topics":["exec","semver-range"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/darkobits.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-06T06:41:44.000Z","updated_at":"2023-01-25T12:46:21.000Z","dependencies_parsed_at":"2024-11-16T01:40:26.633Z","dependency_job_id":null,"html_url":"https://github.com/darkobits/chex","commit_stats":{"total_commits":86,"total_committers":3,"mean_commits":"28.666666666666668","dds":"0.39534883720930236","last_synced_commit":"67476ce479ee1d9a081c97186e75c37d0e3af778"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":"darkobits/ts-template","purl":"pkg:github/darkobits/chex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkobits%2Fchex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkobits%2Fchex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkobits%2Fchex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkobits%2Fchex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darkobits","download_url":"https://codeload.github.com/darkobits/chex/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkobits%2Fchex/sbom","scorecard":{"id":322788,"data":{"date":"2025-08-11","repo":{"name":"github.com/darkobits/chex","commit":"67476ce479ee1d9a081c97186e75c37d0e3af778"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/darkobits/chex/ci.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/darkobits/chex/ci.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Code-Review","score":0,"reason":"Found 0/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Do What The F*ck You Want To Public License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"32 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-h5c3-5r3r-rr8q","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-67mh-4wv8-2f99","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-353f-5xf4-qw67","Warn: Project is vulnerable to: GHSA-c24v-8rfc-w8vw","Warn: Project is vulnerable to: GHSA-8jhw-289h-jh2g","Warn: Project is vulnerable to: GHSA-64vr-g452-qvp3","Warn: Project is vulnerable to: GHSA-9cwx-2883-4wfx","Warn: Project is vulnerable to: GHSA-vg6x-rcgg-rjx6","Warn: Project is vulnerable to: GHSA-x574-m823-4x7w","Warn: Project is vulnerable to: GHSA-4r4m-qw57-chr8","Warn: Project is vulnerable to: GHSA-xcj6-pq6g-qj4x","Warn: Project is vulnerable to: GHSA-356w-63v5-8wf4","Warn: Project is vulnerable to: GHSA-859w-5945-r5v3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-f9xv-q969-pqx4"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T01:47:11.869Z","repository_id":35882543,"created_at":"2025-08-18T01:47:11.870Z","updated_at":"2025-08-18T01:47:11.870Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32095160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-21T11:25:29.218Z","status":"ssl_error","status_checked_at":"2026-04-21T11:25:28.499Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exec","semver-range"],"created_at":"2024-11-16T01:30:17.028Z","updated_at":"2026-04-21T14:01:45.440Z","avatar_url":"https://github.com/darkobits.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"#top\" id=\"top\"\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/441546/101617498-88b78080-39c5-11eb-8b14-18038e29e5a2.png\" style=\"max-width: 100%;\"\u003e\n\u003c/a\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@darkobits/chex\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/@darkobits/chex.svg?style=flat-square\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/darkobits/chex/actions?query=workflow%3Aci\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/darkobits/chex/ci.yml?style=flat-square\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://depfu.com/github/darkobits/chex\"\u003e\u003cimg src=\"https://img.shields.io/depfu/darkobits/chex?style=flat-square\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://conventionalcommits.org\"\u003e\u003cimg src=\"https://img.shields.io/static/v1?label=commits\u0026message=conventional\u0026style=flat-square\u0026color=398AFB\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nIf you use [Execa](https://github.com/sindresorhus/execa) in your application to integrate with other\nexecutables, this tool provides a way to:\n\n1. Verify that an executable is installed and fail fast if is isn't and/or:\n2. Ensure that a particular version is installed and fail fast if it isn't.\n\n## Install\n\n```\n$ npm install @darkobits/chex\n```\n\n## Use\n\nChex exports an async function that accepts a string. That string may be an executable name, or an\nexecutable name and [valid semver range](https://devhints.io/semver). If a name alone is provided, Chex\nmakes sure the executable is installed. If a semver range is provided along with a name, Chex ensures\nthat the version of the executable satisfies that semver range. Chex then returns an Execa wrapper\nbound to the provided executable.\n\nLet's imagine we are writing a tool that is going to make several calls to the `git` CLI, and we know\nthat we need Git version 2.0.0 or greater. We want to make this assertion as early as possible in our\nprogram so we can present the user with a meaningful error before we try to use an unsupported Git\nfeature. Let's see how we can accomplish this with Chex:\n\n```ts\nimport chex from '@darkobits/chex';\n\n// Assume this is our program's entrypoint.\nexport default async function main() {\n  const git = await chex('git \u003e=2.0.0');\n\n  // Now, we can use this value just like Execa:\n  const status = await git(['rev-parse', 'HEAD']);\n\n  // If you prefer the string form, you can use that as well. Execa's\n  // .command() variant is just an overload with Chex:\n  const sha = await git('status');\n\n  // Execa options are passed-though to Execa:\n  const pushResult = await git('push origin master', { stdio: 'inherit' });\n\n  // You can also do all of the above synchronously:\n  const pullResult = git.sync('pull');\n}\n```\n\nNeed to integrate with several other tools? You can get fancy:\n\n```ts\nimport chex from '@darkobits/chex';\n\n// Assume this is our program's entrypoint.\nexport default async function main() {\n  const dependencies = ['git \u003e=2.0.0', 'docker', 'python'];\n\n  // This will throw if any of the above aren't installed or the version isn't satisfied.\n  const [git, docker, python] = await Promise.all(dependencies.map(chex));\n\n  // ... do awesome things!\n}\n```\n\n**But wait, there's more!**\n\nChex will also attach `version` and `rawVersion` properties to the value it returns, which you can use\nfor debugging/reporting:\n\n```ts\nimport chex from '@darkobits/chex';\n\nexport default async function main() {\n  const docker = await chex('docker \u003e=19');\n\n  console.log(docker.version);\n  //=\u003e '19.3.4'\n\n  console.log(docker.rawVersion);\n  //=\u003e 'Docker version 19.03.4, build 9013bf5'\n}\n```\n\n## API\n\nChex is available in asynchronous and synchronous modes. This package's default export is the\nasynchronous function. The synchronous function is available at the `.sync` property.\n\n```ts\ninterface Chex {\n  (executableExpression: string, execaOpts?: execa.Options): Promise\u003cExecaWrapper\u003e;\n  sync(executableExpression: string, execaOpts?: execa.SyncOptions): ExecaWrapper;\n}\n```\n\n**Note:** Execa options provided to `chex` or `chex.sync` will be used to configure the call to locate\nthe executable. Calls to the executable itself may be configured by providing an Execa options object to\nthe wrapper returned by Chex.\n\n`ExecaWrapper` is a function with the following signature and properties:\n\n```ts\ninterface ExecaWrapper {\n  /**\n   * Call the bound executable via Execa asynchronously.\n   */\n  (commandOrArgs: string | Array\u003cstring\u003e, execaOpts?: ExecaOptions): ExecaChildProcess;\n\n  /**\n   * Call the bound executable via Execa synchronously.\n   */\n  sync(commandOrArgs: string | Array\u003cstring\u003e, execaOpts?: ExecaOptions): ExecaSyncReturnValue;\n\n  /**\n   * Parsed/cleaned semver version of the executable.\n   */\n  version: string;\n\n  /**\n   * Raw version descriptor reported by the executable.\n   */\n  rawVersion: string;\n}\n```\n\n**Note:** Both the synchronous and asynchronous versions of Chex return the same Execa wrapper, which\nitself has synchronous and asynchronous modes. It is therefore possible to mix and match these call\ntypes to fit your application's needs.\n\n## Error Handling\n\nChex has a set of custom sub-classed Error types that make it easier to programmatically determine what\nkind of error was thrown. You can either perform `instanceof` checks against errors thrown by Chex, or\ncheck the `code` property of errors, which will be a string in the standard Node error code format.\n\nAll of these errors will be thrown by the initial call to Chex. If a bound Execa instance is\nsuccessfully created, they will throw [Execa errors](https://github.com/sindresorhus/execa#handling-errors),\nwhich also contain a number of special properties to aid in error-handling.\n\n**Case: Executable Not Found**\n\nThrown if the specified executable was not found on the user's system, either because it is not\ninstalled or not readable/executable due to permissions errors, Chex will throw an\n`ExecutableNotFoundError`.\n\nThe error's `cause` property will contain the original error thrown by Execa.\n\n```ts\nimport chex, { ExecutableNotFoundError } from '@darkobits/chex';\n\ntry {\n  const git = await chex('git');\n} catch (err) {\n  err instanceof ExecutableNotFoundError // true\n  err.code === ExecutableNotFoundError.code // true\n\n  /**\n   * {\n   *   name: 'ExecutableNotFoundError',\n   *   code: 'ERR_EXECUTABLE_NOT_FOUND';\n   *   message: string;\n   *   cause: Error;\n   * }\n   */\n}\n```\n\n**Case: Version Unavailable**\n\nThrown when a semver range was provided to Chex and the executable was found, but Chex was unable to\ndetermine its version.\n\nChex attempts to call executables with the `-v`, `--version`, and `version` arguments. These arguments\nare a standard way for a CLI to indicate its version, but it is entirely possible that an application\ndoesn't support any of them.\n\n```ts\nimport chex, { VersionUnavailableError } from '@darkobits/chex';\n\ntry {\n  const foo = await chex('foo \u003e=2.0.0');\n} catch (err) {\n  err instanceof VersionUnavailableError // true\n  err.code === VersionUnavailableError.code // true\n\n  /**\n   * {\n   *   name: 'VersionUnavailableError',\n   *   code: 'ERR_VERSION_UNAVAILABLE';\n   *   message: string;\n   * }\n   */\n}\n```\n\n**Case: Version Invalid**\n\nThrown when:\n- A semver range was provided to Chex, but it was not a valid semver expression.\n- A semver range was provided to Chex, but the version provided by the executable is not a valid semver\n   version.\n\n```ts\nimport chex, { VersionInvalidError } from '@darkobits/chex';\n\ntry {\n  const foo = await chex('foo \u003e=1.0.kittens');\n} catch (err) {\n  err instanceof VersionInvalidError // true\n  err.code === VersionInvalidError.code // true\n\n  /**\n   * {\n   *   name: 'VersionInvalidError',\n   *   code: 'ERR_VERSION_INVALID';\n   *   message: string;\n   * }\n   */\n}\n\ntry {\n  // Let's assume foo returns something like 'build-2018.04.12', which is not a\n  // valid semver range.\n  const foo = await chex('foo \u003e=1.0.0');\n} catch (err) {\n  err instanceof VersionInvalidError // true\n  err.code === VersionInvalidError.code // true\n\n  /**\n   * {\n   *   name: 'VersionInvalidError',\n   *   code: 'ERR_VERSION_INVALID';\n   *   message: string;\n   * }\n   */\n}\n```\n\n**Case: Version Not Satisfied**\n\nThrown when a semver range was provided to Chex and the executable provided a valid semver version, but\nit did not satisfy the semver range required.\n\n```ts\nimport chex, { VersionNotSatisfiedError } from '@darkobits/chex';\n\ntry {\n  // Let's assume \"foo -v\" returned \"18.5.1\".\n  const foo = await chex('foo \u003e=19.0.3');\n} catch (err) {\n  err instanceof VersionNotSatisfiedError // true\n  err.code === VersionNotSatisfiedError.code // true\n\n  /**\n   * {\n   *   name: 'VersionNotSatisfiedError',\n   *   code: 'ERR_VERSION_NOT_SATISFIED';\n   *   message: string;\n   * }\n   */\n}\n```\n\n\u003cbr /\u003e\n\u003ca href=\"#top\"\u003e\n  \u003cimg src=\"https://user-images.githubusercontent.com/441546/189774318-67cf3578-f4b4-4dcc-ab5a-c8210fbb6838.png\" style=\"max-width: 100%;\"\u003e\n\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkobits%2Fchex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarkobits%2Fchex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkobits%2Fchex/lists"}