diff --git a/bin/commands/generate.mjs b/bin/commands/generate.mjs index 12bfe8d4..301d12e9 100644 --- a/bin/commands/generate.mjs +++ b/bin/commands/generate.mjs @@ -9,16 +9,20 @@ import { } from '../../src/constants.mjs'; import { publicGenerators } from '../../src/generators/index.mjs'; import createGenerator from '../../src/generators.mjs'; +import logger from '../../src/logger/index.mjs'; +import transports from '../../src/logger/transports.mjs'; import { parseChangelog, parseIndex } from '../../src/parsers/markdown.mjs'; import { loadAndParse } from '../utils.mjs'; const availableGenerators = Object.keys(publicGenerators); +const availableTransports = Object.keys(transports); /** * @typedef {Object} Options * @property {Array|string} input - Specifies the glob/path for input files. * @property {Array|string} [ignore] - Specifies the glob/path for ignoring files. * @property {Array} target - Specifies the generator target mode. + * @property {keyof availableTransports} transport - Specified the logging transport. * @property {string} version - Specifies the target Node.js version. * @property {string} changelog - Specifies the path to the Node.js CHANGELOG.md file. * @property {string} [gitRef] - Git ref/commit URL. @@ -104,6 +108,15 @@ export default { })), }, }, + transport: { + flags: ['--transport [transport]'], + desc: 'Transport for logging', + prompt: { + type: 'multiselect', + options: availableTransports.map(t => ({ value: t, label: t })), + initialValue: 'pretty', + }, + }, index: { flags: ['--index '], desc: 'The index document, for getting the titles of various API docs', @@ -119,6 +132,8 @@ export default { * @returns {Promise} */ async action(opts) { + logger.add(transports[opts.transport]()); + const docs = await loadAndParse(opts.input, opts.ignore); const releases = await parseChangelog(opts.changelog); const index = opts.index && (await parseIndex(opts.index)); diff --git a/bin/commands/interactive.mjs b/bin/commands/interactive.mjs index 2cd6b58a..618c9593 100644 --- a/bin/commands/interactive.mjs +++ b/bin/commands/interactive.mjs @@ -13,7 +13,7 @@ import { } from '@clack/prompts'; import commands from './index.mjs'; -import { Logger } from '../../src/logger/index.mjs'; +import logger from '../../src/logger/index.mjs'; /** * Validates that a string is not empty. @@ -166,7 +166,7 @@ export default async function interactive() { const finalCommand = cmdParts.join(' '); - Logger.getInstance().info(`\nGenerated command:\n${finalCommand}\n`); + logger.info(`\nGenerated command:\n${finalCommand}\n`); // Step 5: Confirm and execute the generated command if (await confirm({ message: 'Run now?', initialValue: true })) { diff --git a/bin/utils.mjs b/bin/utils.mjs index 37a6f9b7..8d0df2eb 100644 --- a/bin/utils.mjs +++ b/bin/utils.mjs @@ -1,5 +1,5 @@ import createMarkdownLoader from '../src/loaders/markdown.mjs'; -import { Logger } from '../src/logger/index.mjs'; +import logger from '../src/logger/index.mjs'; import createMarkdownParser from '../src/parsers/markdown.mjs'; /** @@ -43,7 +43,7 @@ export const errorWrap = try { return await fn(...args); } catch (err) { - Logger.getInstance().error(err); + logger.error(err); process.exit(1); } }; diff --git a/package-lock.json b/package-lock.json index 6ae90cd4..a6809f5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,8 @@ "unist-util-select": "^5.1.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", + "winston": "^3.17.0", + "winston-console-format": "^1.0.8", "yaml": "^2.8.1" }, "bin": { @@ -164,6 +166,26 @@ "sisteransi": "^1.0.5" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", @@ -3196,6 +3218,12 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3852,6 +3880,12 @@ "astring": "bin/astring" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -4178,6 +4212,16 @@ "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", "license": "MIT" }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4196,6 +4240,31 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -4203,6 +4272,25 @@ "dev": true, "license": "MIT" }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -4480,6 +4568,12 @@ "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", "license": "MIT" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -5022,6 +5116,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5081,6 +5181,12 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "license": "ISC" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -5569,6 +5675,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", @@ -5599,6 +5711,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -5707,6 +5825,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5824,6 +5954,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6369,6 +6505,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -7481,6 +7634,15 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -7965,6 +8127,20 @@ } } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/reading-time": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", @@ -8449,6 +8625,35 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -8517,6 +8722,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -8612,6 +8826,15 @@ "node": ">=12.0.0" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -8635,6 +8858,15 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -8967,6 +9199,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8989,6 +9227,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -9469,6 +9716,53 @@ "node": ">= 8" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-console-format": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/winston-console-format/-/winston-console-format-1.0.8.tgz", + "integrity": "sha512-dq7t/E0D0QRi4XIOwu6HM1+5e//WPqylH88GVjKEhQVrzGFg34MCz+G7pMJcXFBen9C0kBsu5GYgbYsE2LDwKw==", + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "logform": "^2.2.0", + "triple-beam": "^1.3.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 8c018c42..80149410 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,8 @@ "unist-util-select": "^5.1.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", + "winston": "^3.17.0", + "winston-console-format": "^1.0.8", "yaml": "^2.8.1" } } diff --git a/src/logger/__tests__/logger.test.mjs b/src/logger/__tests__/logger.test.mjs deleted file mode 100644 index 51685af8..00000000 --- a/src/logger/__tests__/logger.test.mjs +++ /dev/null @@ -1,221 +0,0 @@ -import { deepStrictEqual, strictEqual } from 'node:assert'; -import { describe, it } from 'node:test'; - -import { LogLevel } from '../constants.mjs'; -import { createLogger } from '../logger.mjs'; - -/** - * @type {import('../types').Metadata} - */ -const metadata = { - file: { - path: 'test.md', - position: { - start: { line: 1 }, - end: { line: 1 }, - }, - }, -}; - -describe('createLogger', () => { - describe('DEBUG', () => { - it('should log DEBUG messages when logger level is set to DEBUG', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const transport = t.mock.fn(); - - const logger = createLogger(transport, LogLevel.debug); - - logger.debug('Hello, World!', metadata); - - strictEqual(transport.mock.callCount(), 1); - - const call = transport.mock.calls[0]; - deepStrictEqual(call.arguments, [ - { - level: LogLevel.debug, - message: 'Hello, World!', - metadata, - module: undefined, - timestamp: 0, - }, - ]); - }); - - it('should filter DEBUG messages when logger level is set to INFO or higher', t => { - [LogLevel.info, LogLevel.warn, LogLevel.error, LogLevel.fatal].forEach( - loggerLevel => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, loggerLevel); - - logger.debug('Hello, World!'); - - strictEqual(transport.mock.callCount(), 0); - } - ); - }); - }); - - describe('INFO', () => { - it('should log INFO messages when logger level is set to INFO or lower', t => { - t.mock.timers.enable({ apis: ['Date'] }); - [LogLevel.info, LogLevel.debug].forEach(loggerLevel => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, loggerLevel); - - logger.info('Hello, World!', metadata); - - strictEqual(transport.mock.callCount(), 1); - - const call = transport.mock.calls[0]; - deepStrictEqual(call.arguments, [ - { - level: LogLevel.info, - message: 'Hello, World!', - metadata, - module: undefined, - timestamp: 0, - }, - ]); - }); - }); - - it('should filter INFO messages when logger level is set to WARN or higher', t => { - [LogLevel.warn, LogLevel.error, LogLevel.fatal].forEach(loggerLevel => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, loggerLevel); - - logger.info('Hello, World!'); - - strictEqual(transport.mock.callCount(), 0); - }); - }); - }); - - describe('WARN', () => { - it('should log WARN messages when logger level is set to WARN or lower', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - [LogLevel.warn, LogLevel.info, LogLevel.debug].forEach(loggerLevel => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, loggerLevel); - - logger.warn('Hello, World!', metadata); - - strictEqual(transport.mock.callCount(), 1); - - const call = transport.mock.calls[0]; - deepStrictEqual(call.arguments, [ - { - level: LogLevel.warn, - message: 'Hello, World!', - metadata, - module: undefined, - timestamp: 0, - }, - ]); - }); - }); - - it('should filter WARN messages when logger level is set to ERROR or higher', t => { - [LogLevel.error, LogLevel.fatal].forEach(loggerLevel => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, loggerLevel); - - logger.warn('Hello, World!'); - - strictEqual(transport.mock.callCount(), 0); - }); - }); - }); - - describe('ERROR', () => { - it('should log ERROR messages when logger level is set to ERROR or lower', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - [LogLevel.error, LogLevel.warn, LogLevel.info, LogLevel.debug].forEach( - loggerLevel => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, loggerLevel); - - logger.error('Hello, World!', metadata); - - strictEqual(transport.mock.callCount(), 1); - - const call = transport.mock.calls[0]; - deepStrictEqual(call.arguments, [ - { - level: LogLevel.error, - message: 'Hello, World!', - metadata, - module: undefined, - timestamp: 0, - }, - ]); - } - ); - }); - - it('should filter ERROR messages when logger level is set to FATAL', t => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, LogLevel.fatal); - - logger.warn('Hello, World!'); - - strictEqual(transport.mock.callCount(), 0); - }); - }); - - it('should filter all messages when minimum level is set above FATAL', t => { - const transport = t.mock.fn(); - - // silent logs - const logger = createLogger(transport, 100); - - Object.keys(LogLevel).forEach(level => { - logger[level]('Hello, World!'); - }); - - strictEqual(transport.mock.callCount(), 0); - }); - - it('should log all messages if message is a string array', t => { - const transport = t.mock.fn(); - - const logger = createLogger(transport, LogLevel.info); - - logger.info(['Hello, 1!', 'Hello, 2!', 'Hello, 3!']); - - strictEqual(transport.mock.callCount(), 3); - }); - - it('should log error message', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const transport = t.mock.fn(); - - const logger = createLogger(transport, LogLevel.error); - - logger.error(new Error('Hello, World!')); - - strictEqual(transport.mock.callCount(), 1); - - const call = transport.mock.calls[0]; - deepStrictEqual(call.arguments, [ - { - level: LogLevel.error, - message: 'Hello, World!', - metadata: {}, - module: undefined, - timestamp: 0, - }, - ]); - }); -}); diff --git a/src/logger/__tests__/transports/console.test.mjs b/src/logger/__tests__/transports/console.test.mjs deleted file mode 100644 index 71d1268b..00000000 --- a/src/logger/__tests__/transports/console.test.mjs +++ /dev/null @@ -1,210 +0,0 @@ -import { deepStrictEqual, strictEqual } from 'assert'; -import { describe, it } from 'node:test'; - -import { LogLevel } from '../../constants.mjs'; -import console from '../../transports/console.mjs'; - -describe('console', () => { - it('should print debug messages', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.debug, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 4); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' \x1B[34mDEBUG\x1B[39m', - ': Test message', - '\n', - ]); - }); - - it('should print info messages', t => { - t.mock.timers.enable({ apis: ['Date'] }); - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.info, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 4); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' \x1B[32mINFO\x1B[39m', - ': Test message', - '\n', - ]); - }); - - it('should print error messages ', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.error, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 4); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' \x1B[35mERROR\x1B[39m', - ': Test message', - '\n', - ]); - }); - - it('should print fatal messages', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.fatal, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 4); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' \x1B[31mFATAL\x1B[39m', - ': Test message', - '\n', - ]); - }); - - it('should print messages with file', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.info, - message: 'Test message', - metadata: { - file: { - path: 'test.md', - position: { - start: { line: 1 }, - end: { line: 1 }, - }, - }, - }, - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 6); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' \x1B[32mINFO\x1B[39m', - ': Test message', - ' at test.md', - '(1:1)', - '\n', - ]); - }); - - it('should print child logger name', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.info, - message: 'Test message', - timestamp: Date.now(), - module: 'child1', - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 5); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' \x1B[32mINFO\x1B[39m', - ' (child1)', - ': Test message', - '\n', - ]); - }); - - it('should print without colors if FORCE_COLOR = 0', t => { - process.env.FORCE_COLOR = 0; - - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - console({ - level: LogLevel.info, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 4); - deepStrictEqual(callsArgs, [ - '[00:00:00.000]', - ' INFO', - ': Test message', - '\n', - ]); - }); -}); diff --git a/src/logger/__tests__/transports/github.test.mjs b/src/logger/__tests__/transports/github.test.mjs deleted file mode 100644 index 3ee156f8..00000000 --- a/src/logger/__tests__/transports/github.test.mjs +++ /dev/null @@ -1,185 +0,0 @@ -import { deepStrictEqual, strictEqual } from 'assert'; -import { describe, it } from 'node:test'; - -import { LogLevel } from '../../constants.mjs'; -import github from '../../transports/github.mjs'; - -describe('github', () => { - it('should print debug messages', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.debug, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::debug::[00:00:00.000] \x1B[34mDEBUG\x1B[39m: Test message\n', - ]); - }); - - it('should print info messages', t => { - t.mock.timers.enable({ apis: ['Date'] }); - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.info, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::notice ::[00:00:00.000] \x1B[32mINFO\x1B[39m: Test message\n', - ]); - }); - - it('should print error messages ', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.error, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::error ::[00:00:00.000] \x1B[35mERROR\x1B[39m: Test message\n', - ]); - }); - - it('should print fatal messages', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.fatal, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::error ::[00:00:00.000] \x1B[31mFATAL\x1B[39m: Test message\n', - ]); - }); - - it('should print messages with file', t => { - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.info, - message: 'Test message', - metadata: { - file: { - path: 'test.md', - position: { - start: { line: 1 }, - end: { line: 1 }, - }, - }, - }, - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::notice file=test.md,line=1,endLine=1::[00:00:00.000] \x1B[32mINFO\x1B[39m: Test message\n', - ]); - }); - - it('should print child logger name', t => { - t.mock.timers.enable({ apis: ['Date'] }); - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.info, - message: 'Test message', - timestamp: Date.now(), - module: 'child1', - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::notice ::[00:00:00.000] \x1B[32mINFO\x1B[39m (child1): Test message\n', - ]); - }); - - it('should print without colors if FORCE_COLOR = 0', t => { - process.env.FORCE_COLOR = 0; - - t.mock.timers.enable({ apis: ['Date'] }); - - const fn = t.mock.method(process.stdout, 'write'); - - // noop - fn.mock.mockImplementation(() => {}); - - github({ - level: LogLevel.info, - message: 'Test message', - timestamp: Date.now(), - }); - - const callsArgs = process.stdout.write.mock.calls.map( - call => call.arguments[0] - ); - - strictEqual(process.stdout.write.mock.callCount(), 1); - deepStrictEqual(callsArgs, [ - '::notice ::[00:00:00.000] INFO: Test message\n', - ]); - }); -}); diff --git a/src/logger/child.mjs b/src/logger/child.mjs new file mode 100644 index 00000000..fb153c81 --- /dev/null +++ b/src/logger/child.mjs @@ -0,0 +1,15 @@ +import { parentPort } from 'node:worker_threads'; + +import { config } from 'winston'; + +export default Object.fromEntries( + Object.keys(config.cli.levels).map(method => [ + method, + (...args) => + parentPort.postMessage({ + type: 'log', + method, + args, + }), + ]) +); diff --git a/src/logger/constants.mjs b/src/logger/constants.mjs deleted file mode 100644 index 07683124..00000000 --- a/src/logger/constants.mjs +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -/** - * Numeric log level definitions. - */ -export const LogLevel = { - debug: 10, - info: 20, - warn: 30, - error: 40, - fatal: 50, -}; - -/** - * Maps log level numbers to their string tags. - */ -export const levelTags = { - [LogLevel.debug]: 'DEBUG', - [LogLevel.info]: 'INFO', - [LogLevel.warn]: 'WARN', - [LogLevel.error]: 'ERROR', - [LogLevel.fatal]: 'FATAL', -}; - -/** - * Maps log level numbers to CLI color names. - */ -export const levelToColorMap = { - [LogLevel.debug]: 'blue', - [LogLevel.info]: 'green', - [LogLevel.warn]: 'yellow', - [LogLevel.error]: 'magenta', - [LogLevel.fatal]: 'red', -}; diff --git a/src/logger/index.mjs b/src/logger/index.mjs index 4b2ac5b4..91d14c56 100644 --- a/src/logger/index.mjs +++ b/src/logger/index.mjs @@ -1,27 +1,7 @@ -'use strict'; +import { isMainThread } from 'node:worker_threads'; -import { createLogger } from './logger.mjs'; -import { transports } from './transports/index.mjs'; +import ChildLogger from './child.mjs'; +import ParentLogger from './parent.mjs'; -/** - * @typedef {ReturnType} LoggerInstance - */ - -/** - * Creates a new logger instance with the specified transport. - * - * @param {string} [transportName='console'] - Name of the transport to use. - * @returns {LoggerInstance} - */ -export const Logger = (transportName = 'console') => { - const transport = transports[transportName]; - - if (!transport) { - throw new Error(`Transport '${transportName}' not found.`); - } - - return createLogger(transport); -}; - -// Default logger instance using console transport -export default Logger(); +/** @type {import('winston').Logger} */ +export default isMainThread ? ParentLogger : ChildLogger; diff --git a/src/logger/logger.mjs b/src/logger/logger.mjs deleted file mode 100644 index 2277f4f7..00000000 --- a/src/logger/logger.mjs +++ /dev/null @@ -1,131 +0,0 @@ -'use strict'; - -import { LogLevel } from './constants.mjs'; - -/** - * @typedef {import('./types').Metadata} Metadata - * @typedef {import('./types').LogMessage} LogMessage - */ - -/** - * Creates a logger instance with the specified transport, log level and an - * optional module name. - * - * @param {import('./types').Transport} transport - Function to handle log output. - * @param {number} [loggerLevel] - Minimum log level to output. - * @param {string} [module] - Optional module name for the logger. - */ -export const createLogger = ( - transport, - loggerLevel = LogLevel.info, - module -) => { - /** - * Logs a message at the given level with optional metadata. - * - * @param {number} level - Log level for the message. - * @param {LogMessage} message - Message to log. - * @param {Metadata} metadata - Additional metadata - * @returns {void} - */ - const log = (level, message, metadata = {}) => { - if (!shouldLog(level)) { - return; - } - - if (Array.isArray(message)) { - return message.forEach(msg => log(level, msg, metadata)); - } - - const timestamp = Date.now(); - - // Extract message string from Error object or use message as-is - const msg = message instanceof Error ? message.message : message; - - transport({ - level, - message: msg, - timestamp, - metadata, - module, - }); - }; - - /** - * Logs an info message. - * - * @param {LogMessage} message - Info message to log. - * @param {Metadata} metadata - Additional metadata - * @returns {void} - */ - const info = (message, metadata = {}) => - log(LogLevel.info, message, metadata); - - /** - * Logs a warning message. - * - * @param {LogMessage} message - Warning message to log. - * @param {Metadata} metadata - Additional metadata - * @returns {void} - */ - const warn = (message, metadata = {}) => - log(LogLevel.warn, message, metadata); - - /** - * Logs an error message or Error object. - * - * @param {LogMessage} message - Error message or Error object to log. - * @param {Metadata} metadata - Additional metadata - * @returns {void} - */ - const error = (message, metadata = {}) => - log(LogLevel.error, message, metadata); - - /** - * Logs a fatal error message or Error object. - * - * @param {LogMessage} message - Fatal error message or Error object to log. - * @param {Metadata} metadata - Additional metadata - * @returns {void} - */ - const fatal = (message, metadata = {}) => - log(LogLevel.fatal, message, metadata); - - /** - * Logs a debug message. - * - * @param {LogMessage} message - Debug message to log. - * @param {Metadata} metadata - Additional metadata - * @returns {void} - */ - const debug = (message, metadata = {}) => - log(LogLevel.debug, message, metadata); - - /** - * Creates a child logger for a specific module. - * - * @param {string} module - Module name for the child logger. - * @returns {ReturnType} - */ - const child = module => createLogger(transport, loggerLevel, module); - - /** - * Checks if the given log level should be logged based on the current logger - * level. - * - * @param {number} level - Log level to check. - * @returns {boolean} - */ - const shouldLog = level => { - return level >= loggerLevel; - }; - - return { - info, - warn, - error, - fatal, - debug, - child, - }; -}; diff --git a/src/logger/parent.mjs b/src/logger/parent.mjs new file mode 100644 index 00000000..52b67005 --- /dev/null +++ b/src/logger/parent.mjs @@ -0,0 +1,12 @@ +import { createLogger, format } from 'winston'; + +export default createLogger({ + level: 'info', + format: format.combine( + format.timestamp(), + format.ms(), + format.errors({ stack: true }), + format.splat(), + format.json() + ), +}); diff --git a/src/logger/transports.mjs b/src/logger/transports.mjs new file mode 100644 index 00000000..bd0733bf --- /dev/null +++ b/src/logger/transports.mjs @@ -0,0 +1,41 @@ +import actions from '@actions/core'; +import { transports, format } from 'winston'; +import TransportStream from 'winston-transport'; + +/** Pretty-print console transport */ +const pretty = () => + new transports.Console({ + format: format.combine( + format.colorize({ all: true }), + format.printf( + ({ timestamp, level, message, label = '-', stack = '' }) => + `${timestamp} [${label}] ${level}: ${message}${stack.substring(stack.indexOf('\n'))}` + ) + ), + }); + +const actionMap = { + debug: actions.debug, + warn: actions.warning, + warning: actions.warning, + error: actions.error, +}; + +/** GitHub Actions transport */ +const githubActions = () => + new TransportStream({ + /** + * Logs a message to GitHub Actions. + * + * @param {Object} info - Log information. + * @param {string} info.level - Log level (e.g., 'debug', 'info', 'warn', 'error'). + * @param {string} info.message - Log message. + * @param {() => void} callback - Callback to signal completion. + */ + log({ level, message }, callback) { + (actionMap[level] || actions.notice)(message); + callback(); + }, + }); + +export default { pretty, 'github-actions': githubActions }; diff --git a/src/logger/transports/console.mjs b/src/logger/transports/console.mjs deleted file mode 100644 index 325b2a3c..00000000 --- a/src/logger/transports/console.mjs +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -import { prettifyLevel } from '../utils/colors.mjs'; -import { prettifyTimestamp } from '../utils/time.mjs'; - -/** - * Logs a formatted message to stdout for human-friendly CLI output. - * - * @param {import('../types').TransportContext} context - * @returns {void} - */ -const console = ({ level, message, timestamp, metadata = {}, module }) => { - const { file } = metadata; - - const time = prettifyTimestamp(timestamp); - - process.stdout.write(`[${time}]`); - - const prettyLevel = prettifyLevel(level); - - process.stdout.write(` ${prettyLevel}`); - - if (module) { - process.stdout.write(` (${module})`); - } - - process.stdout.write(`: ${message}`); - - if (file) { - process.stdout.write(` at ${file.path}`); - } - - if (file?.position) { - const position = `(${file.position.start.line}:${file.position.end.line})`; - - process.stdout.write(position); - } - - process.stdout.write('\n'); -}; - -export default console; diff --git a/src/logger/transports/github.mjs b/src/logger/transports/github.mjs deleted file mode 100644 index 8b5fb512..00000000 --- a/src/logger/transports/github.mjs +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -import { debug, notice, warning, error } from '@actions/core'; - -import { LogLevel } from '../constants.mjs'; -import { prettifyLevel } from '../utils/colors.mjs'; -import { prettifyTimestamp } from '../utils/time.mjs'; - -const actions = { - [LogLevel.debug]: debug, - [LogLevel.info]: notice, - [LogLevel.warn]: warning, - [LogLevel.error]: error, - [LogLevel.fatal]: error, -}; - -/** - * Logs messages to GitHub Actions with formatted output and file info with - * appropriate gh actions based on level. - * - * @param {import('../types').TransportContext} context - * @returns {void} - */ -const github = ({ level, message, timestamp, metadata = {}, module }) => { - const { file } = metadata; - - const time = prettifyTimestamp(timestamp); - - const prettyLevel = prettifyLevel(level); - - const logMessage = `[${time}] ${prettyLevel}${module ? ` (${module})` : ''}: ${message}`; - - const logFn = actions[level] ?? notice; - - logFn(logMessage, { - file: file?.path, - startLine: file?.position?.start.line, - endLine: file?.position?.end.line, - }); -}; - -export default github; diff --git a/src/logger/transports/index.mjs b/src/logger/transports/index.mjs deleted file mode 100644 index 1adcfd70..00000000 --- a/src/logger/transports/index.mjs +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -import console from './console.mjs'; -import github from './github.mjs'; - -export const transports = { - console, - github, -}; - -export const availableTransports = Object.keys(transports); diff --git a/src/logger/types.d.ts b/src/logger/types.d.ts deleted file mode 100644 index 3e7fc9c1..00000000 --- a/src/logger/types.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -export type LogLevel = 'info' | 'warn' | 'error' | 'fatal' | 'trace' | 'debug'; - -export type LogMessage = string | Error | string[]; - -export interface Position { - start: { line: number }; - end: { line: number }; -} - -export interface File { - path: string; - position?: Position; -} - -interface Metadata { - file?: File; -} - -interface TransportContext { - level: number; - message: string; - timestamp: number; - metadata?: Metadata; - module?: string; -} - -export type Transport = (context: TransportContext) => void; diff --git a/src/logger/utils/colors.mjs b/src/logger/utils/colors.mjs deleted file mode 100644 index ebdcb165..00000000 --- a/src/logger/utils/colors.mjs +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -import { styleText } from 'node:util'; - -import { levelTags, levelToColorMap } from '../constants.mjs'; - -/** - * Returns a styled, uppercase log level tag for CLI output with color mapping - * for better readability. - * - * @param {number} level - * @returns {string} - */ -export const prettifyLevel = level => { - const tag = levelTags[level] ?? String(level); - - return styleText(levelToColorMap[level], tag.toUpperCase()); -}; diff --git a/src/logger/utils/time.mjs b/src/logger/utils/time.mjs deleted file mode 100644 index a35291ab..00000000 --- a/src/logger/utils/time.mjs +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -/** - * Formats a Unix timestamp in milliseconds as a human-readable time string - * in UTC timezone for CLI output. - * - * @param {number} timestamp - * @returns {string} - */ -export const prettifyTimestamp = timestamp => { - const date = new Date(timestamp); - - return new Intl.DateTimeFormat('en-US', { - timeZone: 'UTC', - hour12: false, - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - fractionalSecondDigits: 3, - }).format(date); -}; diff --git a/src/threading/index.mjs b/src/threading/index.mjs index dd600418..c3c7a542 100644 --- a/src/threading/index.mjs +++ b/src/threading/index.mjs @@ -1,5 +1,7 @@ import { Worker } from 'node:worker_threads'; +import logger from '../logger/index.mjs'; + /** * WorkerPool class to manage a pool of worker threads */ @@ -49,14 +51,22 @@ export default class WorkerPool { }); // Handle worker thread messages (result or error) - worker.on('message', result => { - this.changeActiveThreadCount(-1); - this.processQueue(threads); + worker.on('message', message => { + switch (message.type) { + case 'response': + this.changeActiveThreadCount(-1); + this.processQueue(threads); + + if (message?.error) { + reject(message.error); + } else { + resolve(message.value); + } + break; - if (result?.error) { - reject(result.error); - } else { - resolve(result); + case 'log': + logger[message.method](...message.args, { label: name }); + break; } }); diff --git a/src/threading/worker.mjs b/src/threading/worker.mjs index ab107eac..8fa9f639 100644 --- a/src/threading/worker.mjs +++ b/src/threading/worker.mjs @@ -8,5 +8,5 @@ const generator = allGenerators[name]; // Execute the generator and send the result or error back to the parent thread generator .generate(dependencyOutput, extra) - .then(result => parentPort.postMessage(result)) - .catch(error => parentPort.postMessage({ error })); + .then(value => parentPort.postMessage({ type: 'response', value })) + .catch(error => parentPort.postMessage({ type: 'response', error }));