From f81856db282005bcea504f628a35e32050ee2cf5 Mon Sep 17 00:00:00 2001 From: Zino Hofmann Date: Wed, 11 Oct 2023 17:49:10 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=94=8A=20match=20logs=20with=20Nex?= =?UTF-8?q?t=2013.5=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.js | 1 + src/helpers/log.spec.ts | 65 +++++++++++++++++++++++ src/helpers/log.ts | 44 ++++++++++++++++ src/lib/picocolors.ts | 86 +++++++++++++++++++++++++++++++ src/utils/make-env-public.spec.ts | 39 +++++++------- src/utils/make-env-public.ts | 12 ++--- 6 files changed, 220 insertions(+), 27 deletions(-) create mode 100644 src/helpers/log.spec.ts create mode 100644 src/helpers/log.ts create mode 100644 src/lib/picocolors.ts diff --git a/jest.config.js b/jest.config.js index 075d3a17..4ffc0e31 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,5 +14,6 @@ module.exports = { '/*.js', '/build', '/examples', + '/src/lib', ], }; diff --git a/src/helpers/log.spec.ts b/src/helpers/log.spec.ts new file mode 100644 index 00000000..3b271f36 --- /dev/null +++ b/src/helpers/log.spec.ts @@ -0,0 +1,65 @@ +import { error, event, info, prefixes, warn } from './log'; + +const logSpy = jest.spyOn(console, 'log'); +const warnSpy = jest.spyOn(console, 'warn'); +const errorSpy = jest.spyOn(console, 'error'); + +beforeAll(() => { + logSpy.mockImplementation(); + warnSpy.mockImplementation(); + errorSpy.mockImplementation(); +}); + +afterAll(() => { + logSpy.mockRestore(); + warnSpy.mockRestore(); + errorSpy.mockRestore(); +}); + +describe('error', () => { + it('should log an error message', () => { + error('foo'); + + expect(errorSpy).toHaveBeenCalledWith( + ` ${prefixes.error}`, + 'foo', + '(next-runtime-env)', + ); + }); +}); + +describe('warn', () => { + it('should log a warning message', () => { + warn('foo'); + + expect(warnSpy).toHaveBeenCalledWith( + ` ${prefixes.warn}`, + 'foo', + '(next-runtime-env)', + ); + }); +}); + +describe('info', () => { + it('should log an info message', () => { + info('foo'); + + expect(logSpy).toHaveBeenCalledWith( + ` ${prefixes.info}`, + 'foo', + '(next-runtime-env)', + ); + }); +}); + +describe('event', () => { + it('should log an event message', () => { + event('foo'); + + expect(logSpy).toHaveBeenCalledWith( + ` ${prefixes.event}`, + 'foo', + '(next-runtime-env)', + ); + }); +}); diff --git a/src/helpers/log.ts b/src/helpers/log.ts new file mode 100644 index 00000000..05eaa3de --- /dev/null +++ b/src/helpers/log.ts @@ -0,0 +1,44 @@ +import { bold, green, red, white, yellow } from '../lib/picocolors'; + +export const prefixes = { + error: red(bold('⨯')), + warn: yellow(bold('⚠')), + info: white(bold(' ')), + event: green(bold('✓')), +} as const; + +const suffix = '(next-runtime-env)'; + +const LOGGING_METHOD = { + log: 'log', + warn: 'warn', + error: 'error', +} as const; + +function prefixedLog(prefixType: keyof typeof prefixes, message: string) { + const consoleMethod: keyof typeof LOGGING_METHOD = + prefixType in LOGGING_METHOD + ? LOGGING_METHOD[prefixType as keyof typeof LOGGING_METHOD] + : 'log'; + + const prefix = prefixes[prefixType]; + + // eslint-disable-next-line no-console + console[consoleMethod](` ${prefix}`, message, suffix); +} + +export function error(message: string) { + prefixedLog('error', message); +} + +export function warn(message: string) { + prefixedLog('warn', message); +} + +export function info(message: string) { + prefixedLog('info', message); +} + +export function event(message: string) { + prefixedLog('event', message); +} diff --git a/src/lib/picocolors.ts b/src/lib/picocolors.ts new file mode 100644 index 00000000..e8c74f71 --- /dev/null +++ b/src/lib/picocolors.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-bitwise */ +/* eslint-disable prefer-template */ + +// ISC License + +// Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov + +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. + +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// https://github.com/alexeyraspopov/picocolors/blob/b6261487e7b81aaab2440e397a356732cad9e342/picocolors.js#L1 + +const { env, stdout } = globalThis?.process ?? {}; + +const enabled = + env && + !env.NO_COLOR && + (env.FORCE_COLOR || (stdout?.isTTY && !env.CI && env.TERM !== 'dumb')); + +const replaceClose = ( + str: string, + close: string, + replace: string, + index: number, +): string => { + const start = str.substring(0, index) + replace; + const end = str.substring(index + close.length); + const nextIndex = end.indexOf(close); + return ~nextIndex + ? start + replaceClose(end, close, replace, nextIndex) + : start + end; +}; + +const formatter = + (open: string, close: string, replace = open) => + (input: string) => { + const string = '' + input; + const index = string.indexOf(close, open.length); + return ~index + ? open + replaceClose(string, close, replace, index) + close + : open + string + close; + }; + +export const reset = enabled ? (s: string) => `\x1b[0m${s}\x1b[0m` : String; +export const bold = enabled + ? formatter('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m') + : String; +export const dim = enabled + ? formatter('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m') + : String; +export const italic = enabled ? formatter('\x1b[3m', '\x1b[23m') : String; +export const underline = enabled ? formatter('\x1b[4m', '\x1b[24m') : String; +export const inverse = enabled ? formatter('\x1b[7m', '\x1b[27m') : String; +export const hidden = enabled ? formatter('\x1b[8m', '\x1b[28m') : String; +export const strikethrough = enabled + ? formatter('\x1b[9m', '\x1b[29m') + : String; +export const black = enabled ? formatter('\x1b[30m', '\x1b[39m') : String; +export const red = enabled ? formatter('\x1b[31m', '\x1b[39m') : String; +export const green = enabled ? formatter('\x1b[32m', '\x1b[39m') : String; +export const yellow = enabled ? formatter('\x1b[33m', '\x1b[39m') : String; +export const blue = enabled ? formatter('\x1b[34m', '\x1b[39m') : String; +export const magenta = enabled ? formatter('\x1b[35m', '\x1b[39m') : String; +export const purple = enabled + ? formatter('\x1b[38;2;173;127;168m', '\x1b[39m') + : String; +export const cyan = enabled ? formatter('\x1b[36m', '\x1b[39m') : String; +export const white = enabled ? formatter('\x1b[37m', '\x1b[39m') : String; +export const gray = enabled ? formatter('\x1b[90m', '\x1b[39m') : String; +export const bgBlack = enabled ? formatter('\x1b[40m', '\x1b[49m') : String; +export const bgRed = enabled ? formatter('\x1b[41m', '\x1b[49m') : String; +export const bgGreen = enabled ? formatter('\x1b[42m', '\x1b[49m') : String; +export const bgYellow = enabled ? formatter('\x1b[43m', '\x1b[49m') : String; +export const bgBlue = enabled ? formatter('\x1b[44m', '\x1b[49m') : String; +export const bgMagenta = enabled ? formatter('\x1b[45m', '\x1b[49m') : String; +export const bgCyan = enabled ? formatter('\x1b[46m', '\x1b[49m') : String; +export const bgWhite = enabled ? formatter('\x1b[47m', '\x1b[49m') : String; diff --git a/src/utils/make-env-public.spec.ts b/src/utils/make-env-public.spec.ts index 0bc3d76b..423ffdab 100644 --- a/src/utils/make-env-public.spec.ts +++ b/src/utils/make-env-public.spec.ts @@ -1,20 +1,17 @@ import { makeEnvPublic } from './make-env-public'; -const warnSpy = jest.spyOn(console, 'warn'); -const infoSpy = jest.spyOn(console, 'info'); +const warnMock = jest.fn(); +const eventMock = jest.fn(); -beforeAll(() => { - warnSpy.mockImplementation(); - infoSpy.mockImplementation(); -}); - -afterAll(() => { - warnSpy.mockRestore(); - infoSpy.mockRestore(); -}); +jest.mock('../helpers/log', () => ({ + warn: (...args: unknown[]) => warnMock(...args), + event: (...args: unknown[]) => eventMock(...args), +})); describe('makeEnvPublic()', () => { afterEach(() => { + jest.clearAllMocks(); + delete process.env.FOO; delete process.env.BAR; delete process.env.BAZ; @@ -57,24 +54,24 @@ describe('makeEnvPublic()', () => { makeEnvPublic('FOO'); makeEnvPublic(['BAR', 'BAZ']); - expect(infoSpy).toHaveBeenCalledWith( - `prefixed environment variable 'FOO'.`, + expect(eventMock).toHaveBeenCalledWith( + `Prefixed environment variable 'FOO'`, ); - expect(infoSpy).toHaveBeenCalledWith( - `prefixed environment variable 'BAR'.`, + expect(eventMock).toHaveBeenCalledWith( + `Prefixed environment variable 'BAR'`, ); - expect(infoSpy).toHaveBeenCalledWith( - `prefixed environment variable 'BAZ'.`, + expect(eventMock).toHaveBeenCalledWith( + `Prefixed environment variable 'BAZ'`, ); }); it('should warn when prefixing a variable that is not available in process.env', () => { makeEnvPublic('FOO'); - expect(warnSpy).toHaveBeenCalledWith( - `skipped prefixing environment variable 'FOO'. Variable not in process.env.`, + expect(warnMock).toHaveBeenCalledWith( + `Skipped prefixing environment variable 'FOO'. Variable not in process.env`, ); }); @@ -83,8 +80,8 @@ describe('makeEnvPublic()', () => { makeEnvPublic('NEXT_PUBLIC_FOO'); - expect(warnSpy).toHaveBeenCalledWith( - `environment variable 'NEXT_PUBLIC_FOO' is already public.`, + expect(warnMock).toHaveBeenCalledWith( + `Environment variable 'NEXT_PUBLIC_FOO' is already public`, ); }); }); diff --git a/src/utils/make-env-public.ts b/src/utils/make-env-public.ts index 0b50465c..455a5cae 100644 --- a/src/utils/make-env-public.ts +++ b/src/utils/make-env-public.ts @@ -1,9 +1,10 @@ +import { event, warn } from '../helpers/log'; + function prefixKey(key: string) { // Check if key is available in process.env. if (!process.env[key]) { - // eslint-disable-next-line no-console - console.warn( - `skipped prefixing environment variable '${key}'. Variable not in process.env.`, + warn( + `Skipped prefixing environment variable '${key}'. Variable not in process.env`, ); return; @@ -11,8 +12,7 @@ function prefixKey(key: string) { // Check if key is already public. if (/^NEXT_PUBLIC_/i.test(key)) { - // eslint-disable-next-line no-console - console.warn(`environment variable '${key}' is already public.`); + warn(`Environment variable '${key}' is already public`); } const prefixedKey = `NEXT_PUBLIC_${key}`; @@ -20,7 +20,7 @@ function prefixKey(key: string) { process.env[prefixedKey] = process.env[key]; // eslint-disable-next-line no-console - console.info(`prefixed environment variable '${key}'.`); + event(`Prefixed environment variable '${key}'`); } /**