{"id":19602882,"url":"https://github.com/darkyzhou/exit-hook-plus","last_synced_at":"2026-04-25T22:34:16.415Z","repository":{"id":57231698,"uuid":"323325135","full_name":"darkyzhou/exit-hook-plus","owner":"darkyzhou","description":"Do something before the program exits or when the program crashes!","archived":false,"fork":false,"pushed_at":"2020-12-27T12:57:19.000Z","size":48,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-22T04:49:02.266Z","etag":null,"topics":["async","exit","hook","javascript","typescript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/darkyzhou.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-21T12:07:48.000Z","updated_at":"2020-12-27T12:55:38.000Z","dependencies_parsed_at":"2022-09-04T15:04:52.990Z","dependency_job_id":null,"html_url":"https://github.com/darkyzhou/exit-hook-plus","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/darkyzhou/exit-hook-plus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkyzhou%2Fexit-hook-plus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkyzhou%2Fexit-hook-plus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkyzhou%2Fexit-hook-plus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkyzhou%2Fexit-hook-plus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darkyzhou","download_url":"https://codeload.github.com/darkyzhou/exit-hook-plus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkyzhou%2Fexit-hook-plus/sbom","scorecard":{"id":322963,"data":{"date":"2025-08-11","repo":{"name":"github.com/darkyzhou/exit-hook-plus","commit":"d7c19a541db6c5d62c11d8dd41e3ce73c54d17ee"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/node.js.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":"Code-Review","score":0,"reason":"Found 0/15 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":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/darkyzhou/exit-hook-plus/node.js.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/darkyzhou/exit-hook-plus/node.js.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand 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":"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":"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT 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":"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":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"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":"Vulnerabilities","score":2,"reason":"8 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-qrpm-p2h7-hrv2","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55"],"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:49:18.834Z","repository_id":57231698,"created_at":"2025-08-18T01:49:18.835Z","updated_at":"2025-08-18T01:49:18.835Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32279657,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","response_time":59,"last_error":"SSL_read: 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":["async","exit","hook","javascript","typescript"],"created_at":"2024-11-11T09:26:45.820Z","updated_at":"2026-04-25T22:34:16.401Z","avatar_url":"https://github.com/darkyzhou.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# exit-hook-plus\n\n![](https://github.com/darkyzhou/exit-hook-plus/workflows/Node.js%20CI/badge.svg)\n\nDo something and then automatically exit the program when these cases happen:\n\n- an `unhandledRejection` or `uncaughtException` event occurred\n- received a `SIGHUP`, `SIGINT`, `SIGTERM` or `SIGBREAK` signal\n\nOr do something before the program exits due to:\n\n- No more operations are pending (normal exit)\n- a `process.exit` call (synchronous hooks only, read the text below)\n\n## Install\n\nThe library comes with `.d.ts`, so you are free from installing something like `@types/exit-hook-plus`.\n\n```text\n$ npm install exit-hook-plus\n```\n\n## Usage\n\n### APIs\n\n```typescript\n// Remove the default exit logger for exit reasons with category 'exception' and 'signal'.\n//\n// For category 'exception' reasons, the logger will print 'the program is now exiting due to unhandled exception: (reason.errorOrReason or its stack when it's an error)'.\n// For category 'signal' reasons, the logger will print 'the program is now exiting due to receiving signal: (signal name)'.\nfunction disableDefaultExitLogger() {}\n\n// Add an exit hook function.\n//\n// The function can be either sync or async.\n//\n// When using async hooks, you should be aware that they will NOT be executed if you manually\n// call process.exit to termintate the program, see the \"Warnings\" below.\nfunction addExitHook(hook: ExitHook) {}\n\n// Remove an existing hook function.\n// It will do nothing if the given hook does not exist.\nfunction removeExitHook(hook: ExitHook) {}\n\n// Pass the 'extra' object to all the hooks and automatically call process.exit with 'exitCode'.\n//\n// Notice that by this way you are free from the async hook issue above.\n// It is a sync function but under the hood it asynchronously executes the hooks and terminate the program\n// so you should treat it as the last action in your program and DO NOT run other codes after calling it.\n//\n// If you call it without any arguments, the exitCode will default to 0 and extra to undefined.\nfunction executeAllHooksAndTerminate(exitCode: number = 0, extra?: any) {}\n```\n\n### Examples\n\n#### A `unhandledRejection` or `uncaughtException` event\n\nIn this case, `exit-hook-plus` will **automatically terminate the program with exit code 1** after executing all the exit hook functions.\n\n```javascript\nconst { addExitHook } = require('exit-hook-plus');\n\n// the hook can be either async or sync\n// it will be executed after the Promise.reject is called\naddExitHook(async (reason) =\u003e {\n  // reason.category === 'exception'\n  // reason.errorOrReason is the argument of Promise.reject() in this case\n  // so reason.errorOrReason.message === 'test error'\n  console.dir(reason);\n});\n\n(function () {\n  // trigger an unhandledRejection event\n  return Promise.reject(new Error('test error'));\n})();\n```\n\n#### A `SIGHUP`, `SIGINT`, `SIGTERM` or `SIGBREAK` signal\n\nIn this case, `exit-hook-plus` will terminate the program with following exit code after executing all the exit hook functions:\n\n- `SIGUP`: 128+1 = 129\n- `SIGINT`: 128+2 = 130\n- `SIGTERM`: 128+15 = 143\n- `SIGBREAK`: 128+21 = 149\n\n```javascript\nconst { addExitHook } = require('exit-hook-plus');\n\n// the hook can be either async or sync\n// it will be executed after a SIGHUP, SIGINT, SIGTERM or SIGBREAK signal was sent\n// you can try it manually by pressing Ctrl+C to emit an SIGINT signal\naddExitHook(async (reason) =\u003e {\n  // reason.category === 'signal'\n  // reason.signal === 'SIGINT'\n  console.dir(reason);\n});\n\n// wait for signals\nsetTimeout(() =\u003e {}, 100000);\n```\n\n#### A normal exit or a `process.exit` call\n\n```javascript\nconst { addExitHook } = require('exit-hook-plus');\n\n// when you use process.exit to end the process\n// no async hooks can be used!\naddExitHook((reason) =\u003e {\n  // reason.category === 'trivial'\n  // reason.exitCode === 88\n  console.dir(reason);\n});\n\nprocess.exit(88);\n```\n\n#### A real world example\n\n```javascript\nconst { addExitHook, executeAllHooksAndTerminate } = require('exit-hook-plus');\n\n// we initialize connections to kafka and mongodb\n// and add the exit hook for disconnect functions\n// it's a bit like the 'defer' keyword in golang :D\nconsole.info('connecting to kafka');\nawait kafkaDataConsumer.connect();\nfor (const topic of TARGET_TOPICS) {\n  await kafkaDataConsumer.subscribe({ topic });\n}\naddExitHook(async () =\u003e await kafkaDataConsumer.disconnect());\n\nconsole.info('connecting to mongodb');\nawait mongo.connect();\naddExitHook(async () =\u003e await mongo.close());\n\nif (liveStreamEnded) {\n  console.info('live stream ended, program will exit soon');\n  // now terminate the program\n  // here we should NOT use process.exit because it will make async hooks unable to execute, see the \"Warnings\" below\n  executeAllHooksAndTerminate();\n}\n```\n\n### Warnings\n\n#### Hooks and `try-catch`\n\nAll the hooks specified by `addExitHook(...)` will each run with `try-catch` folded under the hood and remains quiet even if it runs into error.\nAs a result, you should prepare your own `try-catch` block in the hook function if needed.\n\n#### Asynchronous hooks and `process.exit()`\n\nIf the program is called to exit using `process.exit()`, only the synchronous hooks will be executed rather than the asynchronous ones because during the process the event loop is no longer available. For details, check out https://nodejs.org/api/process.html#process_event_exit.\n\n#### Windows and `process.kill(signal)`\n\nAccording to [Tappi/async-exit-hook](https://github.com/Tapppi/async-exit-hook/blob/master/readme.md#windows-and-processkillsignal).\nOn windows `process.kill(signal)` immediately kills the process, and does not fire signal events, and as such, cannot be used to gracefully exit.\n\n#### Hook `unhandledRejection` and `uncaughtException` events properly\n\nThe correct use of hooking them is mainly to perform cleanup of allocated resources (e.g. file descriptors, connections, etc) before shutting down the process.\nIt is not safe to resume normal operation after these events happened. For details, please refer to https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly.\n\n## Compatibility\n\nThe library is written in TypeScript 4.1.3 and Node v14, but is compiled into ES5 and tested in Node v10, v12 and v14 environment.\n\n## Reference\n\n- https://github.com/Tapppi/async-exit-hook\n- https://nodejs.org/api/process.html\n- https://blog.heroku.com/best-practices-nodejs-errors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkyzhou%2Fexit-hook-plus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarkyzhou%2Fexit-hook-plus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkyzhou%2Fexit-hook-plus/lists"}