Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .eslintrc

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ name: CI

on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

jobs:
Job:
name: Node.js
uses: node-modules/github-actions/.github/workflows/node-test.yml@master
with:
os: 'ubuntu-latest, macos-latest'
version: '18.19.0, 18, 20, 22'
version: '18.19.0, 18, 20, 22, 24'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Release

on:
push:
branches: [ master ]
branches: [master]

jobs:
release:
Expand Down
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
142 changes: 142 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"env": {
"node": true,
"mocha": true
},
"categories": {
"correctness": "error",
"perf": "error",
"nursery": "error",
"restriction": "error",
"style": "error",
"pedantic": "error",
"suspicious": "error"
},
"plugins": [
"import",
"typescript",
"unicorn",
"jsdoc",
"node",
"promise",
"oxc"
],
"rules": {
// eslint
"constructor-super": "error",
"getter-return": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-var": "error",
"no-eq-null": "error",
"no-await-in-loop": "allow",
"eqeqeq": ["error", "smart"],
"init-declarations": "allow",
"curly": "allow",
"no-ternary": "allow",
"max-params": ["error", 5],
"no-await-expression-member": "error",
"no-continue": "allow",
"guard-for-in": "allow",
"func-style": "allow",
"sort-imports": "allow",
"yoda": "allow",
"sort-keys": "allow",
"no-magic-numbers": "allow",
"no-duplicate-imports": "error",
"no-multi-assign": "error",
"func-names": "error",
"default-param-last": "error",
"prefer-object-spread": "error",
"no-undefined": "allow",
"no-plusplus": "allow",
"no-console": "allow",
"no-extraneous-class": "allow",
"no-empty-function": "allow",
"max-depth": ["error", 6],
"max-lines-per-function": "allow",
"no-lonely-if": "error",
"max-lines": "allow",
"require-await": "allow",
"max-nested-callbacks": ["error", 5],
"max-classes-per-file": "allow",
"radix": "allow",
"no-negated-condition": "error",
"no-else-return": "error",
"no-throw-literal": "error",

// import
"import/exports-last": "allow",
"import/max-dependencies": "allow",
"import/no-cycle": "error",
"import/no-anonymous-default-export": "allow",
"import/no-namespace": "error",
"import/named": "error",
"import/export": "error",
"import/no-default-export": "allow",
"import/unambiguous": "error",
"import/group-exports": "allow",

// promise
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"promise/prefer-await-to-callbacks": "error",
"promise/prefer-await-to-then": "error",
"promise/prefer-catch": "error",
"promise/no-return-in-finally": "error",
"promise/avoid-new": "error",

// unicorn
"unicorn/error-message": "error",
"unicorn/no-null": "allow",
"unicorn/filename-case": "allow",
"unicorn/prefer-structured-clone": "error",
"unicorn/prefer-logical-operator-over-ternary": "error",
"unicorn/prefer-number-properties": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-string-slice": "error",
// "unicorn/no-null": "error",
"unicorn/throw-new-error": "error",
"unicorn/catch-error-name": "allow",
"unicorn/prefer-spread": "allow",
"unicorn/numeric-separators-style": "error",
"unicorn/prefer-string-raw": "error",
"unicorn/text-encoding-identifier-case": "error",
"unicorn/no-array-for-each": "error",
"unicorn/explicit-length-check": "error",
"unicorn/no-lonely-if": "error",
"unicorn/no-useless-undefined": "allow",
"unicorn/prefer-date-now": "error",
"unicorn/no-static-only-class": "allow",
"unicorn/no-typeof-undefined": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/no-anonymous-default-export": "allow",

// oxc
"oxc/no-map-spread": "error",
"oxc/no-rest-spread-properties": "allow",
"oxc/no-optional-chaining": "allow",
"oxc/no-async-await": "allow",

// typescript
"typescript/explicit-function-return-type": "allow",
"typescript/consistent-type-imports": "error",
"typescript/consistent-type-definitions": "error",
"typescript/consistent-indexed-object-style": "allow",
"typescript/no-inferrable-types": "error",
"typescript/array-type": "error",
"typescript/no-non-null-assertion": "error",
"typescript/no-explicit-any": "error",
"typescript/no-import-type-side-effects": "error",
"typescript/no-dynamic-delete": "error",
"typescript/prefer-ts-expect-error": "error",
"typescript/ban-ts-comment": "error",
"typescript/prefer-enum-initializers": "error",

// jsdoc
"jsdoc/require-returns": "allow",
"jsdoc/require-param": "allow"
},
"ignorePatterns": ["index.d.ts", "test/fixtures/**", "__snapshots__"]
}
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CHANGELOG.md
__snapshots__
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"arrowParens": "avoid"
}
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ npm i graceful-process

## Usage

Require this module and execute it on every child process file.
Import this module and execute it on every child process file.

```js
// mycli.js
const { graceful } = require('graceful-process');
```ts
// mycli.ts
import { graceful } from 'graceful-process';

graceful({ logger: console, label: 'mycli-child-cmd' });
```
Expand Down
43 changes: 28 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,43 @@
"engines": {
"node": ">= 18.19.0"
},
"dependencies": {},
"dependencies": {
"read-env-value": "^1.1.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.1",
"@eggjs/tsconfig": "1",
"@types/mocha": "10",
"@arethetypeswrong/cli": "^0.18.1",
"@eggjs/tsconfig": "2",
"@types/node": "22",
"coffee": "^5.5.1",
"egg-bin": "6",
"eslint": "8",
"eslint-config-egg": "14",
"fkill": "^7.2.1",
"mm": "^3.4.0",
"@vitest/coverage-v8": "3",
"coffee": "5",
"fkill": "9",
"husky": "9",
"lint-staged": "15",
"mm": "4",
"oxlint": "^0.16.10",
"prettier": "3",
"tshy": "3",
"tshy-after": "1",
"typescript": "5",
"urllib": "^4.6.8"
"urllib": "4",
"vitest": "3"
},
"scripts": {
"lint": "eslint --cache src test --ext .ts",
"lint": "oxlint",
"pretest": "npm run lint -- --fix && npm run prepublishOnly",
"test": "egg-bin test",
"test": "vitest run --testTimeout=30000",
"preci": "npm run lint && npm run prepublishOnly",
"ci": "egg-bin cov && attw --pack",
"prepublishOnly": "tshy && tshy-after"
"ci": "vitest run --testTimeout=30000 --coverage",
"postci": "npm run prepublishOnly",
"prepublishOnly": "tshy && tshy-after && attw --pack",
"prepare": "husky"
},
"lint-staged": {
"*": "prettier --write --ignore-unknown --cache",
"*.{ts,js,json,md,yml}": [
"prettier --ignore-unknown --write",
"oxlint --fix"
]
},
"type": "module",
"tshy": {
Expand Down
20 changes: 16 additions & 4 deletions src/exit.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import assert from 'node:assert';
import { setTimeout } from 'node:timers/promises';

import type { Logger, BeforeExit } from './types.js';

const TIMEOUT = Symbol('before exit timeout');

export function getExitFunction(logger: Logger, label: string, timeout: number, beforeExit?: BeforeExit) {
export function getExitFunction(
logger: Logger,
label: string,
timeout: number,
beforeExit?: BeforeExit
) {

Check warning on line 13 in src/exit.ts

View check run for this annotation

Codecov / codecov/patch

src/exit.ts#L8-L13

Added lines #L8 - L13 were not covered by tests
if (beforeExit) {
assert(typeof beforeExit === 'function', 'beforeExit only support function');
assert.ok(
typeof beforeExit === 'function',
'beforeExit only support function'
);

Check warning on line 18 in src/exit.ts

View check run for this annotation

Codecov / codecov/patch

src/exit.ts#L15-L18

Added lines #L15 - L18 were not covered by tests
}

// only call beforeExit once
Expand All @@ -29,9 +38,12 @@
throw new Error(`Timeout ${timeout}ms`);
}
logger.info('[%s] beforeExit success', label);
// oxlint-disable-next-line unicorn/no-process-exit
process.exit(code);
} catch (err: any) {
logger.error('[%s] beforeExit fail, error: %s', label, err.message);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
logger.error('[%s] beforeExit fail, error: %s', label, message);

Check warning on line 45 in src/exit.ts

View check run for this annotation

Codecov / codecov/patch

src/exit.ts#L43-L45

Added lines #L43 - L45 were not covered by tests
// oxlint-disable-next-line unicorn/no-process-exit
process.exit(code);
}
};
Expand Down
38 changes: 30 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import cluster from 'node:cluster';

import { env } from 'read-env-value';

Check warning on line 3 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L3

Added line #L3 was not covered by tests

import { getExitFunction } from './exit.js';
import type { Logger, BeforeExit } from './types.js';

Expand Down Expand Up @@ -32,10 +35,12 @@
printLogLevels.warn = false;
}
const label = options.label ?? `graceful-process#${process.pid}`;
const timeout = options.timeout ?? parseInt(process.env.GRACEFUL_TIMEOUT ?? '5000');
const timeout = options.timeout ?? env('GRACEFUL_TIMEOUT', 'number', 5000);

Check warning on line 38 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L38

Added line #L38 was not covered by tests

if (Reflect.get(process, INITED)) {
printLogLevels.warn && logger.warn('[%s] graceful-process init already', label);
if (printLogLevels.warn) {
logger.warn('[%s] graceful-process init already', label);
}

Check warning on line 43 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L41-L43

Added lines #L41 - L43 were not covered by tests
return;
}
Reflect.set(process, INITED, true);
Expand All @@ -48,23 +53,34 @@
let called = false;
process.on('SIGTERM', () => {
if (called) {
printLogLevels.info && logger.info('[%s] receive signal SIGTERM again, waiting for exit', label);
if (printLogLevels.info) {
logger.info(
'[%s] receive signal SIGTERM again, waiting for exit',
label
);
}

Check warning on line 61 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L56-L61

Added lines #L56 - L61 were not covered by tests
return;
}
called = true;
printLogLevels.info && logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
if (printLogLevels.info) {
logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
}

Check warning on line 67 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L65-L67

Added lines #L65 - L67 were not covered by tests
exit(0);
});
} else {
process.once('SIGTERM', () => {
printLogLevels.info && logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
if (printLogLevels.info) {
logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
}

Check warning on line 74 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L72-L74

Added lines #L72 - L74 were not covered by tests
exit(0);
});
}

process.once('exit', code => {
const level = code === 0 ? 'info' : 'error';
printLogLevels[level] && logger[level]('[%s] exit with code:%s', label, code);
if (printLogLevels[level]) {
logger[level]('[%s] exit with code:%s', label, code);
}

Check warning on line 83 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L81-L83

Added lines #L81 - L83 were not covered by tests
});

if (cluster.worker) {
Expand All @@ -75,15 +91,21 @@
if (cluster.worker?.exitedAfterDisconnect) {
return;
}
logger.error('[%s] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false', label);
logger.error(
'[%s] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false',
label
);

Check warning on line 97 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L94-L97

Added lines #L94 - L97 were not covered by tests
});
} else {
// child_process mode
process.once('disconnect', () => {
// wait a loop for SIGTERM event happen
setImmediate(() => {
// if disconnect event emit, maybe master exit in accident
logger.error('[%s] receive disconnect event on child_process fork mode, exiting with code:110', label);
logger.error(
'[%s] receive disconnect event on child_process fork mode, exiting with code:110',
label
);

Check warning on line 108 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L105-L108

Added lines #L105 - L108 were not covered by tests
exit(110);
});
});
Expand Down
Loading