From 7c0f52fef23b87c82e4c6592b46f9221907f8637 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 12 Aug 2021 14:02:41 +0200 Subject: [PATCH] Require Node.js 12.20 and move to ESM --- .github/funding.yml | 3 - .github/workflows/main.yml | 6 +- csv-headers.js | 20 ++-- index.js | 51 +++++---- package.json | 16 +-- readme.md | 209 +++++++++++++++++-------------------- test/global.test.js | 22 ++-- test/promise.test.js | 4 +- test/stream.test.js | 42 ++++---- transform.js | 28 ++--- 10 files changed, 187 insertions(+), 214 deletions(-) delete mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 1a630e9..0000000 --- a/.github/funding.yml +++ /dev/null @@ -1,3 +0,0 @@ -github: sindresorhus -open_collective: sindresorhus -custom: https://sindresorhus.com/donate diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0dc7dc..e6f8365 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,12 +10,10 @@ jobs: fail-fast: false matrix: node-version: - - 14 - - 12 - - 10 + - 16 steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/csv-headers.js b/csv-headers.js index 976f351..37a3e6e 100644 --- a/csv-headers.js +++ b/csv-headers.js @@ -1,12 +1,10 @@ -'use strict'; - -module.exports = { +const csvHeaders = { default: [ 'imageName', 'pid', 'sessionName', 'sessionNumber', - 'memUsage' + 'memUsage', ], defaultVerbose: [ 'imageName', @@ -17,13 +15,13 @@ module.exports = { 'status', 'username', 'cpuTime', - 'windowTitle' + 'windowTitle', ], apps: [ 'imageName', 'pid', 'memUsage', - 'packageName' + 'packageName', ], appsVerbose: [ 'imageName', @@ -35,16 +33,18 @@ module.exports = { 'username', 'cpuTime', 'windowTitle', - 'packageName' + 'packageName', ], modules: [ 'imageName', 'pid', - 'modules' + 'modules', ], services: [ 'imageName', 'pid', - 'services' - ] + 'services', + ], }; + +export default csvHeaders; diff --git a/index.js b/index.js index 5762ca6..9793c20 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,10 @@ -'use strict'; -const childProcess = require('child_process'); -const {promisify} = require('util'); -const {pipeline} = require('stream'); -const csvHeaders = require('./csv-headers'); -const transform = require('./transform'); -const csv = require('csv'); +import childProcess from 'node:child_process'; +import {promisify} from 'node:util'; +import {pipeline} from 'node:stream'; +import process from 'node:process'; +import csv from 'csv'; +import csvHeaders from './csv-headers.js'; +import transform from './transform.js'; const execFile = promisify(childProcess.execFile); const parse = promisify(csv.parse); @@ -23,12 +23,12 @@ function main(options = {}) { } // Check if system, username and password is specified together - const remoteParams = [options.system, options.username, options.password]; + const remoteParameters = [options.system, options.username, options.password]; let isRemote; - if (remoteParams.every(value => value === undefined)) { + if (remoteParameters.every(value => value === undefined)) { // All params are undefined isRemote = false; - } else if (remoteParams.some(value => value === undefined)) { + } else if (remoteParameters.includes(undefined)) { // Some, but not all of the params are undefined throw new Error('The System, Username and Password options must be specified together'); } else { @@ -37,12 +37,12 @@ function main(options = {}) { // Check for unsupported filters on remote machines if (Array.isArray(options.filter) && isRemote) { - options.filter.forEach(filter => { + for (const filter of options.filter) { const parameter = filter.split(' ')[0].toLowerCase(); if (parameter === 'windowtitle' || parameter === 'status') { throw new Error('Windowtitle and Status parameters for filtering are not supported when querying remote machines'); } - }); + } } // Populate args @@ -71,7 +71,7 @@ function main(options = {}) { args.push( '/s', options.system, '/u', options.username, - '/p', options.password + '/p', options.password, ); } @@ -101,18 +101,7 @@ function main(options = {}) { return {args, columns, currentTransform}; } -function streamInterface(options = {}) { - const {args, columns, currentTransform} = main(options); - const checkEmptyStream = new transform.ReportEmpty().getTransform(); - const processOutput = childProcess.spawn('tasklist.exe', args).stdout; - - // Ignore errors originating from stream end - const resultStream = pipeline(processOutput, checkEmptyStream, csv.parse({columns}), transform.makeTransform(currentTransform), error => error); - resultStream.on('error', error => error); - return resultStream; -} - -async function promiseInterface(options = {}) { +export async function tasklist(options = {}) { const {args, columns, currentTransform} = main(options); const {stdout} = await execFile('tasklist.exe', args); if (!stdout.startsWith('"')) { @@ -124,5 +113,13 @@ async function promiseInterface(options = {}) { return records; } -module.exports = promiseInterface; -module.exports.stream = streamInterface; +export function tasklistStream(options = {}) { + const {args, columns, currentTransform} = main(options); + const checkEmptyStream = new transform.ReportEmpty().getTransform(); + const processOutput = childProcess.spawn('tasklist.exe', args).stdout; + + // Ignore errors originating from stream end + const resultStream = pipeline(processOutput, checkEmptyStream, csv.parse({columns}), transform.makeTransform(currentTransform), error => error); + resultStream.on('error', error => error); + return resultStream; +} diff --git a/package.json b/package.json index 6755e7a..f64cb34 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, + "type": "module", + "exports": "./index.js", "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "scripts": { "test": "xo && ava" @@ -35,14 +37,14 @@ "services" ], "dependencies": { - "csv": "^5.1.1", - "sec": "^1.0.0" + "csv": "^5.5.0", + "sec": "^2.0.0" }, "devDependencies": { - "@types/node": "^13.7.6", - "ava": "^2.1.0", - "get-stream": "^5.1.0", - "xo": "^0.27.2" + "@types/node": "^16.6.0", + "ava": "^3.15.0", + "get-stream": "^6.0.1", + "xo": "^0.44.0" }, "ava": { "files": [ diff --git a/readme.md b/readme.md index 671084f..c2bbcf5 100644 --- a/readme.md +++ b/readme.md @@ -11,24 +11,23 @@ $ npm install tasklist ``` ## Usage + ```js -const tasklist = require('tasklist'); - -(async () => { - console.log(await tasklist()); - /* - [ - { - imageName: 'taskhostex.exe', - pid: 1820, - sessionName: 'Console', - sessionNumber: 1, - memUsage: 4415488 - }, - … - ] - */ -})(); +import {tasklist} from 'tasklist'; + +console.log(await tasklist()); +/* +[ + { + imageName: 'taskhostex.exe', + pid: 1820, + sessionName: 'Console', + sessionNumber: 1, + memUsage: 4415488 + }, + … +] +*/ ``` ## API @@ -41,18 +40,16 @@ Returns a `Promise` that contains the normalized results of the comman Examples for `options` below will use this interface, but you can check `tasklist.stream` below for usage of the stream interface. -### tasklist.stream(options?) +### tasklistStream(options?) Returns a `stream.Readable` that returns the resulting lines, normalized, one-by-one. Options are the same as the Promise interface. -**Example using stream interface** - ```js -const tasklist = require('tasklist'); +import {tasklistStream} from 'tasklist'; -tasklist.stream({verbose: true}).pipe(process.stdout); +tasklistStream({verbose: true}).pipe(process.stdout); /* { imageName: 'taskhostex.exe', @@ -74,10 +71,10 @@ tasklist.stream({verbose: true}).pipe(process.stdout); Type: `object` **Warning** -The `system`, `username`, `password` options must be specified together. -The `modules` and `services` options can't be specified if verbose is set to `true`. -The `modules` and `services` options can't be specified at the same time. -When `system`, `username`, `password` options are specified, the filter option can't have `windowtitle` and `status` as the parameter. +- The `system`, `username`, `password` options must be specified together. +- The `modules` and `services` options can't be specified if verbose is set to `true`. +- The `modules` and `services` options can't be specified at the same time. +- When `system`, `username`, `password` options are specified, the filter option can't have `windowtitle` and `status` as the parameter. ##### verbose @@ -106,27 +103,25 @@ With the `verbose` option set to `true` but the `apps` option still set to `fals **Verbose example:** ```js -const tasklist = require('tasklist'); - -(async () => { - console.log(await tasklist({verbose: true})); - /* - [ - { - imageName: 'taskhostex.exe', - pid: 1820, - sessionName: 'Console', - sessionNumber: 1, - memUsage: 4415488, - status: 'Running', - username: 'SINDRESORHU3930\\sindre' - cpuTime: 0, - windowTitle: 'Task Host Window' - }, - … - ] - */ -})(); +import {tasklist} from 'tasklist'; + +console.log(await tasklist({verbose: true})); +/* +[ + { + imageName: 'taskhostex.exe', + pid: 1820, + sessionName: 'Console', + sessionNumber: 1, + memUsage: 4415488, + status: 'Running', + username: 'SINDRESORHU3930\\sindre' + cpuTime: 0, + windowTitle: 'Task Host Window' + }, + … +] +*/ ``` **Warning:** Using the `verbose` option may have a considerable performance impact (See: [#6](https://github.com/sindresorhus/tasklist/issues/6)). @@ -167,25 +162,21 @@ Without the `verbose` option, the command returns the following data: - `memUsage` in bytes (Type: `number`) - `packageName` (Type: `string`) -**Example:** - ```js -const tasklist = require('tasklist'); - -(async () => { - console.log(await tasklist({apps: true})); - /* - [ - { - imageName: 'SearchUI.exe (CortanaUI)', - pid: 1820, - memUsage: 4415488, - packageName: 'Microsoft.Windows.Cortana' - }, - … - ] - */ -})(); +import {tasklist} from 'tasklist'; + +console.log(await tasklist({apps: true})); +/* +[ + { + imageName: 'SearchUI.exe (CortanaUI)', + pid: 1820, + memUsage: 4415488, + packageName: 'Microsoft.Windows.Cortana' + }, + … +] +*/ ``` With the `verbose` option set to `true`, the command additionally returns the following data: @@ -201,28 +192,26 @@ With the `verbose` option set to `true`, the command additionally returns the fo **Verbose example:** ```js -const tasklist = require('tasklist'); - -(async () => { - console.log(await tasklist({apps: true, verbose: true})); - /* - [ - { - imageName: 'SearchUI.exe (CortanaUI)', - pid: 1820, - sessionName: 'Console', - sessionNumber: 1, - memUsage: 4415488, - status: 'Running', - username: 'SINDRESORHU3930\\sindre' - cpuTime: 0, - windowTitle: 'N/A', - packageName: 'Microsoft.Windows.Cortana' - }, - … - ] - */ -})(); +import {tasklist} from 'tasklist'; + +console.log(await tasklist({apps: true, verbose: true})); +/* +[ + { + imageName: 'SearchUI.exe (CortanaUI)', + pid: 1820, + sessionName: 'Console', + sessionNumber: 1, + memUsage: 4415488, + status: 'Running', + username: 'SINDRESORHU3930\\sindre' + cpuTime: 0, + windowTitle: 'N/A', + packageName: 'Microsoft.Windows.Cortana' + }, + … +] +*/ ``` ##### modules @@ -233,21 +222,17 @@ List all tasks using the given DLL module name. If an empty string is given, it **Note:** You can't use the `verbose` option with this option set. -**Example:** - ```js -const tasklist = require('tasklist'); +import {tasklist} from 'tasklist'; -(async () => { - console.log(await tasklist({modules: 'wmiutils.dll'})); - /* - [{ - imageName: 'chrome.exe', - pid: 1820, - modules: ['wmiutils.dll'] - }, …] - */ -})(); +console.log(await tasklist({modules: 'wmiutils.dll'})); +/* +[{ + imageName: 'chrome.exe', + pid: 1820, + modules: ['wmiutils.dll'] +}, …] +*/ ``` ##### services @@ -257,21 +242,17 @@ Type: `boolean` Displays services hosted in each process. **Note:** You can't use the `verbose` option with this option set. -**Example:** - ```js -const tasklist = require('tasklist'); - -(async () => { - console.log(await tasklist({services: true})); - /* - [{ - imageName: 'lsass.exe', - pid: 856, - services: ['KeyIso', 'SamSs', 'VaultSvc'] - }, …] - */ -})(); +import {tasklist} from 'tasklist'; + +console.log(await tasklist({services: true})); +/* +[{ + imageName: 'lsass.exe', + pid: 856, + services: ['KeyIso', 'SamSs', 'VaultSvc'] +}, …] +*/ ``` ## Related diff --git a/test/global.test.js b/test/global.test.js index fec744f..434858f 100644 --- a/test/global.test.js +++ b/test/global.test.js @@ -1,5 +1,5 @@ import test from 'ava'; -import tasklist from '..'; +import {tasklist} from '../index.js'; test('reject windowtitle and status parameter filter for remote machine', async t => { await t.throwsAsync( @@ -7,8 +7,8 @@ test('reject windowtitle and status parameter filter for remote machine', async system: 'test', username: 'test', password: 'test', - filter: ['Windowtitle eq asd'] - }) + filter: ['Windowtitle eq asd'], + }), ); }); @@ -16,8 +16,8 @@ test('reject /svc with /m flag', async t => { await t.throwsAsync( tasklist({ services: true, - modules: '' - }) + modules: '', + }), ); }); @@ -25,8 +25,8 @@ test('reject verbose with /svc flag', async t => { await t.throwsAsync( tasklist({ verbose: true, - services: true - }) + services: true, + }), ); }); @@ -34,15 +34,15 @@ test('reject verbose with /m flag', async t => { await t.throwsAsync( tasklist({ verbose: true, - modules: '' - }) + modules: '', + }), ); }); test('reject system without username and password', async t => { await t.throwsAsync( tasklist({ - system: '192.168.1.1' - }) + system: '192.168.1.1', + }), ); }); diff --git a/test/promise.test.js b/test/promise.test.js index 5386ddb..f30dbcf 100644 --- a/test/promise.test.js +++ b/test/promise.test.js @@ -1,5 +1,5 @@ import test from 'ava'; -import tasklist from '..'; +import {tasklist} from '../index.js'; const hasDefaultTaskProps = (t, task) => { t.is(typeof task.imageName, 'string'); @@ -120,7 +120,7 @@ test('services', async t => { test('test handle no matching tasks gracefully', async t => { const tasks = await tasklist({ - filter: ['imagename eq does-not-exist'] + filter: ['imagename eq does-not-exist'], }); t.is(tasks.length, 0); }); diff --git a/test/stream.test.js b/test/stream.test.js index d4da831..1073c9c 100644 --- a/test/stream.test.js +++ b/test/stream.test.js @@ -1,6 +1,6 @@ import test from 'ava'; import getStream from 'get-stream'; -import tasklist from '..'; +import {tasklistStream} from '../index.js'; const hasDefaultTaskProps = (t, task) => { t.is(typeof task.imageName, 'string'); @@ -61,28 +61,24 @@ const hasServicesProps = (t, task) => { t.true(Array.isArray(task.services)); }; -const _call = opts => { - return new Promise((resolve, reject) => { - try { - const apiStream = tasklist.stream(opts); - resolve(getStream.array(apiStream)); - } catch (error) { - reject(error); - } - }); -}; +const _call = options => new Promise((resolve, reject) => { + try { + const apiStream = tasklistStream(options); + resolve(getStream.array(apiStream)); + } catch (error) { + reject(error); + } +}); -const _callAndClose = opts => { - return new Promise((resolve, reject) => { - try { - const apiStream = tasklist.stream(opts); - apiStream.on('data', () => apiStream.end()); - apiStream.on('end', () => resolve()); - } catch (error) { - reject(error); - } - }); -}; +const _callAndClose = options => new Promise((resolve, reject) => { + try { + const apiStream = tasklistStream(options); + apiStream.on('data', () => apiStream.end()); + apiStream.on('end', () => resolve()); + } catch (error) { + reject(error); + } +}); const macro = async (t, options) => { const tasks = await _call(options); @@ -144,7 +140,7 @@ test('services', async t => { test('test handle no matching tasks gracefully', async t => { const tasks = await _call({ - filter: ['imagename eq does-not-exist'] + filter: ['imagename eq does-not-exist'], }); t.is(tasks.length, 0); }); diff --git a/transform.js b/transform.js index ba0ecda..3282f3d 100644 --- a/transform.js +++ b/transform.js @@ -1,30 +1,30 @@ -const {PassThrough, Transform} = require('stream'); -const sec = require('sec'); +import {PassThrough as PassThroughStream, Transform as TransformStream} from 'node:stream'; +import sec from 'sec'; -const makeTransform = convert => new Transform({ +const makeTransform = convert => new TransformStream({ objectMode: true, transform: (task, _, callback) => - callback(null, convert(task)) + callback(null, convert(task)), }); const defaultTransform = task => { task.pid = Number(task.pid); task.sessionNumber = Number(task.sessionNumber); - task.memUsage = Number(task.memUsage.replace(/[^\d]/g, '')) * 1024; + task.memUsage = Number(task.memUsage.replace(/\D/g, '')) * 1024; return task; }; const defaultVerboseTransform = task => { task.pid = Number(task.pid); task.sessionNumber = Number(task.sessionNumber); - task.memUsage = Number(task.memUsage.replace(/[^\d]/g, '')) * 1024; + task.memUsage = Number(task.memUsage.replace(/\D/g, '')) * 1024; task.cpuTime = sec(task.cpuTime); return task; }; const appsTransform = task => { task.pid = Number(task.pid); - task.memUsage = Number(task.memUsage.replace(/[^\d]/g, '')) * 1024; + task.memUsage = Number(task.memUsage.replace(/\D/g, '')) * 1024; return task; }; @@ -43,7 +43,7 @@ const servicesTransform = task => { return task; }; -const passThrough = () => new PassThrough({objectMode: true}); +const passThrough = () => new PassThroughStream({objectMode: true}); class ReportEmpty { constructor() { @@ -51,7 +51,7 @@ class ReportEmpty { } getTransform() { - return new Transform({ + return new TransformStream({ transform: (input, _, callback) => { const stringInput = input.toString(); if (!stringInput.startsWith('"') && !this.checked) { @@ -60,12 +60,12 @@ class ReportEmpty { callback(null, input); this.checked = true; } - } + }, }); } } -module.exports = { +const transform = { passThrough, ReportEmpty, makeTransform, @@ -75,6 +75,8 @@ module.exports = { apps: appsTransform, appsVerbose: defaultVerboseTransform, modules: modulesTransform, - services: servicesTransform - } + services: servicesTransform, + }, }; + +export default transform;