diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 58858535..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,28 +0,0 @@ - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index af3da470..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/LICENSE b/LICENSE index 658c933d..1a9820e2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,20 @@ (The MIT License) -Copyright (c) 2014 TJ Holowaychuk +Copyright (c) 2014-2017 TJ Holowaychuk +Copyright (c) 2018-2021 Josh Junon -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the 'Software'), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the 'Software'), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 88dae35d..9ebdfbf1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # debug -[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors) @@ -241,6 +241,9 @@ setInterval(function(){ }, 1200); ``` +In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will—by default—only show messages logged by `debug` if the "Verbose" log level is _enabled_. + + ## Output streams @@ -351,12 +354,34 @@ if (debug.enabled) { You can also manually toggle this property to force the debug instance to be enabled or disabled. +## Usage in child processes + +Due to the way `debug` detects if the output is a TTY or not, colors are not shown in child processes when `stderr` is piped. A solution is to pass the `DEBUG_COLORS=1` environment variable to the child process. +For example: + +```javascript +worker = fork(WORKER_WRAP_PATH, [workerPath], { + stdio: [ + /* stdin: */ 0, + /* stdout: */ 'pipe', + /* stderr: */ 'pipe', + 'ipc', + ], + env: Object.assign({}, process.env, { + DEBUG_COLORS: 1 // without this settings, colors won't be shown + }), +}); + +worker.stderr.pipe(process.stderr, { end: false }); +``` + ## Authors - TJ Holowaychuk - Nathan Rajlich - Andrew Rhyne + - Josh Junon ## Backers @@ -434,6 +459,7 @@ Become a sponsor and get your logo on our README on Github with a link to your s (The MIT License) Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca> +Copyright (c) 2018-2021 Josh Junon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/package.json b/package.json index 85b6f1d6..ee8abb52 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "debug", - "version": "4.1.0", + "version": "4.4.3", "repository": { "type": "git", - "url": "git://github.com/visionmedia/debug.git" + "url": "git://github.com/debug-js/debug.git" }, - "description": "small debugging utility", + "description": "Lightweight debugging utility for Node.js and the browser", "keywords": [ "debug", "log", @@ -16,39 +16,49 @@ "LICENSE", "README.md" ], - "author": "TJ Holowaychuk ", + "author": "Josh Junon (https://github.com/qix-)", "contributors": [ + "TJ Holowaychuk ", "Nathan Rajlich (http://n8.io)", - "Andrew Rhyne ", - "Josh Junon " + "Andrew Rhyne " ], "license": "MIT", "scripts": { "lint": "xo", "test": "npm run test:node && npm run test:browser && npm run lint", - "test:node": "istanbul cover _mocha -- test.js", + "test:node": "mocha test.js test.node.js", "test:browser": "karma start --single-run", "test:coverage": "cat ./coverage/lcov.info | coveralls" }, "dependencies": { - "ms": "^2.1.1" + "ms": "^2.1.3" }, "devDependencies": { "brfs": "^2.0.1", "browserify": "^16.2.3", "coveralls": "^3.0.2", - "istanbul": "^0.4.5", "karma": "^3.1.4", "karma-browserify": "^6.0.0", "karma-chrome-launcher": "^2.2.0", "karma-mocha": "^1.3.0", "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", + "sinon": "^14.0.0", "xo": "^0.23.0" }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + }, "main": "./src/index.js", "browser": "./src/browser.js", "engines": { "node": ">=6.0" + }, + "xo": { + "rules": { + "import/extensions": "off" + } } } diff --git a/src/browser.js b/src/browser.js index ac3f7e13..5993451b 100644 --- a/src/browser.js +++ b/src/browser.js @@ -9,6 +9,16 @@ exports.save = save; exports.load = load; exports.useColors = useColors; exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; + + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); /** * Colors. @@ -115,14 +125,17 @@ function useColors() { return false; } + let m; + // Is webkit? http://stackoverflow.com/a/16459606/376773 // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + // eslint-disable-next-line no-return-assign return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || // Is firebug? http://stackoverflow.com/a/398120/376773 (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || // Is firefox >= v31? // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + (typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) || // Double check webkit in userAgent just in case we are in a worker (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); } @@ -206,7 +219,7 @@ function save(namespaces) { function load() { let r; try { - r = exports.storage.getItem('debug'); + r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ; } catch (error) { // Swallow // XXX (@Qix-) should we be logging these? diff --git a/src/common.js b/src/common.js index 32b4fd62..141cb578 100644 --- a/src/common.js +++ b/src/common.js @@ -12,16 +12,12 @@ function setup(env) { createDebug.enable = enable; createDebug.enabled = enabled; createDebug.humanize = require('ms'); + createDebug.destroy = destroy; Object.keys(env).forEach(key => { createDebug[key] = env[key]; }); - /** - * Active `debug` instances. - */ - createDebug.instances = []; - /** * The currently active debug mode names, and names to skip. */ @@ -38,7 +34,7 @@ function setup(env) { /** * Selects a color for a debug namespace - * @param {String} namespace The namespace string for the for the debug instance to be colored + * @param {String} namespace The namespace string for the debug instance to be colored * @return {Number|String} An ANSI color code for the given namespace * @api private */ @@ -63,6 +59,9 @@ function setup(env) { */ function createDebug(namespace) { let prevTime; + let enableOverride = null; + let namespacesCache; + let enabledCache; function debug(...args) { // Disabled? @@ -92,7 +91,7 @@ function setup(env) { args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { // If we encounter an escaped % then don't increase the array index if (match === '%%') { - return match; + return '%'; } index++; const formatter = createDebug.formatters[format]; @@ -115,31 +114,38 @@ function setup(env) { } debug.namespace = namespace; - debug.enabled = createDebug.enabled(namespace); debug.useColors = createDebug.useColors(); - debug.color = selectColor(namespace); - debug.destroy = destroy; + debug.color = createDebug.selectColor(namespace); debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } + + return enabledCache; + }, + set: v => { + enableOverride = v; + } + }); // Env-specific initialization logic for debug instances if (typeof createDebug.init === 'function') { createDebug.init(debug); } - createDebug.instances.push(debug); - return debug; } - function destroy() { - const index = createDebug.instances.indexOf(this); - if (index !== -1) { - createDebug.instances.splice(index, 1); - return true; - } - return false; - } - function extend(namespace, delimiter) { const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); newDebug.log = this.log; @@ -155,33 +161,67 @@ function setup(env) { */ function enable(namespaces) { createDebug.save(namespaces); + createDebug.namespaces = namespaces; createDebug.names = []; createDebug.skips = []; - let i; - const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - const len = split.length; + const split = (typeof namespaces === 'string' ? namespaces : '') + .trim() + .replace(/\s+/g, ',') + .split(',') + .filter(Boolean); - for (i = 0; i < len; i++) { - if (!split[i]) { - // ignore empty strings - continue; + for (const ns of split) { + if (ns[0] === '-') { + createDebug.skips.push(ns.slice(1)); + } else { + createDebug.names.push(ns); } + } + } - namespaces = split[i].replace(/\*/g, '.*?'); - - if (namespaces[0] === '-') { - createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + /** + * Checks if the given string matches a namespace template, honoring + * asterisks as wildcards. + * + * @param {String} search + * @param {String} template + * @return {Boolean} + */ + function matchesTemplate(search, template) { + let searchIndex = 0; + let templateIndex = 0; + let starIndex = -1; + let matchIndex = 0; + + while (searchIndex < search.length) { + if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) { + // Match character or proceed with wildcard + if (template[templateIndex] === '*') { + starIndex = templateIndex; + matchIndex = searchIndex; + templateIndex++; // Skip the '*' + } else { + searchIndex++; + templateIndex++; + } + } else if (starIndex !== -1) { // eslint-disable-line no-negated-condition + // Backtrack to the last '*' and try to match more characters + templateIndex = starIndex + 1; + matchIndex++; + searchIndex = matchIndex; } else { - createDebug.names.push(new RegExp('^' + namespaces + '$')); + return false; // No match } } - for (i = 0; i < createDebug.instances.length; i++) { - const instance = createDebug.instances[i]; - instance.enabled = createDebug.enabled(instance.namespace); + // Handle trailing '*' in template + while (templateIndex < template.length && template[templateIndex] === '*') { + templateIndex++; } + + return templateIndex === template.length; } /** @@ -192,8 +232,8 @@ function setup(env) { */ function disable() { const namespaces = [ - ...createDebug.names.map(toNamespace), - ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) + ...createDebug.names, + ...createDebug.skips.map(namespace => '-' + namespace) ].join(','); createDebug.enable(''); return namespaces; @@ -207,21 +247,14 @@ function setup(env) { * @api public */ function enabled(name) { - if (name[name.length - 1] === '*') { - return true; - } - - let i; - let len; - - for (i = 0, len = createDebug.skips.length; i < len; i++) { - if (createDebug.skips[i].test(name)) { + for (const skip of createDebug.skips) { + if (matchesTemplate(name, skip)) { return false; } } - for (i = 0, len = createDebug.names.length; i < len; i++) { - if (createDebug.names[i].test(name)) { + for (const ns of createDebug.names) { + if (matchesTemplate(name, ns)) { return true; } } @@ -229,19 +262,6 @@ function setup(env) { return false; } - /** - * Convert regexp to namespace - * - * @param {RegExp} regxep - * @return {String} namespace - * @api private - */ - function toNamespace(regexp) { - return regexp.toString() - .substring(2, regexp.toString().length - 2) - .replace(/\.\*\?$/, '*'); - } - /** * Coerce `val`. * @@ -256,6 +276,14 @@ function setup(env) { return val; } + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + createDebug.enable(createDebug.load()); return createDebug; diff --git a/src/node.js b/src/node.js index 5e1f1541..715560a4 100644 --- a/src/node.js +++ b/src/node.js @@ -15,6 +15,10 @@ exports.formatArgs = formatArgs; exports.save = save; exports.load = load; exports.useColors = useColors; +exports.destroy = util.deprecate( + () => {}, + 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' +); /** * Colors. @@ -183,11 +187,11 @@ function getDate() { } /** - * Invokes `util.format()` with the specified arguments and writes to stderr. + * Invokes `util.formatWithOptions()` with the specified arguments and writes to stderr. */ function log(...args) { - return process.stderr.write(util.format(...args) + '\n'); + return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + '\n'); } /** @@ -244,7 +248,9 @@ const {formatters} = module.exports; formatters.o = function (v) { this.inspectOpts.colors = this.useColors; return util.inspect(v, this.inspectOpts) - .replace(/\s*\n\s*/g, ' '); + .split('\n') + .map(str => str.trim()) + .join(' '); }; /** diff --git a/test.js b/test.js index f61e079b..a1d6f633 100644 --- a/test.js +++ b/test.js @@ -117,5 +117,24 @@ describe('debug', () => { assert.deepStrictEqual(oldNames.map(String), debug.names.map(String)); assert.deepStrictEqual(oldSkips.map(String), debug.skips.map(String)); }); + + it('handles re-enabling existing instances', () => { + debug.disable('*'); + const inst = debug('foo'); + const messages = []; + inst.log = msg => messages.push(msg.replace(/^[^@]*@([^@]+)@.*$/, '$1')); + + inst('@test@'); + assert.deepStrictEqual(messages, []); + debug.enable('foo'); + assert.deepStrictEqual(messages, []); + inst('@test2@'); + assert.deepStrictEqual(messages, ['test2']); + inst('@test3@'); + assert.deepStrictEqual(messages, ['test2', 'test3']); + debug.disable('*'); + inst('@test4@'); + assert.deepStrictEqual(messages, ['test2', 'test3']); + }); }); }); diff --git a/test.node.js b/test.node.js new file mode 100644 index 00000000..4cc3c051 --- /dev/null +++ b/test.node.js @@ -0,0 +1,40 @@ +/* eslint-env mocha */ + +const assert = require('assert'); +const util = require('util'); +const sinon = require('sinon'); +const debug = require('./src/node'); + +const formatWithOptionsSpy = sinon.spy(util, 'formatWithOptions'); +beforeEach(() => { + formatWithOptionsSpy.resetHistory(); +}); + +describe('debug node', () => { + describe('formatting options', () => { + it('calls util.formatWithOptions', () => { + debug.enable('*'); + const stdErrWriteStub = sinon.stub(process.stderr, 'write'); + const log = debug('formatting options'); + log('hello world'); + assert(util.formatWithOptions.callCount === 1); + stdErrWriteStub.restore(); + }); + + it('calls util.formatWithOptions with inspectOpts', () => { + debug.enable('*'); + const options = { + hideDate: true, + colors: true, + depth: 10, + showHidden: true + }; + Object.assign(debug.inspectOpts, options); + const stdErrWriteStub = sinon.stub(process.stderr, 'write'); + const log = debug('format with inspectOpts'); + log('hello world2'); + assert.deepStrictEqual(util.formatWithOptions.getCall(0).args[0], options); + stdErrWriteStub.restore(); + }); + }); +});