Skip to content

Commit

Permalink
Merge branch 'feat/errs' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
claytercek committed Nov 1, 2024
2 parents 6194e9b + 166ce90 commit fd049db
Show file tree
Hide file tree
Showing 42 changed files with 521 additions and 415 deletions.
24 changes: 13 additions & 11 deletions packages/cli/lib/commands/content.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { ResultAsync } from 'neverthrow';
import { err, ok, ResultAsync } from 'neverthrow';
import { ImportError } from '../errors.js';
import { loadConfigAndEnv } from '../utils/load-config-and-env.js';
import { handleFatalError, initializeLogger, loadConfigAndEnv } from '../utils/command-utils.js';

/**
* @param {import("../cli.js").LaunchpadArgv} argv
*/
export function content(argv) {
return loadConfigAndEnv(argv).andThen(config => {
return importLaunchpadContent().map(({ LaunchpadContent }) => {
const contentInstance = new LaunchpadContent(config.content);
return contentInstance.download();
return loadConfigAndEnv(argv)
.andThen(initializeLogger)
.andThen(({ config, rootLogger }) => {
return importLaunchpadContent().andThen(({ LaunchpadContent }) => {
if (!config.content) {
return err(new Error('No content config found in your config file.'));
}

const contentInstance = new LaunchpadContent(config.content, rootLogger);
return contentInstance.download();
}).orElse(error => handleFatalError(error, rootLogger));
});
}).mapErr(error => {
console.error('Content failed to download.');
console.error(error.message);
process.exit(1);
});
}

export function importLaunchpadContent() {
Expand Down
39 changes: 21 additions & 18 deletions packages/cli/lib/commands/monitor.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import { ResultAsync } from 'neverthrow';
import { err, ok, ResultAsync } from 'neverthrow';
import { ImportError, MonitorError } from '../errors.js';
import { loadConfigAndEnv } from '../utils/load-config-and-env.js';
import { handleFatalError, initializeLogger, loadConfigAndEnv } from '../utils/command-utils.js';

/**
* @param {import("../cli.js").LaunchpadArgv} argv
*/
export function monitor(argv) {
return loadConfigAndEnv(argv).andThen(config => {
return importLaunchpadMonitor()
.map(({ LaunchpadMonitor }) => {
return new LaunchpadMonitor(config.monitor);
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.connect(), () => new MonitorError('Failed to connect to monitor'));
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.start(), () => new MonitorError('Failed to start monitor'));
});
}).mapErr(error => {
console.error('Monitor failed to start.');
console.error(error.message);
process.exit(1);
});
return loadConfigAndEnv(argv)
.andThen(initializeLogger)
.andThen(({ config, rootLogger }) => {
return importLaunchpadMonitor()
.andThen(({ LaunchpadMonitor }) => {
if (!config.monitor) {
return err(new Error('No monitor config found in your config file.'));
}

const monitorInstance = new LaunchpadMonitor(config.monitor, rootLogger);
return ok(monitorInstance);
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.connect(), () => new MonitorError('Failed to connect to monitor'));
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.start(), () => new MonitorError('Failed to start monitor'));
}).orElse(error => handleFatalError(error, rootLogger));
});
}

export function importLaunchpadMonitor() {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/lib/commands/scaffold.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { launchScaffold } from '@bluecadet/launchpad-scaffold';
import { LogManager } from '@bluecadet/launchpad-utils';

/**
* @param {import("../cli.js").LaunchpadArgv} argv
*/
export async function scaffold(argv) {
await launchScaffold();
const rootLogger = LogManager.configureRootLogger();
await launchScaffold(rootLogger);
}
51 changes: 30 additions & 21 deletions packages/cli/lib/commands/start.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
import { loadConfigAndEnv } from '../utils/load-config-and-env.js';
import { handleFatalError, initializeLogger, loadConfigAndEnv } from '../utils/command-utils.js';
import { importLaunchpadMonitor } from './monitor.js';
import { importLaunchpadContent } from './content.js';
import { ResultAsync } from 'neverthrow';
import { err, ok, ResultAsync } from 'neverthrow';
import { MonitorError } from '../errors.js';

/**
* @param {import("../cli.js").LaunchpadArgv} argv
*/
export async function start(argv) {
return loadConfigAndEnv(argv).andThen(config => {
return importLaunchpadContent().map(({ LaunchpadContent }) => {
const contentInstance = new LaunchpadContent(config.content);
return contentInstance.start();
}).andThen(() => importLaunchpadMonitor())
.map(({ LaunchpadMonitor }) => {
return new LaunchpadMonitor(config.monitor);
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.connect(), () => new MonitorError('Failed to connect to monitor'));
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.start(), () => new MonitorError('Failed to start monitor'));
});
}).mapErr(error => {
console.error('Launchpad failed to start.');
console.error(error.message);
process.exit(1);
});
return loadConfigAndEnv(argv)
.andThen(initializeLogger)
.andThen(({ config, rootLogger }) => {
return importLaunchpadContent()
.andThen(({ LaunchpadContent }) => {
if (!config.content) {
return err(new Error('No content config found in your config file.'));
}

const contentInstance = new LaunchpadContent(config.content, rootLogger);
return contentInstance.start();
})
.andThen(() => importLaunchpadMonitor())
.andThen(({ LaunchpadMonitor }) => {
if (!config.monitor) {
return err(new Error('No monitor config found in your config file.'));
}

const monitorInstance = new LaunchpadMonitor(config.monitor, rootLogger);
return ok(monitorInstance);
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.connect(), () => new MonitorError('Failed to connect to monitor'));
})
.andThrough((monitorInstance) => {
return ResultAsync.fromPromise(monitorInstance.start(), () => new MonitorError('Failed to start monitor'));
}).orElse(error => handleFatalError(error, rootLogger));
});
}
109 changes: 109 additions & 0 deletions packages/cli/lib/utils/command-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { err, errAsync, ok, ResultAsync } from 'neverthrow';
import { findConfig, loadConfigFromFile } from './config.js';
import { ConfigError } from '../errors.js';
import path from 'path';
import { resolveEnv } from './env.js';
import { resolveLaunchpadOptions } from '../launchpad-options.js';
import chalk from 'chalk';
import { LogManager } from '@bluecadet/launchpad-utils';

/**
* @param {import("../cli.js").LaunchpadArgv} argv
* @returns {import('neverthrow').ResultAsync<import('../launchpad-options.js').ResolvedLaunchpadOptions, ConfigError>}
*/
export function loadConfigAndEnv(argv) {
const configPath = argv.config ?? findConfig();

if (!configPath) {
return errAsync(new ConfigError('No config file found.'));
}

const configDir = path.dirname(configPath);

if (argv.env) {
// if env arg is passed, resolve paths relative to the CWD
const rootDir = process.env.INIT_CWD ?? '';
resolveEnv(
argv.env.map(p => path.resolve(rootDir, p.toString()))
);
} else if (argv.envCascade) {
// if env-cascade arg is passed, resolve paths relative to the config file

// Load order: .env < .env.local < .env.[override] < .env.[override].local
resolveEnv([
path.resolve(configDir, '.env'),
path.resolve(configDir, '.env.local'),
path.resolve(configDir, `.env.${argv.envCascade}`),
path.resolve(configDir, `.env.${argv.envCascade}.local`)
]);
} else {
// default to loading .env and .env.local in the config dir
resolveEnv([
path.resolve(configDir, '.env'),
path.resolve(configDir, '.env.local')
]);
}

return ResultAsync.fromPromise(loadConfigFromFile(configPath), (e) => new ConfigError(`Failed to load config file at path: ${chalk.white(configPath)}`))
.map(config => resolveLaunchpadOptions(config));
}

/**
*
* @param {import('../launchpad-options.js').LaunchpadOptions} config
*/
export function initializeLogger(config) {
const rootLogger = LogManager.configureRootLogger(config.logging);

return ok({ config, rootLogger });
}

/**
*
* @param {Error} error
* @param {import('@bluecadet/launchpad-utils').Logger} rootLogger
* @returns {never}
*/
export function handleFatalError(error, rootLogger) {
rootLogger.error('Content failed to download.');
logFullErrorChain(rootLogger, error);
process.exit(1);
}

/**
* @param {import('@bluecadet/launchpad-utils').Logger} logger
* @param {Error} error
*/
export function logFullErrorChain(logger, error) {
/** @type {Error | undefined} */
let currentError = error;
while (currentError) {
logger.error(`${chalk.red('β”Œβ”€')} ${chalk.red.bold(currentError.name)}: ${chalk.red(currentError.message)}`);
const callstack = currentError.stack;
// logger.error(`${chalk.red(callstack ? 'β”‚' : 'β””')} `);
if (callstack) {
const lines = callstack.split('\n').slice(1);
// log up to 3 lines of the callstack
let loggedLines = 0;
for (const line of lines) {
const isLastLine = loggedLines === lines.length - 1 || loggedLines > 2;
logger.error(`${chalk.red('β”‚')} ${chalk.red.dim((isLastLine && lines.length > 3) ? '...' : line.trim())}`);
if (isLastLine) {
logger.error(`${chalk.red('└──────────────────')}`);
}
loggedLines++;

if (loggedLines > 3) {
break;
}
}
}
if (currentError.cause && currentError.cause instanceof Error) {
currentError = currentError.cause;
logger.error(` ${chalk.red.dim('β”‚')} ${chalk.red.dim('Caused by:')}`);
logger.error(` ${chalk.red.dim('β”‚')}`);
} else {
currentError = undefined;
}
}
}
48 changes: 0 additions & 48 deletions packages/cli/lib/utils/load-config-and-env.js

This file was deleted.

Loading

0 comments on commit fd049db

Please sign in to comment.