Skip to content

Commit

Permalink
add option to enable verbose debug output and show saner error messag…
Browse files Browse the repository at this point in the history
…es on failure
  • Loading branch information
jahudka committed Aug 24, 2023
1 parent 3b82cff commit 5b94812
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 51 deletions.
4 changes: 3 additions & 1 deletion core/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"ioc",
"inversion of control"
],
"version": "0.0.34",
"version": "0.0.35",
"license": "MIT",
"author": {
"name": "Dan Kadera",
Expand All @@ -32,6 +32,8 @@
"dicc": "dist/cli.js"
},
"dependencies": {
"@debugr/console": "^3.0.0-rc.10",
"@debugr/core": "^3.0.0-rc.12",
"dicc": "^0.0.26",
"ts-morph": "^18.0",
"typescript": "^5.0",
Expand Down
36 changes: 30 additions & 6 deletions core/cli/src/argv.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
import { LogLevel } from '@debugr/core';
import { parseArgs } from 'util';

export class Argv {
readonly configPath: string;
readonly configFile?: string;
readonly logLevel: LogLevel;

constructor() {
const args = parseArgs({
strict: true,
allowPositionals: true,
allowPositionals: false,
options: {
'config': {
short: 'c',
type: 'string',
},
'verbose': {
short: 'v',
type: 'boolean',
default: [],
multiple: true,
},
},
});

if (args.positionals.length > 1) {
throw new Error('Invalid number of arguments, expected 0-1');
}
this.configFile = args.values.config;

const verbosity = args.values.verbose
? args.values.verbose.reduce((n, v) => n + 2 * (Number(v) - 0.5), 0)
: 0;

this.configPath = args.positionals[0] ?? 'dicc.yaml';
if (verbosity >= 2) {
this.logLevel = LogLevel.TRACE;
} else if (verbosity > 0) {
this.logLevel = LogLevel.DEBUG;
} else if (verbosity < 0) {
this.logLevel = LogLevel.WARNING;
} else {
this.logLevel = LogLevel.INFO;
}
}
}
57 changes: 39 additions & 18 deletions core/cli/src/autowiring.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Logger } from '@debugr/core';
import { ServiceScope } from 'dicc';
import { Type } from 'ts-morph';
import { ServiceRegistry } from './serviceRegistry';
Expand All @@ -11,14 +12,14 @@ import {
} from './types';

export class Autowiring {
private readonly registry: ServiceRegistry;
private readonly visitedServices: Set<ServiceDefinitionInfo> = new Set();
private readonly visitedDecorators: Map<ServiceDecoratorInfo, Set<ServiceScope>> = new Map();
private readonly resolving: string[] = [];

constructor(registry: ServiceRegistry) {
this.registry = registry;
}
constructor(
private readonly registry: ServiceRegistry,
private readonly logger: Logger,
) {}

checkDependencies(): void {
this.registry.applyDecorators();
Expand All @@ -41,30 +42,42 @@ export class Autowiring {
return this.registry.getByType(type).some((def) => def.async);
}

private checkServiceDependencies(definition: ServiceDefinitionInfo): void {
if (!this.visitService(definition)) {
private checkServiceDependencies(info: ServiceDefinitionInfo): void {
if (!this.visitService(info)) {
return;
}

const scope = this.resolveScope(definition);
this.logger.debug(`Checking '${info.path}' dependencies...`);
const scope = this.resolveScope(info);

if (definition.factory) {
if (this.checkParameters(definition.factory.parameters, `service '${definition.id}'`, scope, definition.args)) {
definition.factory.async = true;
if (info.factory) {
if (this.checkParameters(info.factory.parameters, `service '${info.path}'`, scope, info.args) && !info.factory.async) {
this.logger.trace(`Marking '${info.path}' factory as async because one or more parameters need to be awaited`);
info.factory.async = true;
}

if (definition.factory.async) {
definition.async = true;
if (info.factory.async && !info.async) {
this.logger.trace(`Marking '${info.path}' as async because factory is async`);
info.async = true;
}
}

if (this.checkHooks(definition.hooks, `service '${definition.id}'`, scope)) {
definition.async = true;
if (this.checkHooks(info.hooks, `service '${info.path}'`, scope) && !info.async) {
this.logger.trace(`Marking '${info.path}' as async because its 'onCreate' hook is async`);
info.async = true;
}

const flags = this.checkDecorators(definition.decorators, scope);
flags.asyncDecorate && definition.factory && (definition.factory.async = true);
flags.asyncDecorate || flags.asyncOnCreate && (definition.async = true);
const flags = this.checkDecorators(info.decorators, scope);

if (flags.asyncDecorate && info.factory && !info.factory.async) {
this.logger.trace(`Marking '${info.path}' factory as async because it has an async decorator`);
info.factory.async = true;
}

if ((flags.asyncDecorate || flags.asyncOnCreate) && !info.async) {
this.logger.trace(`Marking '${info.path}' as async because it has an async decorator`);
info.async = true;
}
}

private resolveScope(definition: ServiceDefinitionInfo): ServiceScope {
Expand All @@ -80,7 +93,10 @@ export class Autowiring {
continue;
}

if (this.checkParameters(info.parameters, `'${hook}' hook of ${target}`, scope)) {
this.logger.debug(`Checking ${target} '${hook}' hook...`);

if (this.checkParameters(info.parameters, `'${hook}' hook of ${target}`, scope) && !info.async) {
this.logger.trace(`Marking '${hook}' hook of ${target} as async because one or more parameters need to be awaited`);
info.async = true;
}
}
Expand All @@ -107,6 +123,7 @@ export class Autowiring {

if (decorator.decorate) {
if (this.checkParameters(decorator.decorate.parameters, `decorator '${decorator.path}'`, scope)) {
this.logger.trace(`Marking decorator '${decorator.path}' as async because one or more parameters need to be awaited`);
decorator.decorate.async = true;
}

Expand All @@ -116,6 +133,7 @@ export class Autowiring {
}

if (this.checkHooks(decorator.hooks, `decorator '${decorator.path}'`, scope)) {
this.logger.trace(`Marking decorator '${decorator.path}' as async because its 'onCreate' hook is async`);
flags.asyncOnCreate = true;
}
}
Expand All @@ -133,9 +151,11 @@ export class Autowiring {
const arg = args[parameter.name];

if (arg && this.checkParameters(arg.parameters, `argument '${parameter.name}' of ${target}`, scope)) {
this.logger.trace(`Parameter '${parameter.name}' of ${target} is async`);
async = true;
}
} else if (this.checkParameter(parameter, target, scope)) {
this.logger.trace(`Parameter '${parameter.name}' of ${target} is async`);
async = true;
}
}
Expand All @@ -152,6 +172,7 @@ export class Autowiring {

if (!candidates || !candidates.length) {
if (parameter.flags & TypeFlag.Optional) {
this.logger.trace(`Skipping parameter '${parameter.type}' of ${target}: unknown parameter type`);
return false;
}

Expand Down
18 changes: 9 additions & 9 deletions core/cli/src/checker.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { Logger } from '@debugr/core';
import { Node } from 'ts-morph';
import { ServiceRegistry } from './serviceRegistry';
import { TypeHelper } from './typeHelper';
import { TypeFlag } from './types';

export class Checker {
private readonly helper: TypeHelper;
private readonly registry: ServiceRegistry;

constructor(helper: TypeHelper, registry: ServiceRegistry) {
this.helper = helper;
this.registry = registry;
}
constructor(
private readonly helper: TypeHelper,
private readonly registry: ServiceRegistry,
private readonly logger: Logger,
) {}

removeExtraneousImplicitRegistrations(): void {
for (const def of this.registry.getDefinitions()) {
if (!def.explicit && this.registry.getIdsByType(def.type).filter((id) => id !== def.id).length) {
this.logger.debug(`Unregistered extraneous service '${def.path}'`);
this.registry.unregister(def.id);
}
}
Expand All @@ -28,7 +28,7 @@ export class Checker {
if (Node.isStringLiteral(id) && !this.registry.has(id.getLiteralValue())) {
const sf = id.getSourceFile();
const ln = id.getStartLineNumber();
console.log(`Warning: unknown service '${id.getLiteralValue()}' in call to Container.${method}() in ${sf.getFilePath()} on line ${ln}`);
this.logger.warning(`Unknown service '${id.getLiteralValue()}' in call to Container.${method}() in '${sf.getFilePath()}' on line ${ln}`);
}
}
}
Expand Down Expand Up @@ -61,7 +61,7 @@ export class Checker {

for (const id of dynamic) {
if (!registrations.has(id) && !injectors.has(id)) {
console.log(`Warning: no Container.register() call found for dynamic service '${id}'`);
this.logger.warning(`No Container.register() call found for dynamic service '${id}'`);
}
}
}
Expand Down
21 changes: 19 additions & 2 deletions core/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
import { container } from './container';

(async () => {
const dicc = await container.get('dicc');
await dicc.compile();
try {
const dicc = await container.get('dicc');
await dicc.compile();
} catch (e: any) {
const isVerbose = process.argv.slice(2).find((arg) => /^(-v+|--verbose)/.test(arg));

try {
const logger = container.get('debug.logger');
logger.error(isVerbose ? e : e.message);
} catch {
if (isVerbose) {
throw e;
} else {
console.log(`Error: ${e.message}`);
}
}

process.exit(1);
}
})();
47 changes: 42 additions & 5 deletions core/cli/src/configLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,53 @@ import { parse } from 'yaml';
import { Argv } from './argv';
import { DiccConfig, diccConfigSchema } from './types';

const defaultConfigFiles = [
'dicc.yaml',
'dicc.yml',
'.dicc.yaml',
'.dicc.yml',
];

export class ConfigLoader {
private readonly configPath: string;
private readonly configFile?: string;

constructor(argv: Argv) {
this.configPath = resolve(argv.configPath);
this.configFile = argv.configFile;
}

async load(): Promise<DiccConfig> {
const data = await readFile(this.configPath, 'utf-8');
const config = parse(data);
return diccConfigSchema.parse(config);
const [file, data] = await this.loadConfigFile();

try {
const config = parse(data);
return diccConfigSchema.parse(config);
} catch (e: any) {
throw new Error(`Error in config file '${file}': ${e.message}`);
}
}

private async loadConfigFile(): Promise<[file: string, config: string]> {
const candidates = this.configFile === undefined ? defaultConfigFiles : [this.configFile];

for (const file of candidates) {
const fullPath = resolve(file);

try {
const config = await readFile(fullPath, 'utf-8');
return [fullPath, config];
} catch (e: any) {
if (e.code === 'ENOENT') {
continue;
}

throw e;
}
}

throw new Error(
this.configFile === undefined
? 'Config file not specified and none of the default config files exists'
: `Config file '${this.configFile}' doesn't exist`,
);
}
}
20 changes: 19 additions & 1 deletion core/cli/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as defs0 from './definitions';

export interface Services {
'config': Promise<ServiceType<typeof defs0.config>>;
'debug.console': ServiceType<typeof defs0.debug.console>;
'debug.logger': ServiceType<typeof defs0.debug.logger>;
'dicc': Promise<ServiceType<typeof defs0.dicc>>;
'project': Promise<ServiceType<typeof defs0.project>>;
'#Argv.0': defs0.Argv;
Expand All @@ -13,6 +15,8 @@ export interface Services {
'#DefinitionScanner.0': Promise<defs0.DefinitionScanner>;
'#Dicc.0': Promise<ServiceType<typeof defs0.dicc>>;
'#DiccConfig.0': Promise<ServiceType<typeof defs0.config>>;
'#Logger.0': ServiceType<typeof defs0.debug.logger>;
'#Plugin.0': ServiceType<typeof defs0.debug.console>;
'#Project.0': Promise<ServiceType<typeof defs0.project>>;
'#ServiceRegistry.0': defs0.ServiceRegistry;
'#SourceFiles.0': Promise<defs0.SourceFiles>;
Expand All @@ -25,6 +29,15 @@ export const container = new Container<Services>({
async: true,
factory: async (di) => defs0.config(di.get('#ConfigLoader.0')),
},
'debug.console': {
...defs0.debug.console,
aliases: ['#Plugin.0'],
factory: (di) => defs0.debug.console.factory(di.get('#Argv.0')),
},
'debug.logger': {
aliases: ['#Logger.0'],
factory: (di) => defs0.debug.logger(di.find('#Plugin.0')),
},
'dicc': {
aliases: ['#Dicc.0'],
async: true,
Expand All @@ -47,13 +60,17 @@ export const container = new Container<Services>({
factory: () => new defs0.Argv(),
},
'#Autowiring.0': {
factory: (di) => new defs0.Autowiring(di.get('#ServiceRegistry.0')),
factory: (di) => new defs0.Autowiring(
di.get('#ServiceRegistry.0'),
di.get('#Logger.0'),
),
},
'#Checker.0': {
async: true,
factory: async (di) => new defs0.Checker(
await di.get('#TypeHelper.0'),
di.get('#ServiceRegistry.0'),
di.get('#Logger.0'),
),
},
'#Compiler.0': {
Expand All @@ -73,6 +90,7 @@ export const container = new Container<Services>({
factory: async (di) => new defs0.DefinitionScanner(
di.get('#ServiceRegistry.0'),
await di.get('#TypeHelper.0'),
di.get('#Logger.0'),
),
},
'#ServiceRegistry.0': {
Expand Down
Loading

0 comments on commit 5b94812

Please sign in to comment.