Skip to content

Commit ed0d323

Browse files
committed
feat: refactored the code so that the final apply message is handled entirely inside the reporters.
1 parent 9156743 commit ed0d323

12 files changed

Lines changed: 121 additions & 111 deletions

File tree

src/common/base-command.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,7 @@ export abstract class BaseCommand extends Command {
146146

147147
protected async catch(err: Error): Promise<void> {
148148
if (err instanceof PluginError && this.reporter) {
149-
await this.reporter.hide();
150-
await this.reporter.displayPluginError([err]);
149+
await this.reporter.displayPluginError(err);
151150
process.exit(1);
152151
}
153152

src/orchestrators/apply.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { ProcessName, ctx } from '../events/context.js';
22
import { DefaultReporter } from '../ui/reporters/default-reporter.js';
33
import { Reporter } from '../ui/reporters/reporter.js';
4-
import { sleep } from '../utils/index.js';
54
import { VerbosityLevel } from '../utils/verbosity-level.js';
65
import { PlanOrchestrator } from './plan.js';
76

@@ -51,7 +50,6 @@ export const ApplyOrchestrator = {
5150
await reporter.displayApplyComplete(applyResult);
5251

5352
if (applyResult.isPartialFailure()) {
54-
await reporter.displayPluginError(applyResult.errors);
5553
process.exit(1);
5654
}
5755
},

src/orchestrators/destroy.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export class DestroyOrchestrator {
7777
await reporter.displayApplyComplete(applyResult);
7878

7979
if (applyResult.isPartialFailure()) {
80-
await reporter.displayPluginError(applyResult.errors);
8180
process.exit(1);
8281
}
8382
}

src/ui/apply-result-formatter.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import chalk from 'chalk';
2+
import { ResourceOperation } from '@codifycli/schemas';
3+
4+
import { ApplyResultEntry } from '../entities/apply-result.js';
5+
6+
export function applyEntryLabel(entry: ApplyResultEntry): string {
7+
if (entry.status === 'failed') return 'failed';
8+
if (entry.status === 'skipped') return 'skipped';
9+
switch (entry.operation) {
10+
case ResourceOperation.CREATE: return 'installed';
11+
case ResourceOperation.DESTROY: return 'destroyed';
12+
case ResourceOperation.MODIFY:
13+
case ResourceOperation.RECREATE: return 'modified';
14+
default: return 'applied';
15+
}
16+
}
17+
18+
export function applyEntryInkColor(entry: ApplyResultEntry): string {
19+
if (entry.status === 'failed') return 'red';
20+
if (entry.status === 'skipped') return 'gray';
21+
switch (entry.operation) {
22+
case ResourceOperation.CREATE: return 'green';
23+
case ResourceOperation.DESTROY: return 'red';
24+
case ResourceOperation.MODIFY:
25+
case ResourceOperation.RECREATE: return '#d4a017';
26+
default: return 'white';
27+
}
28+
}
29+
30+
export function applyEntryChalkColor(entry: ApplyResultEntry): (s: string) => string {
31+
if (entry.status === 'failed') return chalk.red;
32+
if (entry.status === 'skipped') return chalk.gray;
33+
switch (entry.operation) {
34+
case ResourceOperation.CREATE: return chalk.green;
35+
case ResourceOperation.DESTROY: return chalk.red;
36+
case ResourceOperation.MODIFY:
37+
case ResourceOperation.RECREATE: return chalk.yellow;
38+
default: return (s) => s;
39+
}
40+
}

src/ui/components/default-component.tsx

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { useAtom } from 'jotai';
55
import { EventEmitter } from 'node:events';
66
import React, { useLayoutEffect } from 'react';
77

8-
import { ResourceOperation } from '@codifycli/schemas';
9-
10-
import { ApplyResult, ApplyResultEntry } from '../../entities/apply-result.js';
8+
import { ApplyResult } from '../../entities/apply-result.js';
119
import { Plan, ResourcePlan } from '../../entities/plan.js';
1210
import { prettyFormatResourcePlan } from '../plan-pretty-printer.js';
1311
import { FileModificationResult } from '../../generators/index.js';
@@ -21,60 +19,11 @@ import { InitBanner } from './init/InitBanner.js';
2119
import { MultiSelect } from './multi-select/MultiSelect.js';
2220
import { PlanComponent } from './plan/plan.js';
2321
import { ProgressDisplay } from './progress/progress-display.js';
22+
import { ApplyComplete } from './widgets/ApplyComplete.js';
2423
import { PromptPressKeyToContinue } from './widgets/PromptPressKeyToContinue.js';
2524
import { SudoPasswordInput } from './widgets/SudoPasswordInput.js';
2625
import { TextInput } from './widgets/TextInput.js';
2726

28-
function entryLabel(entry: ApplyResultEntry): string {
29-
if (entry.status === 'failed') return 'failed';
30-
if (entry.status === 'skipped') return 'skipped';
31-
switch (entry.operation) {
32-
case ResourceOperation.CREATE: return 'installed';
33-
case ResourceOperation.DESTROY: return 'destroyed';
34-
case ResourceOperation.MODIFY:
35-
case ResourceOperation.RECREATE: return 'modified';
36-
default: return 'applied';
37-
}
38-
}
39-
40-
function entryColor(entry: ApplyResultEntry): string {
41-
if (entry.status === 'failed') return 'red';
42-
if (entry.status === 'skipped') return 'gray';
43-
switch (entry.operation) {
44-
case ResourceOperation.CREATE: return 'green';
45-
case ResourceOperation.DESTROY: return 'red';
46-
case ResourceOperation.MODIFY:
47-
case ResourceOperation.RECREATE: return '#d4a017';
48-
default: return 'white';
49-
}
50-
}
51-
52-
function ApplyCompleteComponent({ result }: { result: ApplyResult }) {
53-
const isPartial = result.isPartialFailure();
54-
return (
55-
<Box flexDirection="column" marginTop={1}>
56-
<Text bold color={isPartial ? 'red' : 'green'}>
57-
{isPartial ? '⚠ Apply completed with errors' : '🎉 Finished applying 🎉'}
58-
</Text>
59-
{result.entries.length > 0 && (
60-
<Box flexDirection="column" marginTop={1}>
61-
{result.entries.map((entry) => (
62-
<Box key={entry.id}>
63-
<Text>{entry.id.padEnd(30)}</Text>
64-
<Text color={entryColor(entry)}>{entryLabel(entry)}</Text>
65-
</Box>
66-
))}
67-
</Box>
68-
)}
69-
{!isPartial && (
70-
<Box marginTop={1}>
71-
<Text dimColor>Open a new terminal or source &apos;.zshrc&apos; for the new changes to be reflected</Text>
72-
</Box>
73-
)}
74-
</Box>
75-
);
76-
}
77-
7827
export function DefaultComponent(props: {
7928
emitter: EventEmitter
8029
onWriteReady?: (write: (data: string) => void) => void
@@ -96,7 +45,7 @@ export function DefaultComponent(props: {
9645
{
9746
renderStatus === RenderStatus.APPLY_COMPLETE && (
9847
<Static items={[renderData as ApplyResult]}>{
99-
(result, idx) => <ApplyCompleteComponent key={idx} result={result} />
48+
(result, idx) => <ApplyComplete key={idx} result={result} />
10049
}</Static>
10150
)
10251
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Box, Text } from 'ink';
2+
import React from 'react';
3+
4+
import { ApplyResult } from '../../../entities/apply-result.js';
5+
import { applyEntryInkColor, applyEntryLabel } from '../../apply-result-formatter.js';
6+
7+
export function ApplyComplete({ result }: { result: ApplyResult }) {
8+
const isPartial = result.isPartialFailure();
9+
return (
10+
<Box flexDirection="column" marginTop={1}>
11+
<Text bold color={isPartial ? 'red' : 'green'}>
12+
{isPartial ? '⚠ Apply completed with errors' : '🎉 Finished applying 🎉'}
13+
</Text>
14+
{result.entries.length > 0 && (
15+
<Box flexDirection="column" marginTop={1}>
16+
{result.entries.map((entry) => (
17+
<Box key={entry.id}>
18+
<Text>{entry.id.padEnd(30)}</Text>
19+
<Text color={applyEntryInkColor(entry)}>{applyEntryLabel(entry)}</Text>
20+
</Box>
21+
))}
22+
</Box>
23+
)}
24+
{!isPartial && (
25+
<Box marginTop={1}>
26+
<Text dimColor>Open a new terminal or source &apos;.zshrc&apos; for the new changes to be reflected</Text>
27+
</Box>
28+
)}
29+
</Box>
30+
);
31+
}

src/ui/reporters/default-reporter.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,19 +271,28 @@ export class DefaultReporter implements Reporter {
271271

272272
async displayApplyComplete(result: ApplyResult): Promise<void> {
273273
await this.updateRenderState(RenderStatus.APPLY_COMPLETE, result);
274-
}
275274

276-
async displayPluginError(errors: PluginError[]): Promise<void> {
277-
const validationErrors = errors.filter((e) => e.errorData.errorType === 'apply_validation');
278-
const genericErrors = errors.filter((e) => e.errorData.errorType !== 'apply_validation');
275+
if (result.isPartialFailure()) {
276+
const validationErrors = result.errors.filter((e) => e.errorData.errorType === 'apply_validation');
277+
const genericErrors = result.errors.filter((e) => e.errorData.errorType !== 'apply_validation');
279278

280-
if (validationErrors.length > 0) {
281-
const resourcePlans = validationErrors.map((e) => new ResourcePlan((e.errorData.data as any).plan));
282-
await this.updateRenderState(RenderStatus.APPLY_VALIDATION_ERROR, resourcePlans);
279+
if (validationErrors.length > 0) {
280+
const resourcePlans = validationErrors.map((e) => new ResourcePlan((e.errorData.data as any).plan));
281+
await this.updateRenderState(RenderStatus.APPLY_VALIDATION_ERROR, resourcePlans);
282+
}
283+
if (genericErrors.length > 0) {
284+
await this.updateRenderState(RenderStatus.PLUGIN_ERROR, genericErrors.map((e) => e.message));
285+
}
283286
}
284-
if (genericErrors.length > 0) {
285-
await this.updateRenderState(RenderStatus.PLUGIN_ERROR, genericErrors.map((e) => e.message));
287+
}
288+
289+
async displayPluginError(error: PluginError): Promise<void> {
290+
if (error.errorData.errorType === 'apply_validation') {
291+
const resourcePlan = new ResourcePlan((error.errorData.data as any).plan);
292+
await this.updateRenderState(RenderStatus.APPLY_VALIDATION_ERROR, [resourcePlan]);
293+
return;
286294
}
295+
await this.updateRenderState(RenderStatus.PLUGIN_ERROR, [error.message]);
287296
}
288297

289298
private log(log: string): void {

src/ui/reporters/json-reporter.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@ export class JsonReporter implements Reporter {
7474
throw new Error('Json reporter error: disableRawMode is not supported. Raw stdin mode requires interactive terminal access.');
7575
}
7676

77-
async displayPluginError(errors: PluginError[]): Promise<void> {
78-
console.log(JSON.stringify(errors.map((error) => ({
77+
async displayPluginError(error: PluginError): Promise<void> {
78+
console.log(JSON.stringify({
7979
errorType: error.errorData.errorType,
8080
message: error.message,
8181
pluginName: error.pluginName,
8282
resourceType: error.resourceType,
8383
data: error.errorData.data,
84-
})), null, 2));
84+
}, null, 2));
8585
}
8686

8787
async displayApplyComplete(result: ApplyResult): Promise<void> {
@@ -92,6 +92,13 @@ export class JsonReporter implements Reporter {
9292
operation: e.operation,
9393
status: e.status,
9494
})),
95+
errors: result.errors.map((error) => ({
96+
errorType: error.errorData.errorType,
97+
message: error.message,
98+
pluginName: error.pluginName,
99+
resourceType: error.resourceType,
100+
data: error.errorData.data,
101+
})),
95102
}, null, 2));
96103
}
97104
}

src/ui/reporters/plain-reporter.ts

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import chalk from 'chalk';
2-
import { CommandRequestData, ResourceOperation } from '@codifycli/schemas';
2+
import { CommandRequestData } from '@codifycli/schemas';
33
import readline from 'node:readline';
44

55
import { PluginError } from '../../common/errors.js';
6-
import { ApplyResult, ApplyResultEntry } from '../../entities/apply-result.js';
6+
import { ApplyResult } from '../../entities/apply-result.js';
77
import { Plan } from '../../entities/plan.js';
8+
import { applyEntryChalkColor, applyEntryLabel } from '../apply-result-formatter.js';
89
import { formatApplyValidationError } from '../plugin-error-formatter.js';
910
import { ResourceConfig } from '../../entities/resource-config.js';
1011
import { ResourceInfo } from '../../entities/resource-info.js';
@@ -14,30 +15,6 @@ import { ImportResult } from '../../orchestrators/import.js';
1415
import { prettyFormatPlan } from '../plan-pretty-printer.js';
1516
import { PromptType, Reporter } from './reporter.js';
1617

17-
function plainEntryLabel(entry: ApplyResultEntry): string {
18-
if (entry.status === 'failed') return 'failed';
19-
if (entry.status === 'skipped') return 'skipped';
20-
switch (entry.operation) {
21-
case ResourceOperation.CREATE: return 'installed';
22-
case ResourceOperation.DESTROY: return 'destroyed';
23-
case ResourceOperation.MODIFY:
24-
case ResourceOperation.RECREATE: return 'modified';
25-
default: return 'applied';
26-
}
27-
}
28-
29-
function plainEntryColor(entry: ApplyResultEntry): (s: string) => string {
30-
if (entry.status === 'failed') return chalk.red;
31-
if (entry.status === 'skipped') return chalk.gray;
32-
switch (entry.operation) {
33-
case ResourceOperation.CREATE: return chalk.green;
34-
case ResourceOperation.DESTROY: return chalk.red;
35-
case ResourceOperation.MODIFY:
36-
case ResourceOperation.RECREATE: return chalk.yellow;
37-
default: return (s) => s;
38-
}
39-
}
40-
4118
export class PlainReporter implements Reporter {
4219
private readonly rl = readline.createInterface(process.stdin, process.stdout);
4320
silent = false;
@@ -190,13 +167,11 @@ Use this init flow to get started quickly with Codify.
190167
);
191168
}
192169

193-
async displayPluginError(errors: PluginError[]): Promise<void> {
194-
for (const error of errors) {
195-
if (error.errorData.errorType === 'apply_validation') {
196-
ctx.log(chalk.red(formatApplyValidationError(error)));
197-
} else {
198-
ctx.log(chalk.red(error.message));
199-
}
170+
async displayPluginError(error: PluginError): Promise<void> {
171+
if (error.errorData.errorType === 'apply_validation') {
172+
ctx.log(chalk.red(formatApplyValidationError(error)));
173+
} else {
174+
ctx.log(chalk.red(error.message));
200175
}
201176
}
202177

@@ -210,13 +185,16 @@ Use this init flow to get started quickly with Codify.
210185
if (result.entries.length > 0) {
211186
ctx.log('');
212187
for (const entry of result.entries) {
213-
const label = plainEntryLabel(entry);
214-
const colorFn = plainEntryColor(entry);
215-
ctx.log(` ${entry.id.padEnd(30)}${colorFn(label)}`);
188+
ctx.log(` ${entry.id.padEnd(30)}${applyEntryChalkColor(entry)(applyEntryLabel(entry))}`);
216189
}
217190
}
218191

219-
if (!result.isPartialFailure()) {
192+
if (result.isPartialFailure()) {
193+
ctx.log('');
194+
for (const error of result.errors) {
195+
await this.displayPluginError(error);
196+
}
197+
} else {
220198
ctx.log('');
221199
ctx.log('Open a new terminal or source \'.zshrc\' for the new changes to be reflected');
222200
}

src/ui/reporters/reporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export interface Reporter {
8686

8787
disableRawMode(): Promise<void>
8888

89-
displayPluginError(errors: PluginError[]): Promise<void>;
89+
displayPluginError(error: PluginError): Promise<void>;
9090

9191
displayApplyComplete(result: ApplyResult): Promise<void>;
9292
}

0 commit comments

Comments
 (0)