diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9bcdb46 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] +} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..98d6bfb --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test.yml@master + with: + os: 'ubuntu-latest' + version: '18.19.0, 18, 20, 22, 23' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml new file mode 100644 index 0000000..bac3fac --- /dev/null +++ b/.github/workflows/pkg.pr.new.yml @@ -0,0 +1,23 @@ +name: Publish Any Commit +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run prepublishOnly --if-present + + - run: npx pkg-pr-new publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1c6cbb1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +name: Release + +on: + push: + branches: [ master ] + +jobs: + release: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-release.yml@master + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitignore b/.gitignore index 2fb2823..2fa851e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ node_modules build components coverage +.tshy* +.eslintcache +dist diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index 63c6333..0000000 --- a/.jshintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -coverage/ -.tmp/ -.git/ diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index da51ea3..0000000 --- a/.jshintrc +++ /dev/null @@ -1,95 +0,0 @@ -{ - // JSHint Default Configuration File (as on JSHint website) - // See http://jshint.com/docs/ for more details - - "maxerr" : 50, // {int} Maximum error before stopping - - // Enforcing - "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) - "camelcase" : false, // true: Identifiers must be in camelCase - "curly" : true, // true: Require {} for every new block or scope - "eqeqeq" : true, // true: Require triple equals (===) for comparison - "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() - "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` - "indent" : false, // {int} Number of spaces to use for indentation - "latedef" : false, // true: Require variables/functions to be defined before being used - "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` - "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` - "noempty" : true, // true: Prohibit use of empty blocks - "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) - "plusplus" : false, // true: Prohibit use of `++` & `--` - "quotmark" : false, // Quotation mark consistency: - // false : do nothing (default) - // true : ensure whatever is used is consistent - // "single" : require single quotes - // "double" : require double quotes - "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) - "unused" : false, // true: Require all defined variables be used - "strict" : true, // true: Requires all functions run in ES5 Strict Mode - "trailing" : false, // true: Prohibit trailing whitespaces - "maxparams" : false, // {int} Max number of formal params allowed per function - "maxdepth" : false, // {int} Max depth of nested blocks (within functions) - "maxstatements" : false, // {int} Max number statements per function - "maxcomplexity" : false, // {int} Max cyclomatic complexity per function - "maxlen" : false, // {int} Max number of characters per line - - // Relaxing - "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) - "boss" : true, // true: Tolerate assignments where comparisons would be expected - "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. - "eqnull" : false, // true: Tolerate use of `== null` - "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) - "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) - "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) - // (ex: `for each`, multiple try/catch, function expression…) - "evil" : false, // true: Tolerate use of `eval` and `new Function()` - "expr" : true, // true: Tolerate `ExpressionStatement` as Programs - "funcscope" : false, // true: Tolerate defining variables inside control statements" - "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') - "iterator" : false, // true: Tolerate using the `__iterator__` property - "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block - "laxbreak" : true, // true: Tolerate possibly unsafe line breakings - "laxcomma" : false, // true: Tolerate comma-first style coding - "loopfunc" : false, // true: Tolerate functions being defined in loops - "multistr" : true, // true: Tolerate multi-line strings - "proto" : false, // true: Tolerate using the `__proto__` property - "scripturl" : false, // true: Tolerate script-targeted URLs - "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment - "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` - "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation - "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` - "validthis" : true, // true: Tolerate using this in a non-constructor function - - // Environments - "browser" : true, // Web Browser (window, document, etc) - "couch" : false, // CouchDB - "devel" : true, // Development/debugging (alert, confirm, etc) - "dojo" : false, // Dojo Toolkit - "jquery" : false, // jQuery - "mootools" : false, // MooTools - "node" : true, // Node.js - "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) - "prototypejs" : false, // Prototype and Scriptaculous - "rhino" : false, // Rhino - "worker" : false, // Web Workers - "wsh" : false, // Windows Scripting Host - "yui" : false, // Yahoo User Interface - "noyield" : true, // allow generators without a yield - - // Legacy - "nomen" : false, // true: Prohibit dangling `_` in variables - "onevar" : false, // true: Allow only one `var` statement per function - "passfail" : false, // true: Stop on first error - "white" : false, // true: Check against strict whitespace and indentation rules - - // Custom Globals - "globals" : { // additional predefined global variables - // mocha - "describe": true, - "it": true, - "before": true, - "afterEach": true, - "beforeEach": true, - "after": true - } -} diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 6c3d94c..0000000 --- a/.npmignore +++ /dev/null @@ -1,9 +0,0 @@ -test -coverage.html -Makefile -.travis.yml -logo.png -build -components -.jshint* -coverage diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f90af5b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -sudo: false -language: node_js -node_js: - - '10' - - 'stable' -script: "npm run test-travis" -after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index d232562..0000000 --- a/AUTHORS +++ /dev/null @@ -1,6 +0,0 @@ -# Ordered by date of first contribution. -# Auto-generated by 'contributors' on Sat, 05 Jul 2014 11:04:16 GMT. -# https://github.com/xingrz/node-contributors - -fengmk2 (https://github.com/fengmk2) -dead-horse (https://github.com/dead-horse) diff --git a/History.md b/CHANGELOG.md similarity index 100% rename from History.md rename to CHANGELOG.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d6338aa --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2011-2014 fengmk2 +Copyright (c) 2024-present node-modules and the contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e99d166..0ef078c 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,58 @@ # pedding [![NPM version][npm-image]][npm-url] -[![build status][travis-image]][travis-url] -[![Test coverage][coveralls-image]][coveralls-url] -[![Gittip][gittip-image]][gittip-url] -[![David deps][david-image]][david-url] -[![node version][node-image]][node-url] +[![Node.js CI](https://github.com/node-modules/pedding/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/pedding/actions/workflows/nodejs.yml) +[![Test coverage][codecov-image]][codecov-url] [![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/pedding.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [npm-image]: https://img.shields.io/npm/v/pedding.svg?style=flat-square [npm-url]: https://npmjs.org/package/pedding -[travis-image]: https://img.shields.io/travis/node-modules/pedding.svg?style=flat-square -[travis-url]: https://travis-ci.org/node-modules/pedding -[coveralls-image]: https://img.shields.io/coveralls/node-modules/pedding.svg?style=flat-square -[coveralls-url]: https://coveralls.io/r/node-modules/pedding?branch=master -[gittip-image]: https://img.shields.io/gittip/fengmk2.svg?style=flat-square -[gittip-url]: https://www.gittip.com/fengmk2/ -[david-image]: https://img.shields.io/david/node-modules/pedding.svg?style=flat-square -[david-url]: https://david-dm.org/node-modules/pedding -[node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square -[node-url]: http://nodejs.org/download/ +[codecov-image]: https://codecov.io/github/node-modules/pedding/coverage.svg?branch=master +[codecov-url]: https://codecov.io/github/node-modules/pedding?branch=master [download-image]: https://img.shields.io/npm/dm/pedding.svg?style=flat-square [download-url]: https://npmjs.org/package/pedding -Useful tools for unit test: Just pedding for callback. +Useful tools for unit test: Just pending for callback. ## Installation ### Node.js ```bash -$ npm install pedding +npm install pedding ``` -### [Component](https://github.com/component/component) +## Usage -```bash -$ component install node-modules/pedding +CommonJS + +```js +const { pending } = require('pedding'); + +it('should request two resources', done => { + done = pending(2, done); + http.get('http://fengmk2.github.com', res => { + done(); + }); + http.get('http://www.taobao.com', res => { + done(); + }); +}); ``` -## Usage +ESM and TypeScript -```js -var pedding = require('pedding'); +```ts +import { pending } from 'pedding'; -it('should request two resources', function (done) { - done = pedding(2, done); - http.get('http://fengmk2.github.com', function (res) { +it('should request two resources', done => { + done = pending(2, done); + http.get('http://fengmk2.github.com', res => { done(); }); - http.get('http://www.taobao.com', function (res) { + http.get('http://www.taobao.com', res => { done(); }); }); @@ -57,25 +60,10 @@ it('should request two resources', function (done) { ## License -(The MIT License) - -Copyright (c) 2011-2014 fengmk2 <fengmk2@gmail.com> +[MIT](LICENSE) -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +## Contributors -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +[![Contributors](https://contrib.rocks/image?repo=node-modules/pedding)](https://github.com/node-modules/pedding/graphs/contributors) -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Made with [contributors-img](https://contrib.rocks). diff --git a/component.json b/component.json deleted file mode 100644 index 57d794e..0000000 --- a/component.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "pedding", - "repo": "node-modules/pedding", - "version": "1.0.0", - "description": "Just pedding for callback.", - "development": { - "chaijs/chai": "*" - }, - "keywords": [ - "pedding", - "callback" - ], - "scripts": [ - "index.js" - ], - "license": "MIT" -} diff --git a/index.js b/index.js deleted file mode 100644 index d9cf1e8..0000000 --- a/index.js +++ /dev/null @@ -1,47 +0,0 @@ -/**! - * pedding - index.js - * - * Copyright(c) fengmk2 and other contributors. - * MIT Licensed - * - * Authors: - * fengmk2 (http://fengmk2.github.com) - */ - -'use strict'; - -/** - * Module dependencies. - */ - -module.exports = pedding; - -function pedding(n, fn) { - if (typeof n === 'function') { - var tmp = n; - n = fn; - fn = tmp; - } - - var called = false; - var times = 0; - var callStack = new Error(); - callStack.name = 'CallStack'; - return function (err) { - if (called) { - return; - } - if (err) { - called = true; - return fn(err); - } - times++; - if (times === n) { - fn(); - } else if (times > n) { - var err = new Error('Expect to call ' + n + ' times, but got ' + times); - err.stack += '\n' + callStack.stack; - throw err; - } - }; -} diff --git a/package.json b/package.json index acf84b9..6cf23c2 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,63 @@ { "name": "pedding", "version": "1.1.0", - "description": "Just pedding for callback.", - "main": "index.js", - "scripts": { - "test": "mocha --check-leaks -R spec -t 5000 test/*.test.js", - "test-cov": "istanbul cover node_modules/.bin/_mocha -- --check-leaks -t 5000 test/*.test.js", - "test-travis": "istanbul cover node_modules/.bin/_mocha --report lcovonly -- --check-leaks -t 5000 test/*.test.js", - "test-component": "mocha-phantomjs test/index.html", - "jshint": "jshint .", - "cnpm": "npm install --registry=https://registry.npm.taobao.org", - "contributors": "contributors -f plain -o AUTHORS" - }, + "description": "Just pending for callback.", "repository": "git://github.com/node-modules/pedding.git", "keywords": [ "pedding", + "pending", "callback" ], + "author": "fengmk2 ", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, "devDependencies": { - "chai": "*", - "component": "*", - "contributors": "*", - "istanbul": "^0.4.5", - "mocha": "*", - "mocha-phantomjs": "*" + "@arethetypeswrong/cli": "^0.17.1", + "@eggjs/tsconfig": "1", + "@types/node": "22", + "@types/mocha": "10", + "egg-bin": "6", + "eslint": "8", + "eslint-config-egg": "14", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" }, - "author": "fengmk2 ", - "license": "MIT" + "scripts": { + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run lint -- --fix && npm run prepublishOnly", + "test": "egg-bin test", + "preci": "npm run lint && npm run prepublishOnly && attw --pack", + "ci": "egg-bin cov", + "prepublishOnly": "tshy && tshy-after" + }, + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..3b413f8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,51 @@ +export type Callback = (err?: Error) => void; + +export class ExecuteTooManyTimesError extends Error { + expected: number; + actual: number; + + constructor(expected: number, actual: number) { + super(`Expected to execute ${expected} times, but got ${actual} times`); + this.expected = expected; + this.actual = actual; + this.name = this.constructor.name; + Error.captureStackTrace(this, this.constructor); + } +} + +export function pending(n: number, fn: Callback) { + if (typeof n === 'function') { + // keep compatibility for old version + // pending(fn, n) + const tmp = n; + n = fn as unknown as number; + fn = tmp as Callback; + } + + let called = false; + let times = 0; + const callStack = new Error(); + callStack.name = 'CallStack'; + return (err?: Error) => { + if (called) { + return; + } + if (err) { + called = true; + return fn(err); + } + times++; + if (times === n) { + fn(); + } else if (times > n) { + const err = new ExecuteTooManyTimesError(n, times); + err.stack += '\n' + callStack.stack; + throw err; + } + }; +} + +/** + * @deprecated Use `pending` instead + */ +export const pedding = pending; diff --git a/test/index.html b/test/index.html deleted file mode 100644 index b3d7cc4..0000000 --- a/test/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - pedding tests - - - - - -
- - - - - - - \ No newline at end of file diff --git a/test/pedding.test.js b/test/pedding.test.js deleted file mode 100644 index ebddda7..0000000 --- a/test/pedding.test.js +++ /dev/null @@ -1,75 +0,0 @@ -/*! - * pedding - test/pedding.test.js - * Copyright(c) 2013 fengmk2 - * MIT Licensed - */ - -"use strict"; - -/** - * Module dependencies. - */ - -try { - var pedding = require('../'); -} catch (e) { - var pedding = require('pedding'); -} - -var should = require('chai').should(); - -describe('pedding.test.js', function () { - it('should called once', function (done) { - var fn = pedding(1, function () {}); - fn(); - (function () { - fn(); - }).should.throw('Expect to call 1 times, but got 2'); - (function () { - fn(); - }).should.throw('Expect to call 1 times, but got 3'); - done(); - }); - - it('should called 100 times then done()', function (done) { - done = pedding(100, done); - for (var i = 0; i < 100; i++) { - done(); - } - }); - - it('should called pedding(done, 100) then done()', function (done) { - done = pedding(done, 100); - for (var i = 0; i < 100; i++) { - done(); - } - }); - - it('should called once when meet error', function () { - var count = 0; - var cb = function (err) { - should.exist(err); - err.message.should.equal('mock error'); - count++; - }; - cb = pedding(2, cb); - cb(new Error('mock error')); - cb(new Error('mock error')); - cb(new Error('mock error')); - count.should.equal(1); - }); - - it('should contain stack from caller', function(done) { - var cb = pedding(1, function() {}); - cb(); - setTimeout(function() { - try { - cb(); - throw new Error('should not run'); - } catch (e) { - e.stack.should.match(/\nCallStack\n at pedding/); - done(); - } - }, 0); - }) -}); diff --git a/test/pedding.test.ts b/test/pedding.test.ts new file mode 100644 index 0000000..8dd4770 --- /dev/null +++ b/test/pedding.test.ts @@ -0,0 +1,55 @@ +import { strict as assert } from 'node:assert'; +import { pending, pedding } from '../src/index.js'; + +describe('test/pedding.test.ts', () => { + it('should called once', done => { + const fn = pending(1, function() {}); + fn(); + assert.throws(() => { + fn(); + }, /ExecuteTooManyTimesError: Expected to execute 1 times, but got 2 times/); + done(); + }); + + it('should called 100 times then done()', done => { + done = pedding(100, done); + for (let i = 0; i < 100; i++) { + done(); + } + }); + + it('should called pedding(done, 100) then done()', done => { + done = pedding(done as any, 100 as any); + for (let i = 0; i < 100; i++) { + done(); + } + }); + + it('should called once when meet error', () => { + let count = 0; + let cb = function(err?: Error) { + assert(err); + assert.strictEqual(err.message, 'mock error'); + count++; + }; + cb = pedding(2, cb); + cb(new Error('mock error')); + cb(new Error('mock error')); + cb(new Error('mock error')); + assert.strictEqual(count, 1); + }); + + it('should contain stack from caller', done => { + const cb = pending(1, function() {}); + cb(); + setTimeout(() => { + try { + cb(); + throw new Error('should not run'); + } catch (e: any) { + assert.match(e.stack, /\s+CallStack\s+at pending /gm); + done(); + } + }, 0); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +}