diff --git a/README.md b/README.md index 1cd6f07..e888804 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ arguments so we don't have to care about escaping spaces. It can also return stdout/stderr even when the command fails, or times out. Importantly, it's also not susceptible to max buffer issues. +Published as both ESM and CommonJS, so `import {exec} from 'teen_process'` and +`const {exec} = require('teen_process')` both work. + ## teen_process.exec Examples: diff --git a/lib/exec.ts b/lib/exec.ts index dac2608..717df69 100644 --- a/lib/exec.ts +++ b/lib/exec.ts @@ -1,14 +1,14 @@ import {spawn} from 'node:child_process'; import {quote} from 'shell-quote'; -import {formatEnoent} from './helpers'; -import {CircularBuffer, MAX_BUFFER_SIZE} from './circular-buffer'; +import {formatEnoent} from './helpers.js'; +import {CircularBuffer, MAX_BUFFER_SIZE} from './circular-buffer.js'; import type { TeenProcessExecOptions, TeenProcessExecResult, BufferProp, ExecError, StreamName, -} from './types'; +} from './types.js'; /** * Spawns a child process and collects its output. diff --git a/lib/index.ts b/lib/index.ts index 6b747d0..02a492c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,9 +1,9 @@ export {spawn} from 'node:child_process'; -export {SubProcess} from './subprocess'; -export {exec} from './exec'; +export {SubProcess} from './subprocess.js'; +export {exec} from './exec.js'; export type { TeenProcessExecOptions, TeenProcessExecResult, ExecError, SubProcessOptions, -} from './types'; +} from './types.js'; diff --git a/lib/subprocess.ts b/lib/subprocess.ts index a633053..f56b900 100644 --- a/lib/subprocess.ts +++ b/lib/subprocess.ts @@ -2,10 +2,10 @@ import {spawn} from 'node:child_process'; import type {ChildProcess} from 'node:child_process'; import {EventEmitter} from 'node:events'; import {quote} from 'shell-quote'; -import {formatEnoent} from './helpers'; +import {formatEnoent} from './helpers.js'; import {createInterface} from 'node:readline'; import type {Readable} from 'node:stream'; -import type {SubProcessOptions, StartDetector, TIsBufferOpts, StreamName} from './types'; +import type {SubProcessOptions, StartDetector, TIsBufferOpts, StreamName} from './types.js'; /** * A wrapper around Node's spawn that provides event-driven process management. diff --git a/package.json b/package.json index 96bedb7..8fba9f8 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,39 @@ }, "license": "Apache-2.0", "author": "Appium Contributors", - "main": "build/lib/index.js", - "types": "build/lib/index.d.ts", + "type": "module", + "main": "./build/cjs/lib/index.js", + "module": "./build/esm/lib/index.js", + "types": "./build/cjs/lib/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./build/esm/lib/index.d.ts", + "default": "./build/esm/lib/index.js" + }, + "require": { + "types": "./build/cjs/lib/index.d.ts", + "default": "./build/cjs/lib/index.js" + } + }, + "./package.json": "./package.json" + }, "bin": {}, "directories": { "lib": "lib" }, "files": [ "lib", - "build/lib", + "build/cjs/lib", + "build/cjs/package.json", + "build/esm/lib", + "build/esm/package.json", "CHANGELOG.md" ], "scripts": { - "build": "tsc -b", - "clean": "npm run build -- --clean", + "build": "tsc -b tsconfig.esm.json tsconfig.cjs.json", + "postbuild": "node scripts/postbuild.mjs", + "clean": "node -e \"require('node:fs').rmSync('build', {recursive: true, force: true})\"", "dev": "npm run build -- --watch", "lint": "eslint .", "lint:fix": "npm run lint -- --fix", @@ -42,7 +61,7 @@ "format:check": "prettier --check ./lib ./test", "prepare": "npm run rebuild", "rebuild": "npm run clean && npm run build", - "test": "node --test \"./build/test/**/*.spec.js\"" + "test": "node --test --enable-source-maps --test-timeout=60000 \"./build/esm/test/**/*.spec.js\" \"./test/*.spec.cjs\"" }, "prettier": { "bracketSpacing": false, diff --git a/scripts/postbuild.mjs b/scripts/postbuild.mjs new file mode 100644 index 0000000..1b0f9cf --- /dev/null +++ b/scripts/postbuild.mjs @@ -0,0 +1,9 @@ +import {mkdirSync, writeFileSync} from 'node:fs'; + +for (const [dir, type] of [ + ['build/esm', 'module'], + ['build/cjs', 'commonjs'], +]) { + mkdirSync(dir, {recursive: true}); + writeFileSync(`${dir}/package.json`, `${JSON.stringify({type}, null, 2)}\n`); +} diff --git a/test/circular-buffer.spec.ts b/test/circular-buffer.spec.ts index 4e60e48..91c5bdb 100644 --- a/test/circular-buffer.spec.ts +++ b/test/circular-buffer.spec.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import {CircularBuffer} from '../lib/circular-buffer'; +import {CircularBuffer} from '../lib/circular-buffer.js'; import {describe, it} from 'node:test'; describe('CircularBuffer', function () { diff --git a/test/cjs-interop.spec.cjs b/test/cjs-interop.spec.cjs new file mode 100644 index 0000000..2ada475 --- /dev/null +++ b/test/cjs-interop.spec.cjs @@ -0,0 +1,12 @@ +const assert = require('node:assert/strict'); +const {test} = require('node:test'); +// Requires the package by name (rather than a build path) so this exercises the same +// "exports"/"main" resolution real CommonJS consumers go through. +const teenProcess = require('teen_process'); + +test('require() interop with the CJS build', async () => { + assert.equal(typeof teenProcess.exec, 'function'); + assert.equal(typeof teenProcess.SubProcess, 'function'); + const {code} = await teenProcess.exec('true'); + assert.equal(code, 0); +}); diff --git a/test/exec.spec.ts b/test/exec.spec.ts index d77d74d..18dff59 100644 --- a/test/exec.spec.ts +++ b/test/exec.spec.ts @@ -1,9 +1,12 @@ import assert from 'node:assert/strict'; import path from 'node:path'; -import {exec} from '../lib'; -import {getFixture} from './helpers'; +import {fileURLToPath} from 'node:url'; +import {exec} from '../lib/index.js'; +import {getFixture} from './helpers.js'; import {describe, it, type TestContext} from 'node:test'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + describe('exec', function () { it('should work with arguments like spawn', async function () { const cmd = 'ls'; diff --git a/test/helpers.ts b/test/helpers.ts index ceafb22..61d7a35 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -1,5 +1,6 @@ import path from 'node:path'; import fs from 'node:fs/promises'; +import {fileURLToPath} from 'node:url'; let moduleRoot: string | null = null; @@ -16,7 +17,7 @@ export async function getFixture(fix: string): Promise { } async function getModuleRoot(): Promise { - let currentDir = path.dirname(path.resolve(__filename)); + let currentDir = path.dirname(fileURLToPath(import.meta.url)); let isAtFsRoot = false; while (!isAtFsRoot) { const manifestPath = path.join(currentDir, 'package.json'); diff --git a/test/subproc.spec.ts b/test/subproc.spec.ts index 1fc1185..42bb540 100644 --- a/test/subproc.spec.ts +++ b/test/subproc.spec.ts @@ -1,9 +1,13 @@ import assert from 'node:assert/strict'; import path from 'node:path'; -import {exec, SubProcess} from '../lib'; -import {getFixture} from './helpers'; +import {fileURLToPath} from 'node:url'; +import {exec, SubProcess} from '../lib/index.js'; +import {getFixture} from './helpers.js'; import {describe, it, beforeEach, afterEach} from 'node:test'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + // Windows doesn't understand SIGHUP const stopSignal = process.platform === 'win32' ? 'SIGTERM' : 'SIGHUP'; diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..4dfcb4d --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "build/cjs", + "module": "CommonJS", + "moduleResolution": "Bundler" + }, + "include": ["lib"] +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..30361d7 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "build/esm" + } +} diff --git a/tsconfig.json b/tsconfig.json index 7888b90..08a74be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,5 +9,6 @@ "include": [ "lib", "test" - ] + ], + "exclude": ["test/*.cjs"] }