diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5e06a2b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: AlCalzone # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/config.env b/.github/config.env new file mode 100644 index 0000000..5c0949e --- /dev/null +++ b/.github/config.env @@ -0,0 +1,13 @@ +# Define constants here: + +PACKAGE_NAME=p-timeout +ESM2CJS_PACKAGE_NAME="@esm2cjs/${PACKAGE_NAME}" + +UPSTREAM_REPO=sindresorhus/p-timeout +UPSTREAM_REPO_URL=https://github.com/${UPSTREAM_REPO}.git + +TEST_SCRIPT_CJS=.github/test-scripts/test.cjs +TEST_SCRIPT_ESM=.github/test-scripts/test.mjs + +GIT_USER=Botty +GIT_EMAIL=d.griesel@gmx.net diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index e9d1862..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1,4 +0,0 @@ -github: sindresorhus -open_collective: sindresorhus -tidelift: npm/p-timeout -custom: https://sindresorhus.com/donate diff --git a/.github/make_hybrid.sh b/.github/make_hybrid.sh new file mode 100755 index 0000000..e968175 --- /dev/null +++ b/.github/make_hybrid.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +mkdir -p esm cjs +mv index.js esm/index.js +mv index.d.ts esm/index.d.ts +sed -i 's#./index.js#./esm/index.js#' test.js +mv index.test-d.ts esm/index.test-d.ts +mv test.js test.mjs +sed -i 's#test.js#test.mjs#' test.mjs + +# Placeholder to... +# Replace module imports in all ts files +# readarray -d '' files < <(find esm \( -name "*.js" -o -name "*.d.ts" \) -print0) +# function replace_imports () { +# from=$1 +# to="${2:-@esm2cjs/$from}" +# for file in "${files[@]}" ; do +# sed -i "s#'$from'#'$to'#g" "$file" +# done +# } +# replace_imports "FROM" "@esm2cjs/TO" +# replace_imports "FROM" # to = "@esm2cjs/FROM" + +PJSON=$(cat package.json | jq --tab ' + del(.type) + | .description = .description + ". This is a fork of " + .repository + ", but with CommonJS support." + | .repository = "esm2cjs/" + .name + | .name |= "@esm2cjs/" + . + | .author = { "name": "Dominic Griesel", "email": "d.griesel@gmx.net" } + | .publishConfig = { "access": "public" } + | .funding = "https://github.com/sponsors/AlCalzone" + | .main = "cjs/index.js" + | .module = "esm/index.js" + | .files = ["cjs/", "esm/"] + | .exports = {} + | .exports["."].import = "./esm/index.js" + | .exports["."].require = "./cjs/index.js" + | .exports["./package.json"] = "./package.json" + | .types = "esm/index.d.ts" + | .typesVersions = {} + | .typesVersions["*"] = {} + | .typesVersions["*"]["esm/index.d.ts"] = ["esm/index.d.ts"] + | .typesVersions["*"]["cjs/index.d.ts"] = ["esm/index.d.ts"] + | .typesVersions["*"]["*"] = ["esm/*"] + | .scripts["to-cjs"] = "esm2cjs --in esm --out cjs -t node12" + | .xo = {ignores: ["cjs", "**/*.test-d.ts", "**/*.d.ts"]} +') +# Placeholder for custom deps: + # | .dependencies["@esm2cjs/DEP"] = .dependencies["DEP"] + # | del(.dependencies["DEP"]) + +echo "$PJSON" > package.json + +# Update package.json -> version if upstream forgot to update it +if [[ ! -z "${TAG}" ]] ; then + VERSION=$(echo "${TAG/v/}") + PJSON=$(cat package.json | jq --tab --arg VERSION "$VERSION" '.version = $VERSION') + echo "$PJSON" > package.json +fi + +npm i -D @alcalzone/esm2cjs +npm run to-cjs +npm uninstall -D @alcalzone/esm2cjs + +PJSON=$(cat package.json | jq --tab 'del(.scripts["to-cjs"])') +echo "$PJSON" > package.json + +npm test \ No newline at end of file diff --git a/.github/security.md b/.github/security.md deleted file mode 100644 index 5358dc5..0000000 --- a/.github/security.md +++ /dev/null @@ -1,3 +0,0 @@ -# Security Policy - -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. diff --git a/.github/test-scripts/test.cjs b/.github/test-scripts/test.cjs new file mode 100644 index 0000000..e853096 --- /dev/null +++ b/.github/test-scripts/test.cjs @@ -0,0 +1,4 @@ +const pTimeout = require("@esm2cjs/p-timeout").default; +const assert = require("assert"); + +assert(typeof pTimeout === "function"); diff --git a/.github/test-scripts/test.mjs b/.github/test-scripts/test.mjs new file mode 100644 index 0000000..d7885c1 --- /dev/null +++ b/.github/test-scripts/test.mjs @@ -0,0 +1,4 @@ +import pTimeout from "@esm2cjs/p-timeout"; +import assert from "assert"; + +assert(typeof pTimeout === "function"); diff --git a/.github/workflows/check_releases.yml b/.github/workflows/check_releases.yml new file mode 100644 index 0000000..8686d29 --- /dev/null +++ b/.github/workflows/check_releases.yml @@ -0,0 +1,66 @@ +name: Check upstream for new release +on: + schedule: + # check each night at 1 AM + - cron: '00 01 * * *' + workflow_dispatch: {} + +jobs: + check-releases: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Load configuration + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/config.env' + expand: 'true' + + - name: Fetch existing tags + run: git fetch --tags origin + + - id: check-version + name: Determine upstream version + uses: pozetroninc/github-action-get-latest-release@v0.5.0 + with: + repository: ${{ env.UPSTREAM_REPO }} + excludes: prerelease,draft + + - name: Check if tag exists locally + id: check-tag + run: | + RELEASE=${{ steps.check-version.outputs.release }} + echo "Latest release is: ${RELEASE}" + # our tags have a suffix to distinguish them from upstream + if git rev-parse -q --verify "refs/tags/${RELEASE}-cjs"; then + echo "Tag exists here" + else + echo "Tag does not exist here, triggering build..." + echo "::set-output name=tag::${RELEASE}" + fi + + - name: Trigger build and wait + id: build + if: steps.check-tag.outputs.tag + uses: aurelien-baudet/workflow-dispatch@v2 + with: + workflow: Sync with upstream and patch + token: ${{ secrets.REPO_ACCESS_TOKEN }} + inputs: '{"tag":"${{steps.check-tag.outputs.tag}}"}' + wait-for-completion: true + wait-for-completion-interval: 30s + display-workflow-run-url: false + + - name: Publish new version + if: steps.check-tag.outputs.tag && steps.build.outputs.workflow-conclusion == 'success' + uses: aurelien-baudet/workflow-dispatch@v2 + with: + workflow: Publish + token: ${{ secrets.REPO_ACCESS_TOKEN }} + inputs: '{"tag":"${{steps.check-tag.outputs.tag}}"}' + wait-for-completion: true + wait-for-completion-interval: 30s + display-workflow-run-url: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 6a82b18..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: CI -on: - - push - - pull_request -jobs: - test: - name: Node.js ${{ matrix.node-version }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - node-version: - - 18 - - 16 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm test diff --git a/.github/workflows/make_hybrid.yml b/.github/workflows/make_hybrid.yml new file mode 100644 index 0000000..d7eaa0c --- /dev/null +++ b/.github/workflows/make_hybrid.yml @@ -0,0 +1,112 @@ +name: Sync with upstream and patch # Do not rename without updating check_releases.yml too +on: + workflow_dispatch: + inputs: + tag: + description: 'Which tag to build' + required: true + +jobs: + patch: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] # This should be LTS + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Load configuration + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/config.env' + expand: 'true' + + - name: Prepare repository + env: + TAG: ${{ github.event.inputs.tag }} + run: | + # Restore files to the state in the upstream repo, except our CI scripts and the readme + git remote add upstream ${UPSTREAM_REPO_URL} + git fetch upstream + # The tag without suffix only exists on upstream, so we check it out directly + git restore --source=$TAG -- . ':!.github' ':!README.md' ':!readme.md' + ls -la + + - name: Convert to CommonJS/ESM Hybrid + env: + TAG: ${{ github.event.inputs.tag }} + run: bash .github/make_hybrid.sh + + - name: Test that both import and require work + run: | + mkdir __test + PACK_FILENAME=$(npm pack --silent --pack-destination __test) + cd __test + npm init -y + npm i $PACK_FILENAME + + echo "Test import" + node ../${TEST_SCRIPT_ESM} + + echo "Test require" + node ../${TEST_SCRIPT_CJS} + + cd .. + rm -rf __test + + - name: Remember changes for push + run: | + git add . + git diff --patch --staged > delta.patch + + - name: Upload changes to cache + uses: actions/upload-artifact@v3 + with: + name: delta + path: delta.patch + retention-days: 1 # We don't need this any longer than the workflow takes, but 1 day is the minimum + + commit: + runs-on: ubuntu-latest + needs: [patch] + + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Load configuration + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/config.env' + expand: 'true' + + - name: Download changes from cache + uses: actions/download-artifact@v3 + with: + name: delta + + - name: Push changes to repo + env: + TAG: ${{ github.event.inputs.tag }} + run: | + git apply delta.patch + rm delta.patch + + git config --global user.name "${GIT_USER}" + git config --global user.email "${GIT_EMAIL}" + + git add -A -- ':!.github/*' ':!README.md' ':!readme.md' + git commit -m "update repository with upstream changes" + # our tags have a suffix to distinguish them from upstream + git tag "$TAG-cjs" + + git push + git push origin "$TAG-cjs" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..12b269a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,36 @@ +name: Publish + +on: + workflow_dispatch: + inputs: + tag: + description: 'Which tag to build' + required: true + +jobs: + publish: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] # This should be LTS + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + # our tags have a suffix to distinguish them from upstream + ref: ${{ github.event.inputs.tag }}-cjs + + # Do we need this step? + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - run: npm pack --dry-run + # - name: Publish package to npm + # run: | + # npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} + # npm whoami + # npm publish diff --git a/readme.md b/readme.md index 12c7c52..cdb5f81 100644 --- a/readme.md +++ b/readme.md @@ -1,146 +1,52 @@ -# p-timeout +# @esm2cjs/p-timeout -> Timeout a promise after a specified amount of time +This is a fork of https://github.com/sindresorhus/p-timeout, but automatically patched to support ESM **and** CommonJS, unlike the original repository. ## Install -```sh -npm install p-timeout -``` - -## Usage - -```js -import {setTimeout} from 'node:timers/promises'; -import pTimeout from 'p-timeout'; - -const delayedPromise = setTimeout(200); +You can use an npm alias to install this package under the original name: -await pTimeout(delayedPromise, { - milliseconds: 50, -}); -//=> [TimeoutError: Promise timed out after 50 milliseconds] ``` - -## API - -### pTimeout(input, options) - -Returns a decorated `input` that times out after `milliseconds` time. It has a `.clear()` method that clears the timeout. - -If you pass in a cancelable promise, specifically a promise with a `.cancel()` method, that method will be called when the `pTimeout` promise times out. - -#### input - -Type: `Promise` - -Promise to decorate. - -#### options - -Type: `object` - -##### milliseconds - -Type: `number` - -Milliseconds before timing out. - -Passing `Infinity` will cause it to never time out. - -##### message - -Type: `string | Error`\ -Default: `'Promise timed out after 50 milliseconds'` - -Specify a custom error message or error. - -If you do a custom error, it's recommended to sub-class `pTimeout.TimeoutError`. - -##### fallback - -Type: `Function` - -Do something other than rejecting with an error on timeout. - -You could for example retry: - -```js -import {setTimeout} from 'node:timers/promises'; -import pTimeout from 'p-timeout'; - -const delayedPromise = () => setTimeout(200); - -await pTimeout(delayedPromise(), { - milliseconds: 50, - fallback: () => { - return pTimeout(delayedPromise(), 300); - }, -}); +npm i p-timeout@npm:@esm2cjs/p-timeout ``` -##### customTimers - -Type: `object` with function properties `setTimeout` and `clearTimeout` - -Custom implementations for the `setTimeout` and `clearTimeout` functions. - -Useful for testing purposes, in particular to work around [`sinon.useFakeTimers()`](https://sinonjs.org/releases/latest/fake-timers/). - -Example: - -```js -import {setTimeout} from 'node:timers/promises'; -import pTimeout from 'p-timeout'; - -const originalSetTimeout = setTimeout; -const originalClearTimeout = clearTimeout; - -sinon.useFakeTimers(); - -// Use `pTimeout` without being affected by `sinon.useFakeTimers()`: -await pTimeout(doSomething(), { - milliseconds: 2000, - customTimers: { - setTimeout: originalSetTimeout, - clearTimeout: originalClearTimeout - } -}); +```jsonc +// package.json +"dependencies": { + "p-timeout": "npm:@esm2cjs/p-timeout" +} ``` -#### signal +but `npm` might dedupe this incorrectly when other packages depend on the replaced package. If you can, prefer using the scoped package directly: -Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) +``` +npm i @esm2cjs/p-timeout +``` -You can abort the promise using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). +```jsonc +// package.json +"dependencies": { + "@esm2cjs/p-timeout": "^ver.si.on" +} +``` -*Requires Node.js 16 or later.* +## Usage ```js -import pTimeout from 'p-timeout'; -import delay from 'delay'; - -const delayedPromise = delay(3000); - -const abortController = new AbortController(); +// Using ESM import syntax +import pTimeout from "@esm2cjs/p-timeout"; -setTimeout(() => { - abortController.abort(); -}, 100); - -await pTimeout(delayedPromise, { - milliseconds: 2000, - signal: abortController.signal -}); +// Using CommonJS require() +const pTimeout = require("@esm2cjs/p-timeout").default; ``` -### TimeoutError +> **Note:** +> Because the original module uses `export default`, you need to append `.default` to the `require()` call. + +For more details, please see the original [repository](https://github.com/sindresorhus/p-timeout). -Exposed for instance checking and sub-classing. +## Sponsoring -## Related +To support my efforts in maintaining the ESM/CommonJS hybrid, please sponsor [here](https://github.com/sponsors/AlCalzone). -- [delay](https://github.com/sindresorhus/delay) - Delay a promise a specified amount of time -- [p-min-delay](https://github.com/sindresorhus/p-min-delay) - Delay a promise a minimum amount of time -- [p-retry](https://github.com/sindresorhus/p-retry) - Retry a promise-returning function -- [Moreā€¦](https://github.com/sindresorhus/promise-fun) +To support the original author of the module, please sponsor [here](https://github.com/sindresorhus/p-timeout).