From 2238838645bcf24a46060def62df6d281cf471b2 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 3 May 2019 13:04:04 +0100 Subject: [PATCH 1/4] chore(aws-cdk): Version controlled version.ts instead of generated. Switching version.ts from generated to version controlled so that it can be expanded with more functions. --- packages/aws-cdk/.gitignore | 4 ++-- packages/aws-cdk/bin/cdk.ts | 6 +++--- packages/aws-cdk/generate.sh | 13 ++++++------- packages/aws-cdk/lib/commands/doctor.ts | 4 ++-- packages/aws-cdk/lib/version.ts | 9 +++++++++ 5 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 packages/aws-cdk/lib/version.ts diff --git a/packages/aws-cdk/.gitignore b/packages/aws-cdk/.gitignore index cd4b6d8758860..5cac1a6a13095 100644 --- a/packages/aws-cdk/.gitignore +++ b/packages/aws-cdk/.gitignore @@ -5,11 +5,11 @@ node_modules dist # Generated by generate.sh -lib/version.ts +build-info.json .LAST_BUILD .nyc_output coverage .nycrc .LAST_PACKAGE -*.snk \ No newline at end of file +*.snk diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 421ff6dc2831b..a2081a76ef74d 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -20,7 +20,7 @@ import { PluginHost } from '../lib/plugin'; import { parseRenames } from '../lib/renames'; import { serializeStructure } from '../lib/serialize'; import { Configuration, Settings } from '../lib/settings'; -import { VERSION } from '../lib/version'; +import { DISPLAY_VERSION } from '../lib/version'; // tslint:disable-next-line:no-var-requires const promptly = require('promptly'); @@ -76,7 +76,7 @@ async function parseCommandLineArguments() { .option('language', { type: 'string', alias: 'l', desc: 'the language to be used for the new project (default can be configured in ~/.cdk.json)', choices: initTemplateLanuages }) .option('list', { type: 'boolean', desc: 'list the available templates' })) .commandDir('../lib/commands', { exclude: /^_.*/ }) - .version(VERSION) + .version(DISPLAY_VERSION) .demandCommand(1, '') // just print help .help() .alias('h', 'help') @@ -97,7 +97,7 @@ async function initCommandLine() { setVerbose(); } - debug('CDK toolkit version:', VERSION); + debug('CDK toolkit version:', DISPLAY_VERSION); debug('Command line arguments:', argv); const aws = new SDK({ diff --git a/packages/aws-cdk/generate.sh b/packages/aws-cdk/generate.sh index 476fe8618945d..e5ea04d12acd8 100755 --- a/packages/aws-cdk/generate.sh +++ b/packages/aws-cdk/generate.sh @@ -8,10 +8,9 @@ if [ -z "${commit}" ]; then commit="$(git rev-parse --verify HEAD)" fi -cat > lib/version.ts < build-info.json < boolean | Promise> = [ // ### Verifications ### function displayVersionInformation() { - print(`ℹ️ CDK Version: ${colors.green(VERSION)}`); + print(`ℹ️ CDK Version: ${colors.green(DISPLAY_VERSION)}`); return true; } diff --git a/packages/aws-cdk/lib/version.ts b/packages/aws-cdk/lib/version.ts new file mode 100644 index 0000000000000..dcad06652d6c2 --- /dev/null +++ b/packages/aws-cdk/lib/version.ts @@ -0,0 +1,9 @@ +export const DISPLAY_VERSION = `${versionNumber()} (build ${commit()})`; + +function versionNumber(): string { + return require('../package.json').version.replace(/\+[0-9a-f]+$/, ''); +} + +function commit(): string { + return require('../build-info.json').commit; +} \ No newline at end of file From 3d451fc0c36df3d25ef9569a2f2e6b3190d6e975 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Sun, 5 May 2019 17:44:33 +0100 Subject: [PATCH 2/4] feat(aws-cdk): Announce availability of new CDK version Check, once a day, if a newer CDK version available in npm and announce it's availability at the end of a significant command. TESTING: * New unit tests for version.ts * Downgraded version number in package.json and verified that the expected message is printed. * Verified that the file cache throttles the check to run only once per day. closes #297 --- packages/aws-cdk/.gitignore | 1 + packages/aws-cdk/bin/cdk.ts | 29 ++++---- packages/aws-cdk/lib/commands/context.ts | 2 + packages/aws-cdk/lib/commands/doctor.ts | 5 +- packages/aws-cdk/lib/version.ts | 89 ++++++++++++++++++++++++ packages/aws-cdk/test/test.version.ts | 52 ++++++++++++++ 6 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 packages/aws-cdk/test/test.version.ts diff --git a/packages/aws-cdk/.gitignore b/packages/aws-cdk/.gitignore index 5cac1a6a13095..f15ab74e43d6f 100644 --- a/packages/aws-cdk/.gitignore +++ b/packages/aws-cdk/.gitignore @@ -6,6 +6,7 @@ dist # Generated by generate.sh build-info.json +.LAST_VERSION_CHECK .LAST_BUILD .nyc_output diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index a2081a76ef74d..6d2b623e9bbf6 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -20,7 +20,7 @@ import { PluginHost } from '../lib/plugin'; import { parseRenames } from '../lib/renames'; import { serializeStructure } from '../lib/serialize'; import { Configuration, Settings } from '../lib/settings'; -import { DISPLAY_VERSION } from '../lib/version'; +import version = require('../lib/version'); // tslint:disable-next-line:no-var-requires const promptly = require('promptly'); @@ -76,7 +76,7 @@ async function parseCommandLineArguments() { .option('language', { type: 'string', alias: 'l', desc: 'the language to be used for the new project (default can be configured in ~/.cdk.json)', choices: initTemplateLanuages }) .option('list', { type: 'boolean', desc: 'list the available templates' })) .commandDir('../lib/commands', { exclude: /^_.*/ }) - .version(DISPLAY_VERSION) + .version(version.DISPLAY_VERSION) .demandCommand(1, '') // just print help .help() .alias('h', 'help') @@ -96,8 +96,7 @@ async function initCommandLine() { if (argv.verbose) { setVerbose(); } - - debug('CDK toolkit version:', DISPLAY_VERSION); + debug('CDK toolkit version:', version.DISPLAY_VERSION); debug('Command line arguments:', argv); const aws = new SDK({ @@ -152,15 +151,19 @@ async function initCommandLine() { // Bundle up global objects so the commands have access to them const commandOptions = { args: argv, appStacks, configuration, aws }; - const returnValue = argv.commandHandler - ? await (argv.commandHandler as (opts: typeof commandOptions) => any)(commandOptions) - : await main(cmd, argv); - if (typeof returnValue === 'object') { - return toJsonOrYaml(returnValue); - } else if (typeof returnValue === 'string') { - return returnValue; - } else { - return returnValue; + try { + const returnValue = argv.commandHandler + ? await (argv.commandHandler as (opts: typeof commandOptions) => any)(commandOptions) + : await main(cmd, argv); + if (typeof returnValue === 'object') { + return toJsonOrYaml(returnValue); + } else if (typeof returnValue === 'string') { + return returnValue; + } else { + return returnValue; + } + } finally { + await version.displayVersionMessage(); } async function main(command: string, args: any): Promise { diff --git a/packages/aws-cdk/lib/commands/context.ts b/packages/aws-cdk/lib/commands/context.ts index 185528762aa4e..2796362e942a9 100644 --- a/packages/aws-cdk/lib/commands/context.ts +++ b/packages/aws-cdk/lib/commands/context.ts @@ -1,5 +1,6 @@ import colors = require('colors/safe'); import yargs = require('yargs'); +import version = require('../../lib/version'); import { CommandOptions } from '../command-api'; import { print } from '../logging'; import { Context, PROJECT_CONFIG } from '../settings'; @@ -44,6 +45,7 @@ export async function realHandler(options: CommandOptions): Promise { listContext(contextValues); } } + await version.displayVersionMessage(); return 0; } diff --git a/packages/aws-cdk/lib/commands/doctor.ts b/packages/aws-cdk/lib/commands/doctor.ts index 594671797d8cb..5aba8e58e506a 100644 --- a/packages/aws-cdk/lib/commands/doctor.ts +++ b/packages/aws-cdk/lib/commands/doctor.ts @@ -3,7 +3,7 @@ import colors = require('colors/safe'); import process = require('process'); import yargs = require('yargs'); import { print } from '../../lib/logging'; -import { DISPLAY_VERSION } from '../../lib/version'; +import version = require('../../lib/version'); import { CommandOptions } from '../command-api'; export const command = 'doctor'; @@ -21,6 +21,7 @@ export async function realHandler(_options: CommandOptions): Promise { exitStatus = -1; } } + await version.displayVersionMessage(); return exitStatus; } @@ -33,7 +34,7 @@ const verifications: Array<() => boolean | Promise> = [ // ### Verifications ### function displayVersionInformation() { - print(`ℹ️ CDK Version: ${colors.green(DISPLAY_VERSION)}`); + print(`ℹ️ CDK Version: ${colors.green(version.DISPLAY_VERSION)}`); return true; } diff --git a/packages/aws-cdk/lib/version.ts b/packages/aws-cdk/lib/version.ts index dcad06652d6c2..19ec0b9f9199a 100644 --- a/packages/aws-cdk/lib/version.ts +++ b/packages/aws-cdk/lib/version.ts @@ -1,3 +1,16 @@ +import { exec as _exec } from 'child_process'; +import colors = require('colors/safe'); +import { open as _open, stat as _stat } from 'fs'; +import semver = require('semver'); +import { promisify } from 'util'; +import { debug, print, warning } from '../lib/logging'; + +const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60; + +const exec = promisify(_exec); +const open = promisify(_open); +const stat = promisify(_stat); + export const DISPLAY_VERSION = `${versionNumber()} (build ${commit()})`; function versionNumber(): string { @@ -6,4 +19,80 @@ function versionNumber(): string { function commit(): string { return require('../build-info.json').commit; +} + +export class CacheFile { + private readonly file: string; + + // File modify times are accurate only till the second, hence using seconds as precision + private readonly ttlSecs: number; + + constructor(file: string, ttlSecs: number) { + this.file = file; + this.ttlSecs = ttlSecs; + } + + public async hasExpired(): Promise { + try { + const lastCheckTime = (await stat(this.file)).mtimeMs; + const today = new Date().getTime(); + + if ((today - lastCheckTime) / 1000 > this.ttlSecs) { // convert ms to secs + return true; + } + return false; + } catch (err) { + if (err.code === 'ENOENT') { + return true; + } else { + throw err; + } + } + } + + public async update(): Promise { + await open(this.file, 'w'); + } +} + +// Export for unit testing only. +// Don't use directly, use displayVersionMessage() instead. +export async function latestVersionIfHigher(currentVersion: string, cacheFile: CacheFile): Promise { + if (!(await cacheFile.hasExpired())) { + return null; + } + + const { stdout, stderr } = await exec(`npm view cdk version`); + if (stderr && stderr.trim().length > 0) { + debug(`The 'npm view' command generated an error stream with content [${stderr.trim()}]`); + } + const latestVersion = stdout.trim(); + if (!semver.valid(latestVersion)) { + throw new Error(`npm returned an invalid semver ${latestVersion}`); + } + const isNewer = semver.gt(latestVersion, currentVersion); + await cacheFile.update(); + + if (isNewer) { + return latestVersion; + } else { + return null; + } +} + +const versionCheckCache = new CacheFile(`${__dirname}/../.LAST_VERSION_CHECK`, ONE_DAY_IN_SECONDS); + +export async function displayVersionMessage(): Promise { + try { + const laterVersion = await latestVersionIfHigher(versionNumber(), versionCheckCache); + if (laterVersion) { + const fmt = colors.green(laterVersion as string); + print('********************************************************'); + print(`*** Newer version of the CDK is available [${fmt}] ***`); + print(`*** Upgrade now by running "npm up -g cdk" ***`); + print('********************************************************'); + } + } catch (err) { + warning(`Could not run version check due to error ${err.message} - ${err.stack}`); + } } \ No newline at end of file diff --git a/packages/aws-cdk/test/test.version.ts b/packages/aws-cdk/test/test.version.ts new file mode 100644 index 0000000000000..0f716c2ed53b3 --- /dev/null +++ b/packages/aws-cdk/test/test.version.ts @@ -0,0 +1,52 @@ +import { Test } from 'nodeunit'; +import { setTimeout as _setTimeout } from 'timers'; +import { promisify } from 'util'; +import { CacheFile, latestVersionIfHigher } from '../lib/version'; + +const setTimeout = promisify(_setTimeout); + +function tmpfile(): string { + return `/tmp/version-${Math.floor(Math.random() * 10000)}`; +} + +export = { + async 'cache file responds correctly when file is not present'(test: Test) { + const cache = new CacheFile(tmpfile(), 1); + test.strictEqual(await cache.hasExpired(), true); + test.done(); + }, + + async 'cache file honours the specified TTL'(test: Test) { + const cache = new CacheFile(tmpfile(), 1); + await cache.update(); + test.strictEqual(await cache.hasExpired(), false); + await setTimeout(1000); // 1 sec in ms + test.strictEqual(await cache.hasExpired(), true); + test.done(); + }, + + async 'Skip version check if cache has not expired'(test: Test) { + const cache = new CacheFile(tmpfile(), 100); + await cache.update(); + test.equal(await latestVersionIfHigher('0.0.0', cache), null); + test.done(); + }, + + async 'Return later version when exists & skip recent re-check'(test: Test) { + const cache = new CacheFile(tmpfile(), 100); + const result = await latestVersionIfHigher('0.0.0', cache); + test.notEqual(result, null); + test.ok((result as string).length > 0); + + const result2 = await latestVersionIfHigher('0.0.0', cache); + test.equal(result2, null); + test.done(); + }, + + async 'Return null if version is higher than npm'(test: Test) { + const cache = new CacheFile(tmpfile(), 100); + const result = await latestVersionIfHigher('100.100.100', cache); + test.equal(result, null); + test.done(); + }, +}; \ No newline at end of file From 73c1fc5b7178477a8ff873cb4aee11f06b686886 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 8 May 2019 10:20:03 +0100 Subject: [PATCH 3/4] Updates based on PR feedback * Dropped recommending the command for upgrade. * Switch to use aws-cdk instead of cdk for npm package name. * Closed leaking fd. * Renamed CacheFile to TimestampFile. * Dropped printing of stack trace in a 'warn' message. --- packages/aws-cdk/lib/version.ts | 20 +++++++++++++------- packages/aws-cdk/test/test.version.ts | 12 ++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/aws-cdk/lib/version.ts b/packages/aws-cdk/lib/version.ts index 19ec0b9f9199a..43bf12ecfdd4c 100644 --- a/packages/aws-cdk/lib/version.ts +++ b/packages/aws-cdk/lib/version.ts @@ -1,12 +1,13 @@ import { exec as _exec } from 'child_process'; import colors = require('colors/safe'); -import { open as _open, stat as _stat } from 'fs'; +import { close as _close, open as _open, stat as _stat } from 'fs'; import semver = require('semver'); import { promisify } from 'util'; import { debug, print, warning } from '../lib/logging'; const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60; +const close = promisify(_close); const exec = promisify(_exec); const open = promisify(_open); const stat = promisify(_stat); @@ -21,7 +22,7 @@ function commit(): string { return require('../build-info.json').commit; } -export class CacheFile { +export class TimestampFile { private readonly file: string; // File modify times are accurate only till the second, hence using seconds as precision @@ -51,18 +52,19 @@ export class CacheFile { } public async update(): Promise { - await open(this.file, 'w'); + const fd = await open(this.file, 'w'); + await close(fd); } } // Export for unit testing only. // Don't use directly, use displayVersionMessage() instead. -export async function latestVersionIfHigher(currentVersion: string, cacheFile: CacheFile): Promise { +export async function latestVersionIfHigher(currentVersion: string, cacheFile: TimestampFile): Promise { if (!(await cacheFile.hasExpired())) { return null; } - const { stdout, stderr } = await exec(`npm view cdk version`); + const { stdout, stderr } = await exec(`npm view aws-cdk version`); if (stderr && stderr.trim().length > 0) { debug(`The 'npm view' command generated an error stream with content [${stderr.trim()}]`); } @@ -80,9 +82,13 @@ export async function latestVersionIfHigher(currentVersion: string, cacheFile: C } } -const versionCheckCache = new CacheFile(`${__dirname}/../.LAST_VERSION_CHECK`, ONE_DAY_IN_SECONDS); +const versionCheckCache = new TimestampFile(`${__dirname}/../.LAST_VERSION_CHECK`, ONE_DAY_IN_SECONDS); export async function displayVersionMessage(): Promise { + if (!process.stdout.isTTY) { + return; + } + try { const laterVersion = await latestVersionIfHigher(versionNumber(), versionCheckCache); if (laterVersion) { @@ -93,6 +99,6 @@ export async function displayVersionMessage(): Promise { print('********************************************************'); } } catch (err) { - warning(`Could not run version check due to error ${err.message} - ${err.stack}`); + warning(`Could not run version check due to error ${err.message}`); } } \ No newline at end of file diff --git a/packages/aws-cdk/test/test.version.ts b/packages/aws-cdk/test/test.version.ts index 0f716c2ed53b3..d7b53f7eb0a51 100644 --- a/packages/aws-cdk/test/test.version.ts +++ b/packages/aws-cdk/test/test.version.ts @@ -1,7 +1,7 @@ import { Test } from 'nodeunit'; import { setTimeout as _setTimeout } from 'timers'; import { promisify } from 'util'; -import { CacheFile, latestVersionIfHigher } from '../lib/version'; +import { latestVersionIfHigher, TimestampFile } from '../lib/version'; const setTimeout = promisify(_setTimeout); @@ -11,13 +11,13 @@ function tmpfile(): string { export = { async 'cache file responds correctly when file is not present'(test: Test) { - const cache = new CacheFile(tmpfile(), 1); + const cache = new TimestampFile(tmpfile(), 1); test.strictEqual(await cache.hasExpired(), true); test.done(); }, async 'cache file honours the specified TTL'(test: Test) { - const cache = new CacheFile(tmpfile(), 1); + const cache = new TimestampFile(tmpfile(), 1); await cache.update(); test.strictEqual(await cache.hasExpired(), false); await setTimeout(1000); // 1 sec in ms @@ -26,14 +26,14 @@ export = { }, async 'Skip version check if cache has not expired'(test: Test) { - const cache = new CacheFile(tmpfile(), 100); + const cache = new TimestampFile(tmpfile(), 100); await cache.update(); test.equal(await latestVersionIfHigher('0.0.0', cache), null); test.done(); }, async 'Return later version when exists & skip recent re-check'(test: Test) { - const cache = new CacheFile(tmpfile(), 100); + const cache = new TimestampFile(tmpfile(), 100); const result = await latestVersionIfHigher('0.0.0', cache); test.notEqual(result, null); test.ok((result as string).length > 0); @@ -44,7 +44,7 @@ export = { }, async 'Return null if version is higher than npm'(test: Test) { - const cache = new CacheFile(tmpfile(), 100); + const cache = new TimestampFile(tmpfile(), 100); const result = await latestVersionIfHigher('100.100.100', cache); test.equal(result, null); test.done(); From 07194b8f6636455704993c1522dba0ad0d67d0da Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 8 May 2019 11:34:29 +0100 Subject: [PATCH 4/4] Better banner formatting logic for printing on console. Banner formatting to support variable sized string and recognizes colors support. This allows for the banner to work correctly when the message size changes. --- .../aws-cdk/lib/util/console-formatters.ts | 43 ++++++++++++++ packages/aws-cdk/lib/version.ts | 11 ++-- .../test/util/test.console-formatters.ts | 58 +++++++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 packages/aws-cdk/lib/util/console-formatters.ts create mode 100644 packages/aws-cdk/test/util/test.console-formatters.ts diff --git a/packages/aws-cdk/lib/util/console-formatters.ts b/packages/aws-cdk/lib/util/console-formatters.ts new file mode 100644 index 0000000000000..79c55eba0bc3f --- /dev/null +++ b/packages/aws-cdk/lib/util/console-formatters.ts @@ -0,0 +1,43 @@ +import colors = require('colors/safe'); + +/** + * Returns a set of strings when printed on the console produces a banner msg. The message is in the following format - + * ******************** + * *** msg line x *** + * *** msg line xyz *** + * ******************** + * + * Spec: + * - The width of every line is equal, dictated by the longest message string + * - The first and last lines are '*'s for the full length of the line + * - Each line in between is prepended with '*** ' and appended with ' ***' + * - The text is indented left, i.e. whitespace is right-padded when the length is shorter than the longest. + * + * @param msgs array of strings containing the message lines to be printed in the banner. Returns empty string if array + * is empty. + * @returns array of strings containing the message formatted as a banner + */ +export function formatAsBanner(msgs: string[]): string[] { + const printLen = (str: string) => colors.strip(str).length; + + if (msgs.length === 0) { + return []; + } + + const leftPad = '*** '; + const rightPad = ' ***'; + const bannerWidth = printLen(leftPad) + printLen(rightPad) + + msgs.reduce((acc, msg) => Math.max(acc, printLen(msg)), 0); + + const bannerLines: string[] = []; + bannerLines.push('*'.repeat(bannerWidth)); + + // Improvement: If any 'msg' is wider than the terminal width, wrap message across lines. + msgs.forEach((msg) => { + const padding = ' '.repeat(bannerWidth - (printLen(msg) + printLen(leftPad) + printLen(rightPad))); + bannerLines.push(''.concat(leftPad, msg, padding, rightPad)); + }); + + bannerLines.push('*'.repeat(bannerWidth)); + return bannerLines; +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/version.ts b/packages/aws-cdk/lib/version.ts index 43bf12ecfdd4c..7d9933b6b8f03 100644 --- a/packages/aws-cdk/lib/version.ts +++ b/packages/aws-cdk/lib/version.ts @@ -4,6 +4,7 @@ import { close as _close, open as _open, stat as _stat } from 'fs'; import semver = require('semver'); import { promisify } from 'util'; import { debug, print, warning } from '../lib/logging'; +import { formatAsBanner } from '../lib/util/console-formatters'; const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60; @@ -92,11 +93,11 @@ export async function displayVersionMessage(): Promise { try { const laterVersion = await latestVersionIfHigher(versionNumber(), versionCheckCache); if (laterVersion) { - const fmt = colors.green(laterVersion as string); - print('********************************************************'); - print(`*** Newer version of the CDK is available [${fmt}] ***`); - print(`*** Upgrade now by running "npm up -g cdk" ***`); - print('********************************************************'); + const bannerMsg = formatAsBanner([ + `Newer version of CDK is available [${colors.green(laterVersion as string)}]`, + `Upgrade recommended`, + ]); + bannerMsg.forEach((e) => print(e)); } } catch (err) { warning(`Could not run version check due to error ${err.message}`); diff --git a/packages/aws-cdk/test/util/test.console-formatters.ts b/packages/aws-cdk/test/util/test.console-formatters.ts new file mode 100644 index 0000000000000..6ed0a05fe3a64 --- /dev/null +++ b/packages/aws-cdk/test/util/test.console-formatters.ts @@ -0,0 +1,58 @@ +import colors = require('colors/safe'); +import { Test } from 'nodeunit'; +import { formatAsBanner } from '../../lib/util/console-formatters'; + +function reportBanners(actual: string[], expected: string[]): string { + return 'Assertion failed.\n' + + 'Expected banner: \n' + expected.join('\n') + '\n' + + 'Actual banner: \n' + actual.join('\n'); +} + +export = { + 'no banner on empty msg list'(test: Test) { + test.strictEqual(formatAsBanner([]).length, 0); + test.done(); + }, + + 'banner works as expected'(test: Test) { + const msgs = [ 'msg1', 'msg2' ]; + const expected = [ + '************', + '*** msg1 ***', + '*** msg2 ***', + '************' + ]; + + const actual = formatAsBanner(msgs); + + test.strictEqual(formatAsBanner(msgs).length, expected.length, reportBanners(actual, expected)); + for (let i = 0; i < expected.length; i++) { + test.strictEqual(actual[i], expected[i], reportBanners(actual, expected)); + } + test.done(); + }, + + 'banner works for formatted msgs'(test: Test) { + const msgs = [ + 'hello msg1', + colors.yellow('hello msg2'), + colors.bold('hello msg3'), + ]; + const expected = [ + '******************', + '*** hello msg1 ***', + `*** ${colors.yellow('hello msg2')} ***`, + `*** ${colors.bold('hello msg3')} ***`, + '******************', + ]; + + const actual = formatAsBanner(msgs); + + test.strictEqual(formatAsBanner(msgs).length, expected.length, reportBanners(actual, expected)); + for (let i = 0; i < expected.length; i++) { + test.strictEqual(actual[i], expected[i], reportBanners(actual, expected)); + } + + test.done(); + } +}; \ No newline at end of file