Skip to content

Commit 23ed470

Browse files
committed
Added error handling to messgae handlers. Added catch blocks to commands instead of a try-catch. Made error message red
1 parent 446df40 commit 23ed470

5 files changed

Lines changed: 73 additions & 61 deletions

File tree

src/commands/apply/index.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Args, Command, Flags } from '@oclif/core'
2+
import chalk from 'chalk';
23
import { ResourceOperation } from 'codify-schemas';
3-
import path from 'node:path';
4+
import * as path from 'node:path';
45

56
import { ApplyOrchestrator } from '../../orchestrators/apply.js';
67
import { PlanOrchestrator } from '../../orchestrators/plan.js';
@@ -22,37 +23,37 @@ export default class Apply extends Command {
2223
path: Flags.string({ char: 'p', description: 'path to project' }),
2324
}
2425

26+
protected async catch(err: Error): Promise<void> {
27+
console.log(chalk.red(err.message));
28+
process.exit(1);
29+
}
30+
2531
public async run(): Promise<void> {
2632
const { flags } = await this.parse(Apply)
2733
const reporter = new DefaultReporter()
2834

29-
try {
30-
if (flags.path) {
31-
this.log(`Applying Codify from: ${flags.path}`);
32-
}
33-
34-
const resolvedPath = path.resolve(flags.path ?? '.');
35+
if (flags.path) {
36+
this.log(`Applying Codify from: ${flags.path}`);
37+
}
3538

36-
const planResult = await PlanOrchestrator.run(resolvedPath);
37-
reporter.displayPlan(planResult.plan);
39+
const resolvedPath = path.resolve(flags.path ?? '.');
3840

39-
// Short circuit and exit if every change is NOOP
40-
if (planResult.plan.every((p) => p.operation === ResourceOperation.NOOP)) {
41-
console.log('No changes necessary. Exiting');
42-
return process.exit(0);
43-
}
41+
const planResult = await PlanOrchestrator.run(resolvedPath);
42+
reporter.displayPlan(planResult.plan);
4443

45-
const confirm = await reporter.promptApplyConfirmation()
46-
if (!confirm) {
47-
return process.exit(0);
48-
}
44+
// Short circuit and exit if every change is NOOP
45+
if (planResult.plan.every((p) => p.operation === ResourceOperation.NOOP)) {
46+
console.log('No changes necessary. Exiting');
47+
return process.exit(0);
48+
}
4949

50-
await ApplyOrchestrator.run(planResult);
51-
} catch (error: unknown) {
52-
console.error(error);
53-
process.exit(1);
50+
const confirm = await reporter.promptApplyConfirmation()
51+
if (!confirm) {
52+
return process.exit(0);
5453
}
5554

55+
await ApplyOrchestrator.run(planResult);
56+
5657
process.exit(0);
5758
}
5859
}

src/commands/plan/index.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Args, Command, Flags } from '@oclif/core'
2+
import chalk from 'chalk';
23
import * as path from 'node:path';
34

45
import { PlanOrchestrator } from '../../orchestrators/plan.js';
@@ -15,6 +16,11 @@ export default class Plan extends Command {
1516
'<%= config.bin %> <%= command.id %>',
1617
]
1718

19+
protected async catch(err: Error): Promise<void> {
20+
console.log(chalk.red(err.message));
21+
process.exit(1);
22+
}
23+
1824
static flags = {
1925
// flag with no value (-f, --force)
2026
force: Flags.boolean({ char: 'f' }),
@@ -28,19 +34,14 @@ export default class Plan extends Command {
2834
const { flags } = await this.parse(Plan)
2935
const reporter = new DefaultReporter()
3036

31-
try {
32-
if (flags.path) {
33-
this.log(`Applying Codify from: ${flags.path}`);
34-
}
35-
36-
const resolvedPath = path.resolve(flags.path ?? '.');
37+
if (flags.path) {
38+
this.log(`Applying Codify from: ${flags.path}`);
39+
}
3740

38-
const { plan } = await PlanOrchestrator.run(resolvedPath);
39-
reporter.displayPlan(plan);
41+
const resolvedPath = path.resolve(flags.path ?? '.');
4042

41-
} catch (error) {
42-
console.error(error);
43-
}
43+
const { plan } = await PlanOrchestrator.run(resolvedPath);
44+
reporter.displayPlan(plan);
4445

4546
process.exit(0);
4647
}

src/commands/uninstall.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Args, Command } from '@oclif/core'
2+
import chalk from 'chalk';
3+
24
import { UninstallOrchestrator } from '../orchestrators/uninstall.js';
35
import { DefaultReporter } from '../ui/reporters/default-reporter.js';
4-
import { ctx } from '../events/context.js';
56

67
export default class Uninstall extends Command {
78
static description = 'describe the command here'
@@ -21,6 +22,11 @@ export default class Uninstall extends Command {
2122
}),
2223
}
2324

25+
protected async catch(err: Error): Promise<void> {
26+
console.log(chalk.red(err.message));
27+
process.exit(1);
28+
}
29+
2430
public async run(): Promise<void> {
2531
const { raw } = await this.parse(Uninstall)
2632
new DefaultReporter()
@@ -36,12 +42,7 @@ export default class Uninstall extends Command {
3642
throw new Error('A resource id must be specified for uninstall. Ex: "codify uninstall homebrew"')
3743
}
3844

39-
try {
40-
await UninstallOrchestrator.run(args);
41-
} catch (error) {
42-
ctx.log(error);
43-
process.exit(1);
44-
}
45+
await UninstallOrchestrator.run(args);
4546

4647
process.exit(0);
4748
}

src/plugins/plugin-process.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,14 @@ import { PluginMessage } from './message.js';
77

88
const ipcMessageValidator = ajv.compile(IpcMessageSchema);
99

10-
type Resolve = (value: unknown) => void;
10+
type Resolve<T> = (value: T) => void;
1111
type Reject = (reason?: Error) => void;
1212

1313
const resultFunctionName = (cmd: string) => `${cmd}_Response`;
1414

1515
export class PluginProcess {
1616
process: ChildProcess;
1717

18-
constructor(process: ChildProcess) {
19-
this.process = process;
20-
}
21-
2218
static async start(pluginPath: string, name: string): Promise<PluginProcess> {
2319
const isTypescript = pluginPath.endsWith('.ts');
2420
const isTsxInstalled = PluginProcess.isTsxInstalled();
@@ -48,7 +44,11 @@ export class PluginProcess {
4844
return new PluginProcess(_process);
4945
}
5046

51-
async sendMessageForResult(message: PluginMessage): Promise<unknown> {
47+
constructor(process: ChildProcess) {
48+
this.process = process;
49+
}
50+
51+
async sendMessageForResult(message: PluginMessage): Promise<IpcMessage> {
5252
return new Promise((resolve, reject) => {
5353
const handler = new SendMessageForResultHandler(message, this.process, resolve, reject);
5454

@@ -76,14 +76,14 @@ export class PluginProcess {
7676
class SendMessageForResultHandler {
7777
messageToSend: PluginMessage;
7878
process: ChildProcess;
79-
promiseResolve: Resolve;
79+
promiseResolve: Resolve<IpcMessage>;
8080
promiseReject: Reject;
8181
timer: NodeJS.Timeout;
8282

8383
constructor(
8484
messageToSend: PluginMessage,
8585
process: ChildProcess,
86-
resolve: Resolve,
86+
resolve: Resolve<IpcMessage>,
8787
reject: Reject,
8888
timeout = 600_000, // Default time is 10 minutes for a command
8989
) {
@@ -98,11 +98,11 @@ class SendMessageForResultHandler {
9898
ctx.debug(JSON.stringify(incomingMessage, null, 2));
9999

100100
if (!this.validateIpcMessage(incomingMessage)) {
101-
return this.reject(new Error(`Bad message from plugin. ${JSON.stringify(incomingMessage, null, 2)}`))
101+
return this.reject(new Error(`Invalid message from plugin. ${JSON.stringify(incomingMessage, null, 2)}`))
102102
}
103103

104104
if (incomingMessage.cmd === resultFunctionName(this.messageToSend.cmd)) {
105-
this.resolve(incomingMessage.data);
105+
this.resolve(incomingMessage);
106106
}
107107
};
108108

@@ -115,7 +115,7 @@ class SendMessageForResultHandler {
115115
this.promiseReject(err);
116116
}
117117

118-
private resolve = (value: unknown) => {
118+
private resolve = (value: IpcMessage) => {
119119
if (this.timer.hasRef()) {
120120
clearTimeout(this.timer);
121121
}

src/plugins/plugin.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
InitializeResponseData,
33
InitializeResponseDataSchema,
4+
MessageStatus,
45
PlanResponseData,
56
PlanResponseDataSchema,
67
ValidateResponseData,
@@ -35,36 +36,44 @@ export class Plugin {
3536

3637
const initializeResponse = await this.process.sendMessageForResult({ cmd: 'initialize', data: {} });
3738

38-
if (!this.validateInitializeResponse(initializeResponse)) {
39+
if (!this.validateInitializeResponse(initializeResponse.data)) {
3940
throw new Error(`Invalid initialize response from plugin: ${this.name}`);
4041
}
4142

42-
initializeResponse.resourceDefinitions.forEach((d) => {
43+
for (const d of initializeResponse.data.resourceDefinitions) {
4344
this.resourceDependenciesMap.set(d.type, d.dependencies)
44-
});
45+
}
4546

46-
return initializeResponse;
47+
return initializeResponse.data;
4748
}
4849

4950
async validate(configs: ResourceConfig[]): Promise<ValidateResponseData> {
5051
const rawConfigs = configs.map((c) => c.raw);
51-
const response = await this.process!.sendMessageForResult({ cmd: 'validate', data: { configs: rawConfigs } });
52+
const { data, status } = await this.process!.sendMessageForResult({ cmd: 'validate', data: { configs: rawConfigs } });
53+
54+
if (status === MessageStatus.ERROR) {
55+
throw new Error(`Initialize error for plugin: "${this.name} \n\n` + data);
56+
}
5257

53-
if (!this.validateValidateResponse(response)) {
58+
if (!this.validateValidateResponse(data)) {
5459
throw new Error(`Plugin error: Invalid validate response from plugin: ${this.name}`);
5560
}
5661

57-
return response;
62+
return data;
5863
}
5964

6065
async plan(resource: ResourceConfig): Promise<PlanResponseData> {
61-
const response = await this.process!.sendMessageForResult({ cmd: 'plan', data: resource.raw });
66+
const { data, status } = await this.process!.sendMessageForResult({ cmd: 'plan', data: resource.raw });
67+
68+
if (status === MessageStatus.ERROR) {
69+
throw new Error(`Plan error for plugin: "${this.name}", resource: "${resource.type}" \n\n` + data);
70+
}
6271

63-
if (!this.validatePlanResponse(response)) {
72+
if (!this.validatePlanResponse(data)) {
6473
throw new Error(`Plugin error: plugin ${this.name} returned invalid plan response: ${JSON.stringify(planResponseValidator.errors, null, 2)}`)
6574
}
6675

67-
return response;
76+
return data;
6877
}
6978

7079
async apply(plan: PlanResponseData): Promise<void> {

0 commit comments

Comments
 (0)