diff --git a/.github/workflows/package-manager-ci.yml b/.github/workflows/package-manager-ci.yml new file mode 100644 index 0000000..c0255a3 --- /dev/null +++ b/.github/workflows/package-manager-ci.yml @@ -0,0 +1,59 @@ +name: package-manager-ci +on: + push: + paths-ignore: + - 'docs/**' + - '*.md' + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' +jobs: + pnpm: + name: pnpm package manager on ${{ matrix.node-version }} ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macOS-latest, windows-latest] + node-version: [12, 14, 16] + steps: + - uses: actions/checkout@v2.3.4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2.4.0 + with: + node-version: ${{ matrix.node-version }} + - name: Use pnpm + uses: pnpm/action-setup@v2.0.1 + with: + version: ^6.0.0 + - name: Install dependancies + run: pnpm install + - name: Tests + run: pnpm run test:ci + + yarn-pnp: + name: yarn-pnp package manager on ${{ matrix.node-version }} ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macOS-latest] + node-version: [12, 14, 16] + steps: + - uses: actions/checkout@v2.3.4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2.4.0 + with: + node-version: ${{ matrix.node-version }} + - name: Use yarn + run: | + npm install -g yarn + yarn set version berry + echo "nodeLinker: pnp" >> .yarnrc.yml + echo "pnpMode: loose" >> .yarnrc.yml + yarn add -D pino-elasticsearch@^6.0.0 + yarn install + env: + # needed due the yarn.lock file in repository's .gitignore + YARN_ENABLE_IMMUTABLE_INSTALLS: false + - name: Tests + run: yarn run test:yarn diff --git a/.gitignore b/.gitignore index 6704566..3556c94 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,12 @@ typings/ # Optional npm cache directory .npm +# Yarn files +.yarn +.yarnrc.yml +.pnp.cjs +yarn.lock + # Optional eslint cache .eslintcache diff --git a/README.md b/README.md index 3bed0ff..50ea5b1 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,28 @@ flag your stream classes. The underlining worker is automatically closed if the stream is garbage collected. + +### External modules + +You may use this module within compatible external modules, that exports the `worker.js` interface. + +```js +const ThreadStream = require('thread-stream') + +const modulePath = require.resolve('pino-elasticsearch') + +const stream = new ThreadStream({ + filename: modulePath, + workerData: { node: 'http://localhost:9200' } +}) + +stream.write('log to elasticsearch!') +stream.flushSync() +stream.end() +``` + +This module works with `yarn` in PnP (plug'n play) mode too! + ## License MIT diff --git a/lib/worker.js b/lib/worker.js index 349598a..1ed8e66 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -15,7 +15,25 @@ const state = new Int32Array(stateBuf) const data = Buffer.from(dataBuf) async function start () { - const fn = (await import(workerData.filename)).default + let fn + try { + fn = (await import(workerData.filename)).default + } catch (error) { + // A yarn user that tries to start a ThreadStream for an external module + // provides a filename pointing to a zip file. + // eg. require.resolve('pino-elasticsearch') // returns /foo/pino-elasticsearch-npm-6.1.0-0c03079478-6915435172.zip/bar.js + // The `import` will fail to try to load it. + // This catch block executes the `require` fallback to load the module correctly. + // In fact, yarn modifies the `require` function to manage the zipped path. + // More details at https://github.com/pinojs/pino/pull/1113 + // The error codes may change based on the node.js version (ENOTDIR > 12, ERR_MODULE_NOT_FOUND <= 12 ) + if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND') && + workerData.filename.startsWith('file://')) { + fn = require(workerData.filename.replace('file://', '')) + } else { + throw error + } + } destination = await fn(workerData.workerData) destination.on('error', function (err) { diff --git a/package.json b/package.json index 6810d04..430ec3f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "scripts": { "test": "standard && tap --no-check-coverage test/*.test.*js", "test:ci": "standard && tap \"test/**/*.test.*js\" --no-check-coverage --coverage-report=lcovonly", + "test:yarn": "tap \"test/**/*.test.js\" --no-check-coverage", "prepare": "husky install" }, "repository": { diff --git a/test/commonjs-fallback.test.js b/test/commonjs-fallback.test.js new file mode 100644 index 0000000..051e8b6 --- /dev/null +++ b/test/commonjs-fallback.test.js @@ -0,0 +1,29 @@ +'use strict' + +const { test } = require('tap') +const ThreadStream = require('..') + +const isYarnPnp = process.versions.pnp !== undefined + +test('yarn module resolution', { skip: !isYarnPnp }, t => { + t.plan(5) + + const modulePath = require.resolve('pino-elasticsearch') + t.match(modulePath, /.*\.zip.*/) + + const stream = new ThreadStream({ + filename: modulePath, + workerData: { node: null }, + sync: true + }) + + stream.on('error', (err) => { + t.pass('error emitted') + t.equal(err.message, 'Missing node(s) option', 'module custom error') + }) + + t.ok(stream.write('hello world\n')) + t.ok(stream.writable) + + stream.end() +})