From 9312900f0edbe6c4caa4b593cf4e7f58ab0d5893 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 2 Nov 2023 23:24:57 +0700 Subject: [PATCH] Require Node.js 18 --- .github/workflows/main.yml | 5 +- package.json | 11 +- readme.md | 2 +- test.js | 477 ++++++++++++------------------------- 4 files changed, 167 insertions(+), 328 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1ed55d5..346585c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,10 +12,9 @@ jobs: node-version: - 20 - 18 - - 16 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/package.json b/package.json index 27f93f1..792adb4 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "type": "module", "exports": "./index.js", "engines": { - "node": ">=16" + "node": ">=18" }, "scripts": { - "test": "xo && mocha" + "test": "xo && ava" }, "files": [ "index.js" @@ -34,15 +34,16 @@ "vinyl" ], "dependencies": { - "multimatch": "^6.0.0", + "multimatch": "^7.0.0", "plugin-error": "^2.0.1", "streamfilter": "^3.0.0", "to-absolute-glob": "^3.0.0" }, "devDependencies": { - "mocha": "^10.2.0", + "ava": "^5.3.1", + "p-event": "^6.0.0", "vinyl": "^3.0.0", - "xo": "^0.55.0" + "xo": "^0.56.0" }, "peerDependencies": { "gulp": ">=4" diff --git a/readme.md b/readme.md index a744d06..2558634 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ import gulp from 'gulp'; import uglify from 'gulp-uglify'; import filter from 'gulp-filter'; -exports.default = () => { +export default () => { // Create filter instance inside task function const f = filter(['**', '!*src/vendor']); diff --git a/test.js b/test.js index 6a107ff..30ff07c 100644 --- a/test.js +++ b/test.js @@ -1,331 +1,179 @@ -/* eslint-env mocha */ import {fileURLToPath} from 'node:url'; import path from 'node:path'; -import {strict as assert} from 'node:assert'; +import {Readable} from 'node:stream'; +import test from 'ava'; +import {pEvent} from 'p-event'; import Vinyl from 'vinyl'; import filter from './index.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -describe('filter()', () => { - it('should filter files', cb => { - const stream = filter('included.js'); - const buffer = []; - - stream.on('data', file => { - buffer.push(file); - }); - - stream.on('end', () => { - assert.equal(buffer.length, 1); - assert.equal(buffer[0].relative, 'included.js'); - cb(); - }); +test('filter', async t => { + const stream = filter('included.js'); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'included.js'), + })); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'ignored.js'), + })); + stream.end(); + + const data = await pEvent(stream, 'data'); + t.is(data.relative, 'included.js'); +}); - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'included.js'), - })); +test('filter with restore set to false', async t => { + const stream = filter('included.js', {restore: false}); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'included.js'), + })); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'ignored.js'), + })); + stream.end(); + + const data = await pEvent(stream, 'data'); + t.is(data.relative, 'included.js'); +}); - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'ignored.js'), - })); +test('forward multimatch options', async t => { + const stream = filter('*.js', {matchBase: true}); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'nested', 'resource.js'), + })); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'nested', 'resource.css'), + })); + stream.end(); + + const data = await pEvent(stream, 'data'); + t.is(data.relative, path.join('nested', 'resource.js')); +}); - stream.end(); - }); - - describe('with restore set to false', () => { - it('should filter files', cb => { - const stream = filter('included.js', {restore: false}); - const buffer = []; - - stream.on('data', file => { - buffer.push(file); - }); - - stream.on('end', () => { - assert.equal(buffer.length, 1); - assert.equal(buffer[0].relative, 'included.js'); - cb(); - }); - - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'included.js'), - })); - - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'ignored.js'), - })); - - stream.end(); - }); - }); - - it('should forward multimatch options', cb => { - const stream = filter('*.js', {matchBase: true}); - const buffer = []; - - stream.on('data', file => { - buffer.push(file); - }); - - stream.on('end', () => { - assert.equal(buffer.length, 1); - assert.equal(buffer[0].relative, path.join('nested', 'resource.js')); - cb(); - }); - - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'nested', 'resource.js'), - })); - - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'nested', 'resource.css'), - })); +test('filter using a function', async t => { + const stream = filter(file => file.path === 'included.js'); + stream.write(new Vinyl({path: 'included.js'})); + stream.write(new Vinyl({path: 'ignored.js'})); + stream.end(); - stream.end(); - }); + const data = await pEvent(stream, 'data'); + t.is(data.path, 'included.js'); +}); - it('should filter using a function', cb => { - const stream = filter(file => file.path === 'included.js'); +test('filter files with negate pattern and leading dot', async t => { + const stream = filter(['*', '!*.json', '!*rc'], {dot: true}); + stream.write(new Vinyl({path: 'included.js'})); + stream.write(new Vinyl({path: 'package.json'})); + stream.write(new Vinyl({path: '.jshintrc'})); + stream.write(new Vinyl({path: 'app.js'})); + stream.end(); - const buffer = []; + const data = await pEvent(stream, 'data'); + t.is(data.path, 'included.js'); +}); - stream.on('data', file => { - buffer.push(file); - }); +test('filter with respect to current working directory', async t => { + const stream = filter('test/**/*.js'); + stream.write(new Vinyl({ + base: path.join(__dirname, 'test'), + path: path.join(__dirname, 'test', 'included.js'), + })); + stream.write(new Vinyl({ + base: __dirname, + path: path.join(__dirname, 'ignored.js'), + })); + stream.end(); + + const data = await pEvent(stream, 'data'); + t.is(data.relative, 'included.js'); +}); - stream.on('end', () => { - assert.equal(buffer.length, 1); - assert.equal(buffer[0].path, 'included.js'); - cb(); - }); +test('filter.restore - bring back the previously filtered files', async t => { + const stream = filter('*.json', {restore: true}); + const completeStream = stream.pipe(stream.restore); + stream.write(new Vinyl({path: 'package.json'})); + stream.write(new Vinyl({path: 'app.js'})); + stream.write(new Vinyl({path: 'package2.json'})); + stream.end(); - stream.write(new Vinyl({path: 'included.js'})); - stream.write(new Vinyl({path: 'ignored.js'})); - stream.end(); - }); - - it('should filter files with negate pattern and leading dot', cb => { - const stream = filter(['*', '!*.json', '!*rc'], {dot: true}); - const buffer = []; - - stream.on('data', file => { - buffer.push(file); - }); - - stream.on('end', () => { - assert.equal(buffer.length, 2); - assert.equal(buffer[0].path, 'included.js'); - assert.equal(buffer[1].path, 'app.js'); - cb(); - }); - - stream.write(new Vinyl({path: 'included.js'})); - stream.write(new Vinyl({path: 'package.json'})); - stream.write(new Vinyl({path: '.jshintrc'})); - stream.write(new Vinyl({path: 'app.js'})); - stream.end(); - }); + const data = await pEvent(completeStream, 'data'); + t.is(data.path, 'package.json'); +}); - it('should filter with respect to current working directory', cb => { - const stream = filter('test/**/*.js'); - const buffer = []; +test('filter.restore - work when using multiple filters', async t => { + const streamFilter1 = filter(['*.js'], {restore: true}); + const streamFilter2 = filter(['*.json'], {restore: true}); + const completeStream = streamFilter1 + .pipe(streamFilter2) + .pipe(streamFilter1.restore) + .pipe(streamFilter2.restore); + streamFilter1.write(new Vinyl({path: 'package.json'})); + streamFilter1.write(new Vinyl({path: 'app.js'})); + streamFilter1.write(new Vinyl({path: 'main.css'})); + streamFilter1.end(); + + const data = await pEvent(completeStream, 'data'); + t.is(data.path, 'package.json'); +}); - stream.on('data', file => { - buffer.push(file); - }); +test('filter.restore - end when not using the passthrough option', async t => { + const stream = filter('*.json', {restore: true, passthrough: false}); + const restoreStream = stream.restore; + stream.write(new Vinyl({path: 'package.json'})); + stream.write(new Vinyl({path: 'app.js'})); + stream.write(new Vinyl({path: 'package2.json'})); + stream.end(); - stream.on('end', () => { - assert.equal(buffer.length, 1); - assert.equal(buffer[0].relative, 'included.js'); - cb(); - }); + const data = await pEvent(restoreStream, 'data'); + t.is(data.path, 'app.js'); +}); - // Mimic `gulp.src('test/**/*.js')` - stream.write(new Vinyl({ - base: path.join(__dirname, 'test'), - path: path.join(__dirname, 'test', 'included.js'), - })); +test('filter.restore - not end before the restore stream didn\'t end', async t => { + const stream = filter('*.json', {restore: true}); + const restoreStream = stream.restore; + stream.write(new Vinyl({path: 'package.json'})); + stream.write(new Vinyl({path: 'app.js'})); + stream.end(); - stream.write(new Vinyl({ - base: __dirname, - path: path.join(__dirname, 'ignored.js'), - })); + const data = await pEvent(restoreStream, 'data'); + t.is(data.path, 'app.js'); +}); - stream.end(); - }); +test('filter.restore - pass files as they come', async t => { + const stream = filter('*.json', {restore: true}); + const restoreStream = stream.restore; + stream.pipe(restoreStream); + stream.write(new Vinyl({path: 'package.json'})); + stream.write(new Vinyl({path: 'app.js'})); + stream.write(new Vinyl({path: 'package2.json'})); + stream.write(new Vinyl({path: 'app2.js'})); + stream.end(); + + const data = await pEvent(restoreStream, 'data'); + t.is(data.path, 'package.json'); }); -describe('filter.restore', () => { - it('should bring back the previously filtered files', cb => { - const stream = filter('*.json', {restore: true}); - const buffer = []; - const completeStream = stream.pipe(stream.restore); - const completeBuffer = []; - - stream.on('data', file => { - buffer.push(file); - }); - - completeStream.on('data', file => { - completeBuffer.push(file); - }); - - completeStream.on('end', () => { - assert.equal(buffer.length, 2); - assert.equal(buffer[0].path, 'package.json'); - assert.equal(buffer[1].path, 'package2.json'); - assert.equal(completeBuffer.length, 3); - assert.equal(completeBuffer[0].path, 'package.json'); - assert.equal(completeBuffer[1].path, 'app.js'); - assert.equal(completeBuffer[2].path, 'package2.json'); - cb(); - }); - - stream.write(new Vinyl({path: 'package.json'})); - stream.write(new Vinyl({path: 'app.js'})); - stream.write(new Vinyl({path: 'package2.json'})); - stream.end(); - }); - - it('should work when using multiple filters', cb => { - const streamFilter1 = filter(['*.js'], {restore: true}); - const streamFilter2 = filter(['*.json'], {restore: true}); - const buffer = []; - - const completeStream = streamFilter1 - .pipe(streamFilter2) - .pipe(streamFilter1.restore) - .pipe(streamFilter2.restore); - - completeStream.on('data', file => { - buffer.push(file); - }); - - completeStream.on('end', () => { - assert.equal(buffer.length, 3); - assert.equal(buffer[0].path, 'package.json'); - assert.equal(buffer[1].path, 'app.js'); - assert.equal(buffer[2].path, 'main.css'); - cb(); - }); - - streamFilter1.write(new Vinyl({path: 'package.json'})); - streamFilter1.write(new Vinyl({path: 'app.js'})); - streamFilter1.write(new Vinyl({path: 'main.css'})); - streamFilter1.end(); - }); - - it('should end when not using the passthrough option', cb => { - const stream = filter('*.json', {restore: true, passthrough: false}); - const restoreStream = stream.restore; - const buffer = []; - - restoreStream.on('data', file => { - buffer.push(file); - }); - - restoreStream.on('end', () => { - assert.equal(buffer.length, 1); - assert.equal(buffer[0].path, 'app.js'); - cb(); - }); - - stream.write(new Vinyl({path: 'package.json'})); - stream.write(new Vinyl({path: 'app.js'})); - stream.write(new Vinyl({path: 'package2.json'})); - stream.end(); - }); - - it('should not end before the restore stream didn\'t end', cb => { - const stream = filter('*.json', {restore: true}); - const restoreStream = stream.restore; - const buffer = []; - - restoreStream.on('data', file => { - buffer.push(file); - if (buffer.length === 1) { - setImmediate(() => { - restoreStream.end(); - setImmediate(() => { - stream.write(new Vinyl({path: 'app2.js'})); - stream.end(); - }); - }); - } - }); - - restoreStream.on('end', () => { - assert.equal(buffer.length, 2); - assert.equal(buffer[0].path, 'app.js'); - assert.equal(buffer[1].path, 'app2.js'); - cb(); - }); - - stream.write(new Vinyl({path: 'package.json'})); - stream.write(new Vinyl({path: 'app.js'})); - }); - - it('should pass files as they come', cb => { - const stream = filter('*.json', {restore: true}); - const restoreStream = stream.restore; - const buffer = []; - - restoreStream.on('data', file => { - buffer.push(file); - - if (buffer.length === 4) { - assert.equal(buffer[0].path, 'package.json'); - assert.equal(buffer[1].path, 'app.js'); - assert.equal(buffer[2].path, 'package2.json'); - assert.equal(buffer[3].path, 'app2.js'); - cb(); - } - }); - - restoreStream.on('end', () => { - cb(new Error('Not expected to end!')); - }); - - stream.pipe(restoreStream); - stream.write(new Vinyl({path: 'package.json'})); - stream.write(new Vinyl({path: 'app.js'})); - stream.write(new Vinyl({path: 'package2.json'})); - stream.write(new Vinyl({path: 'app2.js'})); - }); - - it('should work when restore stream is not used', cb => { - const stream = filter('*.json'); - - for (let i = 0; i < stream._writableState.highWaterMark + 1; i++) { - stream.write(new Vinyl({path: 'nonmatch.js'})); - } +test('filter.restore - work when restore stream is not used', async t => { + t.plan(1); - stream.on('finish', cb); - stream.end(); - }); + const stream = filter('*.json'); + for (let index = 0; index < stream._writableState.highWaterMark + 1; index++) { + stream.write(new Vinyl({path: 'nonmatch.js'})); + } + + const finish = pEvent(stream, 'finish'); + stream.end(); + await finish; + t.pass(); }); -// Base directory: /A/B -// Files: -// A /test.js -// B /A/test.js -// C /A/C/test.js -// D /A/B/test.js -// E /A/B/C/test.js - -// Matching behaviour: -// 1) Starting with / - absolute path matching -// 2) Starting with .. - relative path mapping, cwd prepended -// 3) Starting with just path, like abcd/<...> or **/**.js - relative path mapping, cwd prepended -// Same rules for `!` -describe('path matching', () => { +test('path matching', async t => { const testFilesPaths = [ '/test.js', '/A/test.js', @@ -335,7 +183,7 @@ describe('path matching', () => { '/A/B/C/d.js', ]; - const testFiles = testFilesPaths.map(path => new Vinyl({cwd: '/A/B', path})); + const testFiles = testFilesPaths.map(filePath => new Vinyl({cwd: '/A/B', path: filePath})); const testCases = [ { @@ -431,25 +279,16 @@ describe('path matching', () => { ]; for (const testCase of testCases) { - it(`Should ${testCase.description}`, cb => { - const stream = filter(testCase.pattern); - - for (const testFile of testFiles) { - stream.write(testFile); - } + const stream = filter(testCase.pattern); + const promise = Readable.from(stream).toArray(); - const files = []; - - stream.on('data', file => { - files.push(file); - }); + for (const testFile of testFiles) { + stream.write(testFile); + } - stream.on('end', () => { - assert.deepEqual(files.map(file => file.path), testCase.expectedFiles.map(file => file.path)); - cb(); - }); + stream.end(); - stream.end(); - }); + const files = await promise; // eslint-disable-line no-await-in-loop + t.deepEqual(files.map(file => file.path), testCase.expectedFiles.map(file => file.path)); } });