feat: Allow for custom CLI and variable options

* feat: (AppRunner) Mechanism to configure cli args and derive componentsjs vars from them implemented

* fix: (AppRunner) tidying

* fix: (AppRunner) tidying up

* fix: (AppRunner) runCli method made sync

* fix; (VarResolver) refactored to multiple files, and other stylistic fixes.

* chore: (AppRunner) Uses builder pattern for yargs base arguments setup to enable better typescript inference

* fix(AppRunner): refactoring AppRunner and VarResolver

* fix(AppRunner): refactoring AppRunner promise handling

* fix(AppRunner): verror dependency removal

* fix: Simplify CLI error handling

* feat: Use same config for both CLI and app instantiation

* fix: Update typings and imports

* feat: Split VariableResolver behaviour to 2 classes

* feat: Move default value behaviour from CLI to ValueComputers

* test: Add unit tests for new CLI classes

* feat: Integrate new CLI configuration with all default configurations

* feat: Add createApp function to AppRunner

* docs: Update comments in CLI-related classes

* fix: Various fixes and refactors

Co-authored-by: damooo <damodara@protonmail.com>
This commit is contained in:
Joachim Van Herwegen
2022-02-11 10:00:12 +01:00
committed by GitHub
parent d067165b68
commit c216efd62f
39 changed files with 1026 additions and 373 deletions

View File

@@ -0,0 +1,27 @@
import { createErrorMessage } from '../../util/errors/ErrorUtil';
import type { SettingsExtractor } from './extractors/SettingsExtractor';
import { SettingsResolver } from './SettingsResolver';
/**
* Generates variable values by running a set of {@link SettingsExtractor}s on the input.
*/
export class CombinedSettingsResolver extends SettingsResolver {
public readonly computers: Record<string, SettingsExtractor>;
public constructor(computers: Record<string, SettingsExtractor>) {
super();
this.computers = computers;
}
public async handle(input: Record<string, unknown>): Promise<Record<string, unknown>> {
const vars: Record<string, any> = {};
for (const [ name, computer ] of Object.entries(this.computers)) {
try {
vars[name] = await computer.handleSafe(input);
} catch (err: unknown) {
throw new Error(`Error in computing value for variable ${name}: ${createErrorMessage(err)}`);
}
}
return vars;
}
}

View File

@@ -0,0 +1,9 @@
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { Settings, VariableBindings } from './Types';
/**
* Converts a key/value object, extracted from the CLI or passed as a parameter,
* into a new key/value object where the keys are variables defined in the Components.js configuration.
* The resulting values are the values that should be assigned to those variables.
*/
export abstract class SettingsResolver extends AsyncHandler<Settings, VariableBindings> {}

View File

@@ -0,0 +1,16 @@
// These types are used to clarify what is expected for the CLI-related handlers
/**
* A list of command line arguments provided to the process.
*/
export type CliArgv = string[];
/**
* A key/value mapping of parsed command line arguments.
*/
export type Settings = Record<string, unknown>;
/**
* A key/value mapping of Components.js variables.
*/
export type VariableBindings = Record<string, unknown>;

View File

@@ -0,0 +1,26 @@
import { resolveAssetPath } from '../../../util/PathUtil';
import type { Settings } from '../Types';
import { SettingsExtractor } from './SettingsExtractor';
/**
* A {@link SettingsExtractor} that converts a path value to an absolute asset path by making use of `resolveAssetPath`.
* Returns the default path in case it is defined and no path was found in the map.
*/
export class AssetPathExtractor extends SettingsExtractor {
private readonly key: string;
private readonly defaultPath?: string;
public constructor(key: string, defaultPath?: string) {
super();
this.key = key;
this.defaultPath = defaultPath;
}
public async handle(args: Settings): Promise<unknown> {
const path = args[this.key] ?? this.defaultPath;
if (typeof path !== 'string') {
throw new Error(`Invalid ${this.key} argument`);
}
return resolveAssetPath(path);
}
}

View File

@@ -0,0 +1,24 @@
import { ensureTrailingSlash } from '../../../util/PathUtil';
import type { Settings } from '../Types';
import { SettingsExtractor } from './SettingsExtractor';
/**
* A {@link SettingsExtractor} that that generates the base URL based on the input `baseUrl` value,
* or by using the port if the first isn't provided.
*/
export class BaseUrlExtractor extends SettingsExtractor {
private readonly defaultPort: number;
public constructor(defaultPort = 3000) {
super();
this.defaultPort = defaultPort;
}
public async handle(args: Settings): Promise<unknown> {
if (typeof args.baseUrl === 'string') {
return ensureTrailingSlash(args.baseUrl);
}
const port = args.port ?? this.defaultPort;
return `http://localhost:${port}/`;
}
}

View File

@@ -0,0 +1,21 @@
import type { Settings } from '../Types';
import { SettingsExtractor } from './SettingsExtractor';
/**
* A simple {@link SettingsExtractor} that extracts a single value from the input map.
* Returns the default value if it was defined in case no value was found in the map.
*/
export class KeyExtractor extends SettingsExtractor {
private readonly key: string;
private readonly defaultValue: unknown;
public constructor(key: string, defaultValue?: unknown) {
super();
this.key = key;
this.defaultValue = defaultValue;
}
public async handle(args: Settings): Promise<unknown> {
return typeof args[this.key] === 'undefined' ? this.defaultValue : args[this.key];
}
}

View File

@@ -0,0 +1,7 @@
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
import type { Settings } from '../Types';
/**
* A handler that computes a specific value from a given map of values.
*/
export abstract class SettingsExtractor extends AsyncHandler<Settings, unknown> {}