From 7befc6aa6d1e5e85d87fd0853c0751d2089efdb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Wed, 3 Jul 2024 13:14:56 +0200 Subject: [PATCH] fix: stop piping into `process.stdout/stderr` and use `console.log` (#2558) --- .changeset/flat-ways-act.md | 8 + packages/abi-typegen/src/cli.test.ts | 5 +- packages/abi-typegen/src/cli.ts | 3 +- packages/abi-typegen/src/runTypegen.test.ts | 8 +- packages/abi-typegen/src/runTypegen.ts | 3 +- .../account/src/test-utils/launchNode.test.ts | 187 +++++++----------- packages/account/src/test-utils/launchNode.ts | 28 ++- .../create-fuels/src/utils/logger.test.ts | 70 +++---- packages/create-fuels/src/utils/logger.ts | 8 +- packages/errors/src/error-codes.ts | 3 + .../test-utils/expect-to-throw-fuel-error.ts | 2 + packages/fuels/src/bin.ts | 5 +- .../commands/build/buildSwayProgram.test.ts | 33 ++-- .../cli/commands/build/buildSwayProgram.ts | 9 +- .../src/cli/commands/dev/autoStartFuelCore.ts | 1 - packages/fuels/src/cli/commands/init/index.ts | 3 +- packages/fuels/src/cli/utils/logger.test.ts | 72 +++---- packages/fuels/src/cli/utils/logger.ts | 8 +- .../fuels/src/setup-launch-node-server.ts | 1 - packages/fuels/test/features/init.test.ts | 6 +- scripts/verify-forc-version.ts | 3 +- 21 files changed, 209 insertions(+), 257 deletions(-) create mode 100644 .changeset/flat-ways-act.md diff --git a/.changeset/flat-ways-act.md b/.changeset/flat-ways-act.md new file mode 100644 index 00000000000..05b4da96ec5 --- /dev/null +++ b/.changeset/flat-ways-act.md @@ -0,0 +1,8 @@ +--- +"@fuel-ts/abi-typegen": patch +"@fuel-ts/account": minor +"create-fuels": patch +"fuels": patch +--- + +fix!: stop piping into `process.stdout/stderr` and use `console.log` diff --git a/packages/abi-typegen/src/cli.test.ts b/packages/abi-typegen/src/cli.test.ts index 683ffafca44..4dd76866dfc 100644 --- a/packages/abi-typegen/src/cli.test.ts +++ b/packages/abi-typegen/src/cli.test.ts @@ -164,7 +164,8 @@ describe('cli.ts', () => { test('should handle errors when running cli action', () => { const runTypegenError = new Error('Pretty message'); - const { exit, err } = mockDeps({ runTypegenError }); + const logSpy = vi.spyOn(console, 'log'); + const { exit } = mockDeps({ runTypegenError }); const inputs = ['*-no-abis-here.json']; const output = './aything'; @@ -175,6 +176,6 @@ describe('cli.ts', () => { }); expect(exit).toBeCalledWith(1); - expect(err).toBeCalledWith(`error: ${runTypegenError.message}\n`); + expect(logSpy).toBeCalledWith(`error: ${runTypegenError.message}`); }); }); diff --git a/packages/abi-typegen/src/cli.ts b/packages/abi-typegen/src/cli.ts index 942edf10883..2ce115a6d59 100644 --- a/packages/abi-typegen/src/cli.ts +++ b/packages/abi-typegen/src/cli.ts @@ -49,7 +49,8 @@ export function runCliAction(options: ICliParams) { silent: !!silent, }); } catch (err) { - process.stderr.write(`error: ${(err).message}\n`); + // eslint-disable-next-line no-console + console.log(`error: ${(err).message}`); process.exit(1); } } diff --git a/packages/abi-typegen/src/runTypegen.test.ts b/packages/abi-typegen/src/runTypegen.test.ts index f791d5e37b5..73cf4765748 100644 --- a/packages/abi-typegen/src/runTypegen.test.ts +++ b/packages/abi-typegen/src/runTypegen.test.ts @@ -160,7 +160,7 @@ describe('runTypegen.js', () => { }); test('should log messages to stdout', async () => { - const stdoutWrite = vi.spyOn(process.stdout, 'write').mockResolvedValue(true); + const logSpy = vi.spyOn(console, 'log'); // setup temp sway project const project = getTypegenForcProject(AbiTypegenProjectsEnum.SCRIPT); @@ -189,7 +189,7 @@ describe('runTypegen.js', () => { // validates execution was ok expect(error).toBeFalsy(); - expect(stdoutWrite).toHaveBeenCalledTimes(5); + expect(logSpy).toHaveBeenCalledTimes(5); }); test('should raise error for non-existent Script BIN file', async () => { @@ -279,7 +279,7 @@ describe('runTypegen.js', () => { cpSync(fromBin, toBin); // mocking - const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true); + const logSpy = vi.spyOn(console, 'log'); // executes program const fn = () => @@ -313,7 +313,7 @@ describe('runTypegen.js', () => { expect(existsSync(f)).toEqual(true); }); - expect(write).toHaveBeenCalled(); + expect(logSpy).toHaveBeenCalled(); }); test('should error for no ABI in inputs', async () => { diff --git a/packages/abi-typegen/src/runTypegen.ts b/packages/abi-typegen/src/runTypegen.ts index a108ebb04c3..63b38d6542f 100644 --- a/packages/abi-typegen/src/runTypegen.ts +++ b/packages/abi-typegen/src/runTypegen.ts @@ -27,7 +27,8 @@ export function runTypegen(params: IGenerateFilesParams) { function log(...args: unknown[]) { if (!silent) { - process.stdout.write(`${args.join(' ')}\n`); + // eslint-disable-next-line no-console + console.log(args.join(' ')); } } diff --git a/packages/account/src/test-utils/launchNode.test.ts b/packages/account/src/test-utils/launchNode.test.ts index 5067a68158b..07564cc7217 100644 --- a/packages/account/src/test-utils/launchNode.test.ts +++ b/packages/account/src/test-utils/launchNode.test.ts @@ -1,8 +1,11 @@ -import { safeExec } from '@fuel-ts/errors/test-utils'; +import { ErrorCode } from '@fuel-ts/errors'; +import { safeExec, expectToThrowFuelError } from '@fuel-ts/errors/test-utils'; +import { defaultSnapshotConfigs } from '@fuel-ts/utils'; import { waitUntilUnreachable } from '@fuel-ts/utils/test-utils'; import * as childProcessMod from 'child_process'; -import type { LaunchNodeOptions } from './launchNode'; +import { Provider } from '../providers'; + import { killNode, launchNode } from './launchNode'; type ChildProcessWithoutNullStreams = childProcessMod.ChildProcessWithoutNullStreams; @@ -15,51 +18,6 @@ vi.mock('child_process', async () => { }; }); -/** - * This should mimic the stderr.on('data') event, returning both - * success and error messages, as strings. These messages are like - * the ones from `fuel-core` startup log messages. We filter them - * to know fuel-core state. - */ -function mockSpawn(params: { shouldError: boolean } = { shouldError: false }) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const stderrOn = (eventName: string, fn: (data: any) => void) => { - if (eventName === 'data') { - if (params.shouldError) { - // The `IO error` message simulates a possible fuel-core error log message - fn('IO error'); - } else { - // The `Binding GraphQL provider to` message simulates a fuel-core - // successful startup log message, usually meaning that the node - // is up and waiting for connections - fn('Binding GraphQL provider to 0.0.0.0:4000'); - } - } - }; - - const innerMocks = { - on: vi.fn(), - stderr: { - pipe: vi.fn(), - on: vi.fn(stderrOn), - removeAllListeners: vi.fn(), - }, - stdout: { - pipe: vi.fn(), - removeAllListeners: vi.fn(), - }, - } as unknown as ChildProcessWithoutNullStreams; - - const spawn = vi.spyOn(childProcessMod, 'spawn').mockReturnValue(innerMocks); - - return { spawn, innerMocks }; -} - -const defaultLaunchNodeConfig: Partial = { - ip: '0.0.0.0', - port: '4000', -}; - /** * @group node */ @@ -73,7 +31,7 @@ describe('launchNode', () => { }); it('cleanup kills the started node', async () => { - const { cleanup, url } = await launchNode({}); + const { cleanup, url } = await launchNode(); expect(await fetch(url)).toBeTruthy(); cleanup(); @@ -81,102 +39,97 @@ describe('launchNode', () => { await waitUntilUnreachable(url); }); - test('should start `fuel-core` node using built-in binary', async () => { - mockSpawn(); + test('should start `fuel-core` node using system binary', async () => { + const spawnSpy = vi.spyOn(childProcessMod, 'spawn'); - const { cleanup, ip, port } = await launchNode({ - ...defaultLaunchNodeConfig, - }); + process.env.FUEL_CORE_PATH = ''; - expect(ip).toBe('0.0.0.0'); - expect(port).toBe('4000'); - cleanup(); - }); + const { result } = await safeExec(async () => launchNode()); - test('should start `fuel-core` node using system binary', async () => { - mockSpawn(); - - const { cleanup, ip, port } = await launchNode(defaultLaunchNodeConfig); + const command = spawnSpy.mock.calls[0][0]; + expect(command).toEqual('fuel-core'); - expect(ip).toBe('0.0.0.0'); - expect(port).toBe('4000'); + process.env.FUEL_CORE_PATH = 'fuels-core'; - cleanup(); + /** + * result can be undefined when running in CI and fuel-core is not installed + * meaning that spawn(fuel-core, ...) threw an error + */ + if (result !== undefined) { + (await result)?.cleanup(); + } }); - test('should start `fuel-core` node with custom binary', async () => { - const { spawn } = mockSpawn(); + test('should start `fuel-core` node using custom binary', async () => { + const spawnSpy = vi.spyOn(childProcessMod, 'spawn'); - const { cleanup, ip, port } = await launchNode({ - ...defaultLaunchNodeConfig, - fuelCorePath: 'fuels-core', + const fuelCorePath = './my-fuel-core-binary-path'; + const { error } = await safeExec(async () => { + await launchNode({ fuelCorePath, loggingEnabled: true }); }); - expect(ip).toBe('0.0.0.0'); - expect(port).toBe('4000'); - expect(spawn).toBeCalledWith('fuels-core', expect.any(Array), expect.any(Object)); + expect(error).toBeTruthy(); - cleanup(); + const command = spawnSpy.mock.calls[0][0]; + expect(command).toEqual(fuelCorePath); }); - test('should start `fuel-core` node with custom binaries', async () => { - const { spawn } = mockSpawn(); - - const { cleanup, ip, port } = await launchNode({ - ...defaultLaunchNodeConfig, - fuelCorePath: 'custom-fuels-core', - }); + test('reads FUEL_CORE_PATH environment variable for fuel-core binary', async () => { + const spawnSpy = vi.spyOn(childProcessMod, 'spawn'); + process.env.FUEL_CORE_PATH = 'fuels-core'; + const { cleanup, url } = await launchNode(); + await Provider.create(url); - expect(ip).toBe('0.0.0.0'); - expect(port).toBe('4000'); - expect(spawn).toBeCalledWith('custom-fuels-core', expect.any(Array), expect.any(Object)); + const command = spawnSpy.mock.calls[0][0]; + expect(command).toEqual('fuels-core'); cleanup(); }); - test('should throw on error', async () => { - const { innerMocks } = mockSpawn({ shouldError: true }); - - const { error: safeError, result } = await safeExec(async () => - launchNode(defaultLaunchNodeConfig) + test('should throw on error and log error message', async () => { + const logSpy = vi.spyOn(console, 'log'); + + const invalidCoin = { + asset_id: 'whatever', + tx_id: '', + output_index: 0, + tx_pointer_block_height: 0, + tx_pointer_tx_idx: 0, + owner: '', + amount: 0, + }; + + const error = await expectToThrowFuelError( + async () => + launchNode({ + loggingEnabled: false, + snapshotConfig: { + ...defaultSnapshotConfigs, + stateConfig: { + coins: [invalidCoin], + messages: [], + }, + }, + }), + { + code: ErrorCode.NODE_LAUNCH_FAILED, + } ); - expect(safeError).toBeTruthy(); - expect(result).not.toBeTruthy(); + expect(error).toBeTruthy(); - expect(innerMocks.on).toHaveBeenCalledTimes(1); - expect(innerMocks.stderr.pipe).toHaveBeenCalledTimes(1); - expect(innerMocks.stdout.pipe).toHaveBeenCalledTimes(0); + expect(logSpy).toHaveBeenCalledWith(error?.message); }); - test('should pipe stdout', async () => { - vi.spyOn(process.stdout, 'write'); - - const { innerMocks } = mockSpawn(); - - const { cleanup } = await launchNode(defaultLaunchNodeConfig); - - expect(innerMocks.stderr.pipe).toHaveBeenCalledTimes(1); - expect(innerMocks.stdout.pipe).toHaveBeenCalledTimes(0); + test('logs fuel-core outputs via console.log', async () => { + const logSpy = vi.spyOn(console, 'log'); + const { cleanup } = await launchNode({ loggingEnabled: true }); + const logs = logSpy.mock.calls.map((call) => call[0]); + expect(logs.some((log) => log.includes('Binding GraphQL provider'))).toBe(true); cleanup(); }); - test('should pipe stdout and stderr', async () => { - vi.spyOn(process.stderr, 'write'); - vi.spyOn(process.stdout, 'write'); - - const { innerMocks } = mockSpawn(); - - await launchNode({ - ...defaultLaunchNodeConfig, - debugEnabled: true, - }); - - expect(innerMocks.stderr.pipe).toHaveBeenCalledTimes(1); - expect(innerMocks.stdout.pipe).toHaveBeenCalledTimes(1); - }); - test('should kill process only if PID exists and node is alive', () => { const killFn = vi.fn(); const state = { isDead: true }; diff --git a/packages/account/src/test-utils/launchNode.ts b/packages/account/src/test-utils/launchNode.ts index 4f89ab09041..3fd1ceddb1a 100644 --- a/packages/account/src/test-utils/launchNode.ts +++ b/packages/account/src/test-utils/launchNode.ts @@ -1,5 +1,6 @@ import { BYTES_32 } from '@fuel-ts/abi-coder'; import { randomBytes } from '@fuel-ts/crypto'; +import { FuelError } from '@fuel-ts/errors'; import type { SnapshotConfigs } from '@fuel-ts/utils'; import { defaultConsensusKey, hexlify, defaultSnapshotConfigs } from '@fuel-ts/utils'; import type { ChildProcessWithoutNullStreams } from 'child_process'; @@ -41,7 +42,6 @@ export type LaunchNodeOptions = { args?: string[]; fuelCorePath?: string; loggingEnabled?: boolean; - debugEnabled?: boolean; basePath?: string; /** * The snapshot configuration to use. @@ -76,7 +76,6 @@ export const killNode = (params: KillNodeParams) => { } // Remove all the listeners we've added. - child.stdout.removeAllListeners(); child.stderr.removeAllListeners(); // Remove the temporary folder and all its contents. @@ -138,7 +137,6 @@ function getFinalStateConfigJSON({ stateConfig, chainConfig }: SnapshotConfigs) * @param args - additional arguments to pass to fuel-core. * @param fuelCorePath - the path to the fuel-core binary. (optional, defaults to 'fuel-core') * @param loggingEnabled - whether the node should output logs. (optional, defaults to true) - * @param debugEnabled - whether the node should log debug messages. (optional, defaults to false) * @param basePath - the base path to use for the temporary folder. (optional, defaults to os.tmpdir()) * */ // #endregion launchNode-launchNodeOptions @@ -146,12 +144,11 @@ export const launchNode = async ({ ip, port, args = [], - fuelCorePath = process.env.FUEL_CORE_PATH ?? undefined, + fuelCorePath = process.env.FUEL_CORE_PATH || undefined, loggingEnabled = true, - debugEnabled = false, basePath, snapshotConfig = defaultSnapshotConfigs, -}: LaunchNodeOptions): LaunchNodeResult => +}: LaunchNodeOptions = {}): LaunchNodeResult => // eslint-disable-next-line no-async-promise-executor new Promise(async (resolve, reject) => { // filter out the flags chain, consensus-key, db-type, and poa-instant. we don't want to pass them twice to fuel-core. see line 214. @@ -176,7 +173,7 @@ export const launchNode = async ({ // This string is logged by the client when the node has successfully started. We use it to know when to resolve. const graphQLStartSubstring = 'Binding GraphQL provider to'; - const command = fuelCorePath ?? 'fuel-core'; + const command = fuelCorePath || 'fuel-core'; const ipToUse = ip || '0.0.0.0'; @@ -235,17 +232,14 @@ export const launchNode = async ({ '--debug', ...remainingArgs, ].flat(), - { - stdio: 'pipe', - } + { stdio: 'pipe' } ); if (loggingEnabled) { - child.stderr.pipe(process.stderr); - } - - if (debugEnabled) { - child.stdout.pipe(process.stdout); + child.stderr.on('data', (chunk) => { + // eslint-disable-next-line no-console + console.log(chunk.toString()); + }); } const cleanupConfig: KillNodeParams = { @@ -278,7 +272,9 @@ export const launchNode = async ({ }); } if (/error/i.test(text)) { - reject(text.toString()); + // eslint-disable-next-line no-console + console.log(text); + reject(new FuelError(FuelError.CODES.NODE_LAUNCH_FAILED, text)); } }); diff --git a/packages/create-fuels/src/utils/logger.test.ts b/packages/create-fuels/src/utils/logger.test.ts index b48d5f2d66b..3a3344abbd1 100644 --- a/packages/create-fuels/src/utils/logger.test.ts +++ b/packages/create-fuels/src/utils/logger.test.ts @@ -1,26 +1,17 @@ +import chalk from 'chalk'; + import * as loggerMod from './logger'; +import { configureLogging, debug, error, log, loggingConfig, warn } from './logger'; /** * @group node */ describe('logger', () => { - const { configureLogging, loggingConfig } = loggerMod; - const loggingBackup = structuredClone(loggingConfig); - const reset = () => { - vi.restoreAllMocks(); + beforeEach(() => { configureLogging(loggingBackup); - }; - - beforeEach(reset); - afterAll(reset); - - function mockStdIO() { - const err = vi.spyOn(process.stderr, 'write').mockReturnValue(true); - const out = vi.spyOn(process.stdout, 'write').mockReturnValue(true); - return { err, out }; - } + }); test('should configure logging', () => { configureLogging({ isLoggingEnabled: true, isDebugEnabled: false }); @@ -37,52 +28,49 @@ describe('logger', () => { }); test('should log', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); configureLogging({ isLoggingEnabled: true, isDebugEnabled: false }); - loggerMod.log('any message'); - expect(out).toHaveBeenCalledTimes(1); - expect(out.mock.calls[0][0]).toMatch(/any message/); - expect(err).toHaveBeenCalledTimes(0); + log('message'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith('message'); }); test('should not log', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); + configureLogging({ isLoggingEnabled: false, isDebugEnabled: false }); - loggerMod.log('any message'); - expect(out).toHaveBeenCalledTimes(0); - expect(err).toHaveBeenCalledTimes(0); + log('any message'); + expect(logSpy).not.toHaveBeenCalled(); }); test('should debug', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); + configureLogging({ isLoggingEnabled: true, isDebugEnabled: true }); - loggerMod.debug('any debug message'); - expect(out).toHaveBeenCalledTimes(1); - expect(out.mock.calls[0][0]).toMatch(/any debug message/); - expect(err).toHaveBeenCalledTimes(0); + debug('message'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith('message'); }); test('should not log', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); + configureLogging({ isLoggingEnabled: false, isDebugEnabled: false }); loggerMod.debug('any debug message'); - expect(out).toHaveBeenCalledTimes(0); - expect(err).toHaveBeenCalledTimes(0); + expect(logSpy).toHaveBeenCalledTimes(0); }); test('should warn', () => { - const { err, out } = mockStdIO(); - loggerMod.warn('any warn message'); - expect(out).toHaveBeenCalledTimes(1); - expect(out.mock.calls[0][0]).toMatch(/any warn message/); - expect(err).toHaveBeenCalledTimes(0); + const logSpy = vi.spyOn(console, 'log'); + warn('message1', 'message2'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith(chalk.yellow('message1 message2')); }); test('should error', () => { - const { err, out } = mockStdIO(); - loggerMod.error('any error message'); - expect(out).toHaveBeenCalledTimes(0); - expect(err).toHaveBeenCalledTimes(1); - expect(err.mock.calls[0][0]).toMatch(/any error message/); + const logSpy = vi.spyOn(console, 'log'); + error('message1', 'message2'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith(chalk.red('message1 message2')); }); }); diff --git a/packages/create-fuels/src/utils/logger.ts b/packages/create-fuels/src/utils/logger.ts index 39b7ee94b61..d4ffaf26d20 100644 --- a/packages/create-fuels/src/utils/logger.ts +++ b/packages/create-fuels/src/utils/logger.ts @@ -12,7 +12,8 @@ export function configureLogging(params: { isDebugEnabled: boolean; isLoggingEna export function log(...data: unknown[]) { if (loggingConfig.isLoggingEnabled) { - process.stdout.write(`${data.join(' ')}\n`); + // eslint-disable-next-line no-console + console.log(data.join(' ')); } } @@ -23,9 +24,10 @@ export function debug(...data: unknown[]) { } export function error(...data: unknown[]) { - process.stderr.write(`${chalk.red(data.join(' '))}\n`); + // eslint-disable-next-line no-console + console.log(chalk.red(data.join(' '))); } export function warn(...data: unknown[]) { - log(`${chalk.yellow(data.join(' '))}\n`); + log(chalk.yellow(data.join(' '))); } diff --git a/packages/errors/src/error-codes.ts b/packages/errors/src/error-codes.ts index ff92629a3ab..3a7927f71e1 100644 --- a/packages/errors/src/error-codes.ts +++ b/packages/errors/src/error-codes.ts @@ -101,6 +101,9 @@ export enum ErrorCode { // graphql STREAM_PARSING_ERROR = 'stream-parsing-error', + // launchNode + NODE_LAUNCH_FAILED = 'node-launch-failed', + // Unknown UNKNOWN = 'unknown', } diff --git a/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts b/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts index 2099ee395be..fe768c97167 100644 --- a/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts +++ b/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts @@ -57,4 +57,6 @@ export const expectToThrowFuelError = async ( expect(thrownError.name).toEqual('FuelError'); expect(thrownError).toMatchObject(expectedError); + + return thrownError; }; diff --git a/packages/fuels/src/bin.ts b/packages/fuels/src/bin.ts index 35a32ee2bc9..6737a71abf2 100644 --- a/packages/fuels/src/bin.ts +++ b/packages/fuels/src/bin.ts @@ -2,7 +2,10 @@ import { error } from './cli/utils/logger'; import { run } from './run'; try { - run(process.argv).catch(process.stderr.write); + run(process.argv).catch((x) => { + // eslint-disable-next-line no-console + console.log(x); + }); } catch (err: unknown) { error((err as Error)?.message || err); process.exit(1); diff --git a/packages/fuels/src/cli/commands/build/buildSwayProgram.test.ts b/packages/fuels/src/cli/commands/build/buildSwayProgram.test.ts index 94e32731fe1..f10c9968f35 100644 --- a/packages/fuels/src/cli/commands/build/buildSwayProgram.test.ts +++ b/packages/fuels/src/cli/commands/build/buildSwayProgram.test.ts @@ -18,6 +18,8 @@ vi.mock('child_process', async () => { * @group node */ describe('buildSwayPrograms', () => { + const log = 'log'; + const debugLog = 'debug log'; beforeEach(() => { mockLogger(); }); @@ -33,10 +35,14 @@ describe('buildSwayPrograms', () => { } }), stderr: { - pipe: vi.fn(), + on: vi.fn((eventName: string, cb: (...args: unknown[]) => void) => { + cb(log); + }), }, stdout: { - pipe: vi.fn(), + on: vi.fn((eventName: string, cb: (...args: unknown[]) => void) => { + cb(debugLog); + }), }, } as unknown as childProcessMod.ChildProcessWithoutNullStreams; @@ -48,31 +54,28 @@ describe('buildSwayPrograms', () => { }; } - test('should pipe stdout', async () => { - const { spawn, spawnMocks } = mockAll(); + test('logs to console when logging is enabled', async () => { + const { spawn } = mockAll(); + const logSpy = vi.spyOn(console, 'log'); - vi.spyOn(process.stdout, 'write').mockReturnValue(true); configureLogging({ isLoggingEnabled: true, isDebugEnabled: false }); await buildSwayProgram(fuelsConfig, '/any/workspace/path'); expect(spawn).toHaveBeenCalledTimes(1); - expect(spawnMocks.stderr.pipe).toHaveBeenCalledTimes(1); - expect(spawnMocks.stdout.pipe).toHaveBeenCalledTimes(0); - }); - test('should pipe stdout and stderr', async () => { - const { spawn, spawnMocks } = mockAll(); + expect(logSpy).toHaveBeenCalledWith(log); + expect(logSpy).not.toHaveBeenCalledWith(debugLog); + }); - vi.spyOn(process.stderr, 'write').mockReturnValue(true); - vi.spyOn(process.stdout, 'write').mockReturnValue(true); + test('logs debug to console when debug is enabled', async () => { + const { spawn } = mockAll(); + const logSpy = vi.spyOn(console, 'log'); configureLogging({ isLoggingEnabled: true, isDebugEnabled: true }); await buildSwayProgram(fuelsConfig, '/any/workspace/path'); expect(spawn).toHaveBeenCalledTimes(1); - expect(spawnMocks.stderr.pipe).toHaveBeenCalledTimes(1); - expect(spawnMocks.stdout.pipe).toHaveBeenCalledTimes(1); - expect(spawnMocks.on).toHaveBeenCalledTimes(2); + expect(logSpy).toHaveBeenCalledWith(debugLog); }); }); diff --git a/packages/fuels/src/cli/commands/build/buildSwayProgram.ts b/packages/fuels/src/cli/commands/build/buildSwayProgram.ts index 89b29e15f81..58f989a8fdd 100644 --- a/packages/fuels/src/cli/commands/build/buildSwayProgram.ts +++ b/packages/fuels/src/cli/commands/build/buildSwayProgram.ts @@ -11,13 +11,16 @@ export const buildSwayProgram = async (config: FuelsConfig, path: string) => { return new Promise((resolve, reject) => { const args = ['build', '-p', path].concat(config.forcBuildFlags); const forc = spawn(config.forcPath, args, { stdio: 'pipe' }); - if (loggingConfig.isLoggingEnabled) { - forc.stderr?.pipe(process.stderr); + // eslint-disable-next-line no-console + forc.stderr?.on('data', (chunk) => console.log(chunk.toString())); } if (loggingConfig.isDebugEnabled) { - forc.stdout?.pipe(process.stdout); + forc.stdout?.on('data', (chunk) => { + // eslint-disable-next-line no-console + console.log(chunk.toString()); + }); } const onExit = onForcExit(resolve, reject); diff --git a/packages/fuels/src/cli/commands/dev/autoStartFuelCore.ts b/packages/fuels/src/cli/commands/dev/autoStartFuelCore.ts index 26e4506b233..b82496f56b9 100644 --- a/packages/fuels/src/cli/commands/dev/autoStartFuelCore.ts +++ b/packages/fuels/src/cli/commands/dev/autoStartFuelCore.ts @@ -44,7 +44,6 @@ export const autoStartFuelCore = async (config: FuelsConfig) => { ip: bindIp, port: port.toString(), loggingEnabled: loggingConfig.isLoggingEnabled, - debugEnabled: loggingConfig.isDebugEnabled, basePath: config.basePath, fuelCorePath: config.fuelCorePath, }); diff --git a/packages/fuels/src/cli/commands/init/index.ts b/packages/fuels/src/cli/commands/init/index.ts index 433d6b5fc07..380d6fbb7e2 100644 --- a/packages/fuels/src/cli/commands/init/index.ts +++ b/packages/fuels/src/cli/commands/init/index.ts @@ -38,7 +38,8 @@ export function init(program: Command) { if (noneIsInformed) { // mimicking commander property validation - process.stdout.write(`error: required option '-w, --workspace ' not specified\r`); + // eslint-disable-next-line no-console + console.log(`error: required option '-w, --workspace ' not specified\r`); process.exit(1); } else { const fuelsConfigPath = join(path, 'fuels.config.ts'); diff --git a/packages/fuels/src/cli/utils/logger.test.ts b/packages/fuels/src/cli/utils/logger.test.ts index b48d5f2d66b..7c8cd22069c 100644 --- a/packages/fuels/src/cli/utils/logger.test.ts +++ b/packages/fuels/src/cli/utils/logger.test.ts @@ -1,27 +1,16 @@ +import chalk from 'chalk'; + import * as loggerMod from './logger'; +import { configureLogging, debug, error, log, loggingConfig, warn } from './logger'; /** * @group node */ describe('logger', () => { - const { configureLogging, loggingConfig } = loggerMod; - const loggingBackup = structuredClone(loggingConfig); - - const reset = () => { - vi.restoreAllMocks(); + beforeEach(() => { configureLogging(loggingBackup); - }; - - beforeEach(reset); - afterAll(reset); - - function mockStdIO() { - const err = vi.spyOn(process.stderr, 'write').mockReturnValue(true); - const out = vi.spyOn(process.stdout, 'write').mockReturnValue(true); - return { err, out }; - } - + }); test('should configure logging', () => { configureLogging({ isLoggingEnabled: true, isDebugEnabled: false }); expect(loggingConfig.isLoggingEnabled).toEqual(true); @@ -37,52 +26,49 @@ describe('logger', () => { }); test('should log', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); configureLogging({ isLoggingEnabled: true, isDebugEnabled: false }); - loggerMod.log('any message'); - expect(out).toHaveBeenCalledTimes(1); - expect(out.mock.calls[0][0]).toMatch(/any message/); - expect(err).toHaveBeenCalledTimes(0); + log('message'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith('message'); }); test('should not log', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); + configureLogging({ isLoggingEnabled: false, isDebugEnabled: false }); - loggerMod.log('any message'); - expect(out).toHaveBeenCalledTimes(0); - expect(err).toHaveBeenCalledTimes(0); + log('any message'); + expect(logSpy).not.toHaveBeenCalled(); }); test('should debug', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); + configureLogging({ isLoggingEnabled: true, isDebugEnabled: true }); - loggerMod.debug('any debug message'); - expect(out).toHaveBeenCalledTimes(1); - expect(out.mock.calls[0][0]).toMatch(/any debug message/); - expect(err).toHaveBeenCalledTimes(0); + debug('message'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith('message'); }); test('should not log', () => { - const { err, out } = mockStdIO(); + const logSpy = vi.spyOn(console, 'log'); + configureLogging({ isLoggingEnabled: false, isDebugEnabled: false }); loggerMod.debug('any debug message'); - expect(out).toHaveBeenCalledTimes(0); - expect(err).toHaveBeenCalledTimes(0); + expect(logSpy).toHaveBeenCalledTimes(0); }); test('should warn', () => { - const { err, out } = mockStdIO(); - loggerMod.warn('any warn message'); - expect(out).toHaveBeenCalledTimes(1); - expect(out.mock.calls[0][0]).toMatch(/any warn message/); - expect(err).toHaveBeenCalledTimes(0); + const logSpy = vi.spyOn(console, 'log'); + warn('message1', 'message2'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith(chalk.yellow('message1 message2')); }); test('should error', () => { - const { err, out } = mockStdIO(); - loggerMod.error('any error message'); - expect(out).toHaveBeenCalledTimes(0); - expect(err).toHaveBeenCalledTimes(1); - expect(err.mock.calls[0][0]).toMatch(/any error message/); + const logSpy = vi.spyOn(console, 'log'); + error('message1', 'message2'); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith(chalk.red('message1 message2')); }); }); diff --git a/packages/fuels/src/cli/utils/logger.ts b/packages/fuels/src/cli/utils/logger.ts index 39b7ee94b61..d4ffaf26d20 100644 --- a/packages/fuels/src/cli/utils/logger.ts +++ b/packages/fuels/src/cli/utils/logger.ts @@ -12,7 +12,8 @@ export function configureLogging(params: { isDebugEnabled: boolean; isLoggingEna export function log(...data: unknown[]) { if (loggingConfig.isLoggingEnabled) { - process.stdout.write(`${data.join(' ')}\n`); + // eslint-disable-next-line no-console + console.log(data.join(' ')); } } @@ -23,9 +24,10 @@ export function debug(...data: unknown[]) { } export function error(...data: unknown[]) { - process.stderr.write(`${chalk.red(data.join(' '))}\n`); + // eslint-disable-next-line no-console + console.log(chalk.red(data.join(' '))); } export function warn(...data: unknown[]) { - log(`${chalk.yellow(data.join(' '))}\n`); + log(chalk.yellow(data.join(' '))); } diff --git a/packages/fuels/src/setup-launch-node-server.ts b/packages/fuels/src/setup-launch-node-server.ts index 87001995544..7c880b7f7b2 100644 --- a/packages/fuels/src/setup-launch-node-server.ts +++ b/packages/fuels/src/setup-launch-node-server.ts @@ -36,7 +36,6 @@ const server = http.createServer(async (req, res) => { const node = await launchNode({ port: '0', loggingEnabled: false, - debugEnabled: false, ...body, fuelCorePath: 'fuels-core', }); diff --git a/packages/fuels/test/features/init.test.ts b/packages/fuels/test/features/init.test.ts index b266516a025..05095c9a9f3 100644 --- a/packages/fuels/test/features/init.test.ts +++ b/packages/fuels/test/features/init.test.ts @@ -85,7 +85,7 @@ describe('init', () => { }); it('should error if no inputs/workspace is supplied', async () => { - const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true); + const logSpy = vi.spyOn(console, 'log'); const exit = vi.spyOn(process, 'exit').mockResolvedValue({} as never); await runCommand(Commands.init, ['--path', paths.root, '-o', paths.outputDir]); @@ -93,8 +93,8 @@ describe('init', () => { expect(exit).toHaveBeenCalledTimes(1); expect(exit).toHaveBeenCalledWith(1); - expect(write).toHaveBeenCalledTimes(1); - expect(write).toHaveBeenCalledWith( + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith( `error: required option '-w, --workspace ' not specified\r` ); }); diff --git a/scripts/verify-forc-version.ts b/scripts/verify-forc-version.ts index 9d6a0e764b1..44a18111f73 100644 --- a/scripts/verify-forc-version.ts +++ b/scripts/verify-forc-version.ts @@ -7,4 +7,5 @@ import path from 'path'; if (forcVersion.indexOf('git:') !== -1) { throw new Error('Cannot publish from a git branch. Please use a release directly.'); } -})().catch(process.stderr.write); + // eslint-disable-next-line no-console +})().catch((x) => console.log(x));