diff --git a/.eslintrc b/.eslintrc index 230d696..77d0cd8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,20 +6,13 @@ "extends": ["eslint:recommended", "plugin:import/errors", "plugin:import/warnings"], "plugins": ["import"], "parserOptions": { - "ecmaVersion": 11 + "ecmaVersion": 13, + "sourceType": "module" }, "rules": { "eqeqeq": "error", "no-trailing-spaces": "error", "prefer-arrow-callback": "error", "semi": "error" - }, - "overrides": [ - { - "files": ["./**/*.mjs"], - "parserOptions": { - "sourceType": "module" - } - } - ] + } } diff --git a/bin/node-dev b/bin/node-dev deleted file mode 100755 index a239f82..0000000 --- a/bin/node-dev +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env node - -const dev = require('../lib'); -const cli = require('../lib/cli'); - -const { - script, - scriptArgs, - nodeArgs, - opts -} = cli(process.argv); - -dev(script, scriptArgs, nodeArgs, opts); diff --git a/bin/node-dev.js b/bin/node-dev.js new file mode 100755 index 0000000..ac6b462 --- /dev/null +++ b/bin/node-dev.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +import dev from '../lib/index.js'; +import cli from '../lib/cli.js'; + +const { script, scriptArgs, nodeArgs, opts } = cli(process.argv); + +dev(script, scriptArgs, nodeArgs, opts); diff --git a/lib/cfg.js b/lib/cfg.cjs similarity index 100% rename from lib/cfg.js rename to lib/cfg.cjs diff --git a/lib/clear.js b/lib/clear.js index 9b80825..304a991 100644 --- a/lib/clear.js +++ b/lib/clear.js @@ -1,4 +1,2 @@ -const control = '\u001bc'; -const clearFactory = clear => (clear ? () => process.stdout.write(control) : () => {}); - -module.exports = { clearFactory, control }; +export const control = '\u001bc'; +export const clearFactory = clear => (clear ? () => process.stdout.write(control) : () => {}); diff --git a/lib/cli.js b/lib/cli.js index 4edc038..5e30d16 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,8 +1,8 @@ -const assert = require('assert'); -const minimist = require('minimist'); -const { resolve } = require('path'); +import assert from 'assert'; +import minimist from 'minimist'; +import { resolve } from 'path'; -const { getConfig } = require('./cfg'); +import { getConfig } from './cfg.cjs'; const arrayify = v => (Array.isArray(v) ? [...v] : [v]); const argify = key => ({ arg: `--${key}`, key }); @@ -55,7 +55,7 @@ const unknownFactory = args => arg => { key && !nodeDevNumber.includes(key) && args.push({ arg, key }); }; -module.exports = argv => { +export default argv => { const nodeCustomArgs = []; const args = argv.slice(2).filter(nodeCustomFactory(nodeCustomArgs)); diff --git a/lib/hook.js b/lib/hook.js deleted file mode 100644 index a7059dc..0000000 --- a/lib/hook.js +++ /dev/null @@ -1,60 +0,0 @@ -const vm = require('vm'); - -module.exports = (patchVM, callback) => { - // Hook into Node's `require(...)` - updateHooks(); - - // Patch the vm module to watch files executed via one of these methods: - if (patchVM) { - patch(vm, 'createScript', 1); - patch(vm, 'runInThisContext', 1); - patch(vm, 'runInNewContext', 2); - patch(vm, 'runInContext', 2); - } - - /** - * Patch the specified method to watch the file at the given argument - * index. - */ - function patch(obj, method, optionsArgIndex) { - const orig = obj[method]; - if (!orig) return; - obj[method] = function () { - const opts = arguments[optionsArgIndex]; - let file = null; - if (opts) { - file = typeof opts === 'string' ? opts : opts.filename; - } - if (file) callback(file); - return orig.apply(this, arguments); - }; - } - - /** - * (Re-)install hooks for all registered file extensions. - */ - function updateHooks() { - Object.keys(require.extensions).forEach(ext => { - const fn = require.extensions[ext]; - if (typeof fn === 'function' && fn.name !== 'nodeDevHook') { - require.extensions[ext] = createHook(fn); - } - }); - } - - /** - * Returns a function that can be put into `require.extensions` in order to - * invoke the callback when a module is required for the first time. - */ - function createHook(handler) { - return function nodeDevHook(module, filename) { - if (!module.loaded) callback(module.filename); - - // Invoke the original handler - handler(module, filename); - - // Make sure the module did not hijack the handler - updateHooks(); - }; - } -}; diff --git a/lib/ignore.js b/lib/ignore.js index a3e3078..db27d30 100644 --- a/lib/ignore.js +++ b/lib/ignore.js @@ -18,7 +18,5 @@ const getPrefix = mod => { const isPrefixOf = value => prefix => value.startsWith(prefix); -const configureDeps = deps => required => deps !== -1 && getLevel(required) > deps; -const configureIgnore = ignore => required => ignore.some(isPrefixOf(required)); - -module.exports = { configureDeps, configureIgnore }; +export const configureDeps = deps => required => deps !== -1 && getLevel(required) > deps; +export const configureIgnore = ignore => required => ignore.some(isPrefixOf(required)); diff --git a/lib/index.js b/lib/index.js index cc2a7c8..d8b024e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,15 +1,18 @@ -const { fork } = require('child_process'); -const filewatcher = require('filewatcher'); -const semver = require('semver'); -const { pathToFileURL } = require('url'); - -const { clearFactory } = require('./clear'); -const { configureDeps, configureIgnore } = require('./ignore'); -const ipc = require('./ipc'); -const logFactory = require('./log'); -const notifyFactory = require('./notify'); - -module.exports = function ( +import { fork } from 'child_process'; +import filewatcher from 'filewatcher'; +import { createRequire } from 'module'; +import semver from 'semver'; + +import { clearFactory } from './clear.js'; +import { configureDeps, configureIgnore } from './ignore.js'; +import ipc from './ipc.cjs'; +import logFactory from './log.js'; +import notifyFactory from './notify.js'; + +const require = createRequire(import.meta.url); +const resolveHook = hook => require.resolve('./require/' + hook); + +export default function ( script, scriptArgs, nodeArgs, @@ -18,13 +21,15 @@ module.exports = function ( debounce, dedupe, deps, + fork: patchFork, graceful_ipc: gracefulIPC, ignore, interval, notify: notifyEnabled, poll: forcePolling, respawn, - timestamp + timestamp, + vm: patchVm } ) { if (!script) { @@ -52,9 +57,6 @@ module.exports = function ( const isIgnored = configureIgnore(ignore); const isTooDeep = configureDeps(deps); - // Run ./dedupe.js as preload script - if (dedupe) process.env.NODE_DEV_PRELOAD = require.resolve('./dedupe'); - const watcher = filewatcher({ debounce, forcePolling, interval }); let isPaused = false; @@ -89,12 +91,16 @@ module.exports = function ( function start() { isPaused = false; - const args = nodeArgs.slice(); + const execArgv = nodeArgs.slice(); - args.push(`--require=${require.resolve('./wrap')}`); + execArgv.push(`--require=${resolveHook('suppress-experimental-warnings')}`); + if (dedupe) execArgv.push(`--require=${resolveHook('dedupe')}`); + execArgv.push(`--require=${resolveHook('patch')}`); + if (patchFork) execArgv.push(`--require=${resolveHook('patch-fork')}`); + if (patchVm) execArgv.push(`--require=${resolveHook('patch-vm')}`); if (semver.satisfies(process.version, '<12.17.0')) { - args.push('--experimental-modules'); + execArgv.push('--experimental-modules'); } const loaderName = semver.satisfies(process.version, '>=16.12.0') @@ -103,18 +109,18 @@ module.exports = function ( ? 'get-format' : 'resolve'; - const loaderURL = pathToFileURL(require.resolve(`./loaders/${loaderName}.mjs`)); + const loaderURL = new URL(`./loaders/${loaderName}.mjs`, import.meta.url); const experimentalPrefix = semver.satisfies(process.version, '>=12.11.1') ? 'experimental-' : ''; - args.push(`--${experimentalPrefix}loader=${loaderURL.href}`); + execArgv.push(`--${experimentalPrefix}loader=${loaderURL.href}`); child = fork(script, scriptArgs, { cwd: process.cwd(), env: process.env, - execArgv: args + execArgv }); if (respawn) { @@ -167,4 +173,4 @@ module.exports = function ( clearOutput(); start(); -}; +} diff --git a/lib/ipc.js b/lib/ipc.cjs similarity index 100% rename from lib/ipc.js rename to lib/ipc.cjs diff --git a/lib/loaders/get-format.mjs b/lib/loaders/get-format.mjs index 8e72a4a..5940a3c 100644 --- a/lib/loaders/get-format.mjs +++ b/lib/loaders/get-format.mjs @@ -1,6 +1,6 @@ import { createRequire } from 'module'; import { fileURLToPath } from 'url'; -import { send } from './ipc.mjs'; +import { send } from '../ipc.cjs'; const require = createRequire(import.meta.url); diff --git a/lib/loaders/ipc.mjs b/lib/loaders/ipc.mjs deleted file mode 100644 index 61c48c4..0000000 --- a/lib/loaders/ipc.mjs +++ /dev/null @@ -1,5 +0,0 @@ -const cmd = 'NODE_DEV'; - -export const send = m => { - if (process.connected) process.send({ ...m, cmd }); -}; diff --git a/lib/loaders/load.mjs b/lib/loaders/load.mjs index 2772f60..90909d5 100644 --- a/lib/loaders/load.mjs +++ b/lib/loaders/load.mjs @@ -1,6 +1,6 @@ import { createRequire } from 'module'; import { fileURLToPath } from 'url'; -import { send } from './ipc.mjs'; +import { send } from '../ipc.cjs'; const require = createRequire(import.meta.url); diff --git a/lib/loaders/resolve.mjs b/lib/loaders/resolve.mjs index 6c5fe4a..4d85e24 100644 --- a/lib/loaders/resolve.mjs +++ b/lib/loaders/resolve.mjs @@ -1,5 +1,5 @@ import { fileURLToPath } from 'url'; -import { send } from './ipc.mjs'; +import { send } from '../ipc.cjs'; export function resolve(specifier, parentModule, defaultResolve) { const resolved = defaultResolve(specifier, parentModule); diff --git a/lib/log.js b/lib/log.js index 06f1291..2b867af 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,5 +1,5 @@ -const { format } = require('util'); -const dateformat = require('dateformat'); +import { format } from 'util'; +import dateformat from 'dateformat'; const colors = { error: '31;1', @@ -16,7 +16,7 @@ const colorOutput = (s, c) => '\x1B[' + c + 'm' + s + '\x1B[0m'; * Logs a message to the console. The level is displayed in ANSI colors: * errors are bright red, warnings are yellow, and info is cyan. */ -module.exports = ({ noColor, timestamp }) => { +export default ({ noColor, timestamp }) => { const enableColor = !(noColor || !process.stdout.isTTY); const color = enableColor ? colorOutput : noop; diff --git a/lib/notify.js b/lib/notify.js index aadd579..7d525c1 100644 --- a/lib/notify.js +++ b/lib/notify.js @@ -1,9 +1,11 @@ -const notifier = require('node-notifier'); +import notifier from 'node-notifier'; +import { fileURLToPath } from 'url'; -const iconLevelPath = level => require.resolve(`../icons/node_${level}.png`); +const iconLevelPath = level => + fileURLToPath(new URL(`../icons/node_${level}.png`, import.meta.url)); // Writes a message to the console and optionally displays a desktop notification. -module.exports = (notifyEnabled, log) => { +export default (notifyEnabled, log) => { return (title = 'node-dev', message, level = 'info') => { log[level](`${title}: ${message}`); diff --git a/lib/dedupe.js b/lib/require/dedupe.js similarity index 100% rename from lib/dedupe.js rename to lib/require/dedupe.js diff --git a/lib/require/package.json b/lib/require/package.json new file mode 100644 index 0000000..c9a4422 --- /dev/null +++ b/lib/require/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} \ No newline at end of file diff --git a/lib/require/patch-fork.js b/lib/require/patch-fork.js new file mode 100644 index 0000000..b210da2 --- /dev/null +++ b/lib/require/patch-fork.js @@ -0,0 +1,11 @@ +const childProcess = require('child_process'); +const { relay } = require('../ipc.cjs'); + +// Overwrite child_process.fork() so that we can hook into forked processes +// too. We also need to relay messages about required files to the parent. +const { fork } = childProcess; +childProcess.fork = (modulePath, args, options) => { + const child = fork(modulePath, args, options); + relay(child); + return child; +}; diff --git a/lib/require/patch-vm.js b/lib/require/patch-vm.js new file mode 100644 index 0000000..6d0aa93 --- /dev/null +++ b/lib/require/patch-vm.js @@ -0,0 +1,25 @@ +const vm = require('vm'); +const { send } = require('../ipc.cjs'); + +patch(vm, 'createScript', 1); +patch(vm, 'runInThisContext', 1); +patch(vm, 'runInNewContext', 2); +patch(vm, 'runInContext', 2); + +/** + * Patch the specified method to watch the file at the given argument + * index. + */ +function patch(obj, method, optionsArgIndex) { + const orig = obj[method]; + if (!orig) return; + obj[method] = function () { + const opts = arguments[optionsArgIndex]; + let file = null; + if (opts) { + file = typeof opts === 'string' ? opts : opts.filename; + } + if (file) send({ required: file }); + return orig.apply(this, arguments); + }; +} diff --git a/lib/require/patch.js b/lib/require/patch.js new file mode 100644 index 0000000..4d10f6d --- /dev/null +++ b/lib/require/patch.js @@ -0,0 +1,77 @@ +const { Module } = require('module'); +const { dirname, extname } = require('path'); +const { isMainThread } = require('worker_threads'); +const { getConfig } = require('../cfg.cjs'); +const { send } = require('../ipc.cjs'); + +// Error handler that displays a notification and logs the stack to stderr: +process.on('uncaughtException', err => { + // Sometimes uncaught exceptions are not errors + const { message, name, stack } = + err instanceof Error ? err : new Error(`uncaughtException ${err}`); + + console.error(stack); + + // If there's a custom uncaughtException handler expect it to terminate + // the process. + const willTerminate = process.listenerCount('uncaughtException') > 1; + + send({ error: name, message, willTerminate }); +}); + +if (isMainThread) { + // When using worker threads, each thread inherits execArgv and re-evaulates + // --require scripts. Worker threads do not inherit argv, so we copy argv[1] + // to the environment for those processes to use. + process.env.NODE_DEV_SCRIPT = require.resolve(process.argv[1]); +} + +const script = process.env.NODE_DEV_SCRIPT; + +// Check if a module is registered for this extension +const ext = extname(script).slice(1); +const { extensions } = getConfig(script); + +const register = extensions[ext]; + +// Support extensions where 'require' returns a function that accepts options +if (typeof register === 'object' && register.name) { + const fn = require(require.resolve(register.name, { paths: [dirname(script)] })); + if (typeof fn === 'function' && register.options) { + // require returned a function, call it with options + fn(register.options); + } +} else if (typeof register === 'string') { + require(require.resolve(register, { paths: [dirname(script)] })); +} + +// Hook into Node's `require(...)` +updateHooks(); + +/** + * (Re-)install hooks for all registered file extensions. + */ +function updateHooks() { + Object.keys(Module._extensions).forEach(ext => { + const fn = Module._extensions[ext]; + if (typeof fn === 'function' && fn.name !== 'nodeDevHook') { + Module._extensions[ext] = createHook(fn); + } + }); +} + +/** + * Returns a function that can be put into `require.extensions` in order to + * invoke the callback when a module is required for the first time. + */ +function createHook(handler) { + return function nodeDevHook(module, filename) { + if (!module.loaded) send({ required: module.filename }); + + // Invoke the original handler + handler(module, filename); + + // Make sure the module did not hijack the handler + updateHooks(); + }; +} diff --git a/lib/require/suppress-experimental-warnings.js b/lib/require/suppress-experimental-warnings.js new file mode 100644 index 0000000..738893d --- /dev/null +++ b/lib/require/suppress-experimental-warnings.js @@ -0,0 +1,14 @@ +// Source: https://github.com/nodejs/node/issues/30810#issue-533506790 +const { emitWarning } = process; + +process.emitWarning = (warning, ...args) => { + if (args[0] === 'ExperimentalWarning') { + return; + } + + if (args[0] && typeof args[0] === 'object' && args[0].type === 'ExperimentalWarning') { + return; + } + + return emitWarning(warning, ...args); +}; diff --git a/lib/suppress-experimental-warnings.js b/lib/suppress-experimental-warnings.js deleted file mode 100644 index 55be111..0000000 --- a/lib/suppress-experimental-warnings.js +++ /dev/null @@ -1,17 +0,0 @@ -// Source: https://github.com/nodejs/node/issues/30810#issue-533506790 - -module.exports = p => { - const { emitWarning } = p; - - p.emitWarning = (warning, ...args) => { - if (args[0] === 'ExperimentalWarning') { - return; - } - - if (args[0] && typeof args[0] === 'object' && args[0].type === 'ExperimentalWarning') { - return; - } - - return emitWarning(warning, ...args); - }; -}; diff --git a/lib/wrap.js b/lib/wrap.js deleted file mode 100755 index 71d1a63..0000000 --- a/lib/wrap.js +++ /dev/null @@ -1,71 +0,0 @@ -const { dirname, extname } = require('path'); -const childProcess = require('child_process'); -const { isMainThread } = require('worker_threads'); - -const { getConfig } = require('./cfg'); -const hook = require('./hook'); -const { relay, send } = require('./ipc'); -const suppressExperimentalWarnings = require('./suppress-experimental-warnings'); - -// Experimental warnings need to be suppressed in worker threads as well, since -// their process inherits the Node arguments from the main thread. -suppressExperimentalWarnings(process); - -// When using worker threads, each thread appears to require this file through -// the shared Node arguments (--require), so filter them out here and only run -// on the main thread. -if (!isMainThread) return; - -const script = require.resolve(process.argv[1]); -const { extensions, fork, vm } = getConfig(script); - -if (process.env.NODE_DEV_PRELOAD) { - require(process.env.NODE_DEV_PRELOAD); -} - -// We want to exit on SIGTERM, but defer to existing SIGTERM handlers. -process.once('SIGTERM', () => process.listenerCount('SIGTERM') || process.exit(0)); - -if (fork) { - // Overwrite child_process.fork() so that we can hook into forked processes - // too. We also need to relay messages about required files to the parent. - const originalFork = childProcess.fork; - childProcess.fork = (modulePath, args, options) => { - const child = originalFork(modulePath, args, options); - relay(child); - return child; - }; -} - -// Error handler that displays a notification and logs the stack to stderr: -process.on('uncaughtException', err => { - // Sometimes uncaught exceptions are not errors - const { message, name, stack } = - err instanceof Error ? err : new Error(`uncaughtException ${err}`); - - console.error(stack); - - // If there's a custom uncaughtException handler expect it to terminate - // the process. - const willTerminate = process.listenerCount('uncaughtException') > 1; - - send({ error: name, message, willTerminate }); -}); - -// Hook into require() and notify the parent process about required files -hook(vm, required => send({ required })); - -// Check if a module is registered for this extension -const ext = extname(script).slice(1); -const mod = extensions[ext]; - -// Support extensions where 'require' returns a function that accepts options -if (typeof mod === 'object' && mod.name) { - const fn = require(require.resolve(mod.name, { paths: [dirname(script)] })); - if (typeof fn === 'function' && mod.options) { - // require returned a function, call it with options - fn(mod.options); - } -} else if (typeof mod === 'string') { - require(require.resolve(mod, { paths: [dirname(script)] })); -} diff --git a/package.json b/package.json index 1850e03..f174ab0 100644 --- a/package.json +++ b/package.json @@ -19,15 +19,24 @@ "url": "http://github.com/fgnass/node-dev.git" }, "license": "MIT", + "type": "module", "bin": { - "node-dev": "bin/node-dev" + "node-dev": "bin/node-dev.js" }, "main": "./lib", + "exports": { + ".": "./lib/index.js", + "./package.json": "./package.json" + }, + "files": [ + "icons/", + "lib/" + ], "engines": { - "node": ">=12" + "node": ">=12.17" }, "scripts": { - "lint": "eslint lib test bin/node-dev", + "lint": "eslint lib test bin", "test": "node test", "prepare": "husky install" }, @@ -54,7 +63,7 @@ "typescript": "^4.6.3" }, "lint-staged": { - "*.{js,mjs}": "eslint --cache --fix", - "*.{js,md}": "prettier --write" + "*.{js,cjs,mjs}": "eslint --cache --fix", + "*.{js,cjs,mjs,md}": "prettier --write" } } \ No newline at end of file diff --git a/test/cli.js b/test/cli.js index 64bfa4d..a708151 100644 --- a/test/cli.js +++ b/test/cli.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const cli = require('../lib/cli.js'); +import cli from '../lib/cli.js'; tap.test('notify is enabled by default', t => { const { diff --git a/test/fixture/package.json b/test/fixture/package.json new file mode 100644 index 0000000..c9a4422 --- /dev/null +++ b/test/fixture/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} \ No newline at end of file diff --git a/test/index.js b/test/index.js index d34e931..f2daba2 100644 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -require('./cli'); -require('./log'); -require('./run'); -require('./spawn'); +import './cli.js'; +import './log.js'; +import './run.js'; +import './spawn/index.js'; diff --git a/test/log.js b/test/log.js index 8fc1c4c..abdc484 100644 --- a/test/log.js +++ b/test/log.js @@ -1,7 +1,7 @@ -const tap = require('tap'); +import tap from 'tap'; -const { defaultConfig } = require('../lib/cfg'); -const logFactory = require('../lib/log'); +import { defaultConfig } from '../lib/cfg.cjs'; +import logFactory from '../lib/log.js'; const noColorCfg = { ...defaultConfig, noColor: true }; diff --git a/test/run.js b/test/run.js index b41d5ca..bbe94d1 100644 --- a/test/run.js +++ b/test/run.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('./utils'); +import { spawn, touchFile } from './utils.js'; const run = (cmd, exit) => { return spawn(cmd, out => { diff --git a/test/spawn/argv.js b/test/spawn/argv.js index e534e38..87a5086 100644 --- a/test/spawn/argv.js +++ b/test/spawn/argv.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should not show up in argv', t => { spawn('argv.js foo', out => { diff --git a/test/spawn/caught.js b/test/spawn/caught.js index 5226fde..d55b378 100644 --- a/test/spawn/caught.js +++ b/test/spawn/caught.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should ignore caught errors', t => { spawn('catch-no-such-module.js', out => { diff --git a/test/spawn/clear.js b/test/spawn/clear.js index 718d9fa..4222723 100644 --- a/test/spawn/clear.js +++ b/test/spawn/clear.js @@ -1,8 +1,8 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; -const { control } = require('../../lib/clear'); +import { control } from '../../lib/clear.js'; const reClear = new RegExp(control); diff --git a/test/spawn/cli-require.js b/test/spawn/cli-require.js index ac84963..d2375ad 100644 --- a/test/spawn/cli-require.js +++ b/test/spawn/cli-require.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('Supports require from the command-line (ts-node/register)', t => { spawn('--require=ts-node/register typescript/index.ts', out => { diff --git a/test/spawn/cluster.js b/test/spawn/cluster.js index c616a19..bcc9b31 100644 --- a/test/spawn/cluster.js +++ b/test/spawn/cluster.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('Restart the cluster', t => { if (process.platform === 'win32') { diff --git a/test/spawn/conceal.js b/test/spawn/conceal.js index afd2c89..0686a59 100644 --- a/test/spawn/conceal.js +++ b/test/spawn/conceal.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should conceal the wrapper', t => { // require.main should be main.js not wrap.js! diff --git a/test/spawn/errors.js b/test/spawn/errors.js index 1c4d47f..6861736 100644 --- a/test/spawn/errors.js +++ b/test/spawn/errors.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('should handle errors', t => { spawn('error.js', out => { diff --git a/test/spawn/esmodule.js b/test/spawn/esmodule.js index 1b78287..b38b8a2 100644 --- a/test/spawn/esmodule.js +++ b/test/spawn/esmodule.js @@ -1,7 +1,7 @@ -const semver = require('semver'); -const tap = require('tap'); +import semver from 'semver'; +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('Supports ECMAScript modules with experimental-specifier-resolution', t => { if (semver.satisfies(process.version, '<12.17')) diff --git a/test/spawn/exit-code.js b/test/spawn/exit-code.js index b399c0e..c89b607 100644 --- a/test/spawn/exit-code.js +++ b/test/spawn/exit-code.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should pass through the exit code', t => { spawn('exit.js').on('exit', code => { diff --git a/test/spawn/expose-gc.js b/test/spawn/expose-gc.js index ccecf8d..317f3a4 100644 --- a/test/spawn/expose-gc.js +++ b/test/spawn/expose-gc.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should pass unknown args to node binary', t => { spawn('--expose_gc gc.js foo', out => { diff --git a/test/spawn/extension-options.js b/test/spawn/extension-options.js index 49cd5d2..36a35b1 100644 --- a/test/spawn/extension-options.js +++ b/test/spawn/extension-options.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should pass options to extensions according to .node-dev.json', t => { spawn('extension-options', out => { diff --git a/test/spawn/graceful-ipc.js b/test/spawn/graceful-ipc.js index d8e9d4d..0aa06de 100644 --- a/test/spawn/graceful-ipc.js +++ b/test/spawn/graceful-ipc.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('should send IPC message when configured', t => { spawn('--graceful_ipc=node-dev:restart ipc-server.js', out => { diff --git a/test/spawn/index.js b/test/spawn/index.js index 82c7bd1..94443e8 100644 --- a/test/spawn/index.js +++ b/test/spawn/index.js @@ -1,23 +1,23 @@ -require('./argv'); -require('./caught'); -require('./clear'); -require('./cli-require'); -require('./cluster'); -require('./conceal'); -require('./errors'); -require('./esmodule'); -require('./exit-code'); -require('./expose-gc'); -require('./extension-options'); -require('./graceful-ipc'); -require('./inspect'); -require('./kill-fork'); -require('./no-such-module'); -require('./node-env'); -require('./relay-stdin'); -require('./require-extensions'); -require('./restart-twice'); -require('./sigterm'); -require('./timestamps'); -require('./typescript'); -require('./uncaught'); +import './argv.js'; +import './caught.js'; +import './clear.js'; +import './cli-require.js'; +import './cluster.js'; +import './conceal.js'; +import './errors.js'; +import './esmodule.js'; +import './exit-code.js'; +import './expose-gc.js'; +import './extension-options.js'; +import './graceful-ipc.js'; +import './inspect.js'; +import './kill-fork.js'; +import './no-such-module.js'; +import './node-env.js'; +import './relay-stdin.js'; +import './require-extensions.js'; +import './restart-twice.js'; +import './sigterm.js'; +import './timestamps.js'; +import './typescript.js'; +import './uncaught.js'; diff --git a/test/spawn/inspect.js b/test/spawn/inspect.js index 8a7b587..fc194c4 100644 --- a/test/spawn/inspect.js +++ b/test/spawn/inspect.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('Supports --inspect', t => { spawn('--inspect server.js', out => { diff --git a/test/spawn/kill-fork.js b/test/spawn/kill-fork.js index aa7825a..a836136 100644 --- a/test/spawn/kill-fork.js +++ b/test/spawn/kill-fork.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should kill the forked processes', t => { spawn('pid.js', out => { diff --git a/test/spawn/no-such-module.js b/test/spawn/no-such-module.js index e17bb75..fa29641 100644 --- a/test/spawn/no-such-module.js +++ b/test/spawn/no-such-module.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should watch if no such module', t => { let passed = false; diff --git a/test/spawn/node-env.js b/test/spawn/node-env.js index 33e6f54..13d81b5 100644 --- a/test/spawn/node-env.js +++ b/test/spawn/node-env.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should *not* set NODE_ENV', t => { spawn('env.js', out => { diff --git a/test/spawn/relay-stdin.js b/test/spawn/relay-stdin.js index 4a6aba3..3ddaf80 100644 --- a/test/spawn/relay-stdin.js +++ b/test/spawn/relay-stdin.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should relay stdin', t => { spawn('echo.js', out => { diff --git a/test/spawn/require-extensions.js b/test/spawn/require-extensions.js index d344435..2294de3 100644 --- a/test/spawn/require-extensions.js +++ b/test/spawn/require-extensions.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should be resistant to breaking `require.extensions`', t => { spawn('modify-extensions.js', out => { diff --git a/test/spawn/restart-twice.js b/test/spawn/restart-twice.js index c87d67d..d1f1510 100644 --- a/test/spawn/restart-twice.js +++ b/test/spawn/restart-twice.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('should restart the server twice', t => { spawn('server.js', out => { diff --git a/test/spawn/sigterm.js b/test/spawn/sigterm.js index ed229d8..5025cd5 100644 --- a/test/spawn/sigterm.js +++ b/test/spawn/sigterm.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('should allow graceful shutdowns', t => { if (process.platform === 'win32') { diff --git a/test/spawn/timestamps.js b/test/spawn/timestamps.js index 8105693..f58beb4 100644 --- a/test/spawn/timestamps.js +++ b/test/spawn/timestamps.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('Logs timestamp by default', t => { spawn('server.js', out => { diff --git a/test/spawn/typescript.js b/test/spawn/typescript.js index 49238da..03e8e53 100644 --- a/test/spawn/typescript.js +++ b/test/spawn/typescript.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn, touchFile } = require('../utils'); +import { spawn, touchFile } from '../utils.js'; tap.test('Uses ts-node/register for .ts files through config file (also the default)', t => { spawn('typescript/index.ts', out => { diff --git a/test/spawn/uncaught.js b/test/spawn/uncaught.js index 0930a57..ad7fdc9 100644 --- a/test/spawn/uncaught.js +++ b/test/spawn/uncaught.js @@ -1,6 +1,6 @@ -const tap = require('tap'); +import tap from 'tap'; -const { spawn } = require('../utils'); +import { spawn } from '../utils.js'; tap.test('should run async code uncaughtException handlers', t => { spawn('uncaught-exception-handler.js', out => { diff --git a/test/utils.js b/test/utils.js index b16e843..ae7dc05 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,18 +1,21 @@ -const { spawn } = require('child_process'); -const { join } = require('path'); -const touch = require('touch'); +import { spawn as _spawn } from 'child_process'; +import { join } from 'path'; +import touch from 'touch'; +import { fileURLToPath } from 'url'; -const { control } = require('../lib/clear'); +import { control } from '../lib/clear.js'; -const bin = join(__dirname, '..', 'bin', 'node-dev'); -const dir = join(__dirname, 'fixture'); +const bin = fileURLToPath(new URL('../bin/node-dev.js', import.meta.url)); +const dir = fileURLToPath(new URL('fixture', import.meta.url)); const reClear = new RegExp(control); -const noop = () => {/**/}; +const noop = () => { + /**/ +}; -exports.spawn = (cmd, cb = noop) => { - const ps = spawn('node', [bin].concat(cmd.split(' ')), { cwd: dir }); +export function spawn(cmd, cb = noop) { + const ps = _spawn('node', [bin].concat(cmd.split(' ')), { cwd: dir }); let err = ''; function errorHandler(data) { @@ -50,12 +53,12 @@ exports.spawn = (cmd, cb = noop) => { ps.stdout.on('data', outHandler); return ps; -}; +} // filewatcher requires a new mtime to trigger a change event // but most file systems only have second precision, so wait // one full second before touching. -exports.touchFile = (...filepath) => { +export function touchFile(...filepath) { setTimeout(() => touch(join(dir, ...filepath)), 1000); -}; +}