Skip to content
4 changes: 4 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { updateCommand } from './commands/update';
import { webhooksCommand } from './commands/webhooks/index';
import { whoamiCommand } from './commands/whoami';
import { setupCliExitHandler } from './lib/cli-exit';
import { printBannerPlain } from './lib/logo';
import { errorMessage, outputError } from './lib/output';
import { trackCommand } from './lib/telemetry';
import { checkForUpdates } from './lib/update-check';
Expand Down Expand Up @@ -116,6 +117,9 @@ ${pc.gray('Examples:')}
`,
)
.action(() => {
if (process.stdout.isTTY) {
printBannerPlain();
}
const opts = program.opts();
if (opts.apiKey) {
outputError(
Expand Down
16 changes: 16 additions & 0 deletions src/lib/logo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const LOGO_LINES = [
' ██████╗ ███████╗███████╗███████╗███╗ ██╗██████╗ ',
' ██╔══██╗██╔════╝██╔════╝██╔════╝████╗ ██║██╔══██╗',
' ██████╔╝█████╗ ███████╗█████╗ ██╔██╗ ██║██║ ██║',
' ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║╚██╗██║██║ ██║',
' ██║ ██║███████╗███████║███████╗██║ ╚████║██████╔╝',
' ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝ ',
];

export function printBannerPlain(): void {
process.stdout.write('\n');
for (const line of LOGO_LINES) {
process.stdout.write(`${line}\n`);
}
process.stdout.write('\n');
}
37 changes: 37 additions & 0 deletions tests/lib/logo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
afterEach,
describe,
expect,
type MockInstance,
test,
vi,
} from 'vitest';
import { printBannerPlain } from '../../src/lib/logo';

describe('printBannerPlain', () => {
let writeSpy: MockInstance;

afterEach(() => {
writeSpy?.mockRestore();
});

test('writes ASCII logo to stdout', () => {
const chunks: string[] = [];
writeSpy = vi
.spyOn(process.stdout, 'write')
.mockImplementation((chunk: unknown) => {
chunks.push(
typeof chunk === 'string'
? chunk
: new TextDecoder().decode(chunk as Uint8Array),
);
return true;
});

printBannerPlain();

const out = chunks.join('');
expect(out).toContain('██████╗');
expect(out).toContain('█');
});
});
35 changes: 35 additions & 0 deletions tests/welcome.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type ExecFileSyncOptions, execFileSync } from 'node:child_process';
import { resolve } from 'node:path';
import { describe, expect, test } from 'vitest';

const CLI = resolve(import.meta.dirname, '../src/cli.ts');

const noUpdateEnv = {
...process.env,
RESEND_NO_UPDATE_NOTIFIER: '1',
NO_COLOR: '1',
};

describe('no-args welcome', () => {
test('exits 0 and shows help when invoked with no arguments', () => {
const execOptions: ExecFileSyncOptions = {
encoding: 'utf-8',
timeout: 10_000,
env: noUpdateEnv,
...(process.platform === 'win32' ? { shell: true } : {}),
};
const stdout = execFileSync('npx', ['tsx', CLI], execOptions) as string;
expect(stdout).toContain('Usage: resend');
});

test('skips banner when stdout is not a TTY', () => {
const execOptions: ExecFileSyncOptions = {
encoding: 'utf-8',
timeout: 10_000,
env: noUpdateEnv,
...(process.platform === 'win32' ? { shell: true } : {}),
};
const stdout = execFileSync('npx', ['tsx', CLI], execOptions) as string;
expect(stdout).not.toContain('██████╗');
});
});