Skip to content

Commit

Permalink
fix: 🔊 match logs with Next 13.5 style
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmannZ committed Oct 11, 2023
1 parent 8329c66 commit f81856d
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 27 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ module.exports = {
'<rootDir>/*.js',
'<rootDir>/build',
'<rootDir>/examples',
'<rootDir>/src/lib',
],
};
65 changes: 65 additions & 0 deletions src/helpers/log.spec.ts
Original file line number Diff line number Diff line change
@@ -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)',
);
});
});
44 changes: 44 additions & 0 deletions src/helpers/log.ts
Original file line number Diff line number Diff line change
@@ -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);
}
86 changes: 86 additions & 0 deletions src/lib/picocolors.ts
Original file line number Diff line number Diff line change
@@ -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;
39 changes: 18 additions & 21 deletions src/utils/make-env-public.spec.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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`,
);
});

Expand All @@ -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`,
);
});
});
12 changes: 6 additions & 6 deletions src/utils/make-env-public.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
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;
}

// 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}`;

process.env[prefixedKey] = process.env[key];

// eslint-disable-next-line no-console
console.info(`prefixed environment variable '${key}'.`);
event(`Prefixed environment variable '${key}'`);
}

/**
Expand Down

0 comments on commit f81856d

Please sign in to comment.