Skip to content

Commit 3999b9f

Browse files
committed
feat: Added apply notes system
1 parent 793cb91 commit 3999b9f

10 files changed

Lines changed: 107 additions & 18 deletions

File tree

package-lock.json

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"dependencies": {
77
"@codifycli/ink-form": "0.0.12",
8-
"@codifycli/schemas": "1.1.0-beta8",
8+
"@codifycli/schemas": "1.1.0-beta.9",
99
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
1010
"@mischnic/json-sourcemap": "^0.1.1",
1111
"@oclif/core": "^4.0.8",
@@ -43,7 +43,7 @@
4343
},
4444
"description": "Codify is a configuration-as-code tool that declaratively installs and manages developer tools and applications. Check out https://dashboard.codifycli.com for an editor.",
4545
"devDependencies": {
46-
"@codifycli/plugin-core": "^1.1.0-beta19",
46+
"@codifycli/plugin-core": "^1.1.0-beta.25",
4747
"@oclif/prettier-config": "^0.2.1",
4848
"@types/chalk": "^2.2.0",
4949
"@types/cors": "^2.8.19",

src/entities/apply-note.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface ApplyNote {
2+
message: string;
3+
resourceType: string;
4+
}

src/entities/apply-result.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ResourceOperation } from '@codifycli/schemas';
22

33
import { PluginError } from '../common/errors.js';
4+
import { ApplyNote } from './apply-note.js';
45
import { ResourcePlan } from './plan.js';
56

67
export interface ApplyResultEntry {
@@ -13,6 +14,7 @@ export interface ApplyResultEntry {
1314
export interface ApplyResult {
1415
entries: ApplyResultEntry[];
1516
errors: PluginError[];
17+
notes: ApplyNote[];
1618

1719
isPartialFailure(): boolean;
1820
}
@@ -21,9 +23,8 @@ export function createApplyResult(
2123
succeededPlans: ResourcePlan[],
2224
failedErrors: PluginError[],
2325
skippedIds: Set<string>,
26+
notes: ApplyNote[] = [],
2427
): ApplyResult {
25-
const failedByType = new Map(failedErrors.map((e) => [e.resourceType, e]));
26-
2728
const entries: ApplyResultEntry[] = [
2829
...succeededPlans.map((p) => ({
2930
id: p.id,
@@ -46,6 +47,7 @@ export function createApplyResult(
4647
return {
4748
entries,
4849
errors: failedErrors,
50+
notes,
4951
isPartialFailure() {
5052
return failedErrors.length > 0;
5153
},

src/events/context.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CommandRequestData, CommandRequestResponseData } from '@codifycli/schemas';
1+
import { ApplyNoteRequestData, CommandRequestData, CommandRequestResponseData } from '@codifycli/schemas';
22
import { EventEmitter } from 'node:events';
33

44
export enum Event {
@@ -18,6 +18,7 @@ export enum Event {
1818
PRESS_KEY_TO_CONTINUE_COMPLETED = 'press_key_to_continue_completed',
1919
CODIFY_LOGIN_CREDENTIALS_REQUEST = 'codify_login_credentials_request',
2020
CODIFY_LOGIN_CREDENTIALS_COMPLETED = 'codify_login_credentials_completed',
21+
APPLY_NOTE_REQUEST = 'apply_note_request',
2122
}
2223

2324
export enum ProcessName {
@@ -141,6 +142,10 @@ export const ctx = new class {
141142
this.emitter.emit(Event.CODIFY_LOGIN_CREDENTIALS_COMPLETED, pluginName, credentials);
142143
}
143144

145+
applyNoteRequested(pluginName: string, data: ApplyNoteRequestData) {
146+
this.emitter.emit(Event.APPLY_NOTE_REQUEST, pluginName, data);
147+
}
148+
144149
async subprocess<T>(name: string, run: () => Promise<T>): Promise<T> {
145150
this.emitter.emit(Event.SUB_PROCESS_START, name);
146151
const result = await run();

src/plugins/plugin-manager.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import {
2+
ApplyNoteRequestData,
23
ImportResponseData, ResourceDefinition,
34
ResourceJson,
45
ValidateResponseData,
56
} from '@codifycli/schemas';
67

78
import { InternalError, PluginError } from '../common/errors.js';
89
import { config } from '../config.js';
10+
import { ApplyNote } from '../entities/apply-note.js';
911
import { ApplyResult, createApplyResult } from '../entities/apply-result.js';
1012
import { Plan, ResourcePlan } from '../entities/plan.js';
1113
import { Project } from '../entities/project.js';
1214
import { ResourceConfig } from '../entities/resource-config.js';
1315
import { ResourceInfo } from '../entities/resource-info.js';
14-
import { SubProcessName, SubprocessFinishStatus, ctx } from '../events/context.js';
16+
import { Event, SubProcessName, SubprocessFinishStatus, ctx } from '../events/context.js';
1517
import { groupBy } from '../utils/index.js';
1618
import { registerKillListeners } from '../utils/register-kill-listeners.js';
1719
import { Plugin } from './plugin.js';
@@ -142,6 +144,12 @@ export class PluginManager {
142144
const collectedErrors: PluginError[] = [];
143145
const skippedIds = new Set<string>();
144146
const succeededPlans: ResourcePlan[] = [];
147+
const collectedNotes: ApplyNote[] = [];
148+
149+
const noteListener = (_pluginName: string, data: ApplyNoteRequestData) => {
150+
collectedNotes.push({ message: data.message, resourceType: data.resourceType });
151+
};
152+
ctx.on(Event.APPLY_NOTE_REQUEST, noteListener);
145153

146154
for (const id of project.evaluationOrder ?? []) {
147155
if (skippedIds.has(id)) {
@@ -179,7 +187,8 @@ export class PluginManager {
179187
}
180188
}
181189

182-
return createApplyResult(succeededPlans, collectedErrors, skippedIds);
190+
ctx.emitter.removeListener(Event.APPLY_NOTE_REQUEST, noteListener);
191+
return createApplyResult(succeededPlans, collectedErrors, skippedIds, collectedNotes);
183192
}
184193

185194
async setVerbosityLevel(verbosityLevel: number): Promise<void> {

src/plugins/plugin-process.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
ApplyNoteRequestData,
3+
ApplyNoteRequestDataSchema,
24
CommandRequestData,
35
CommandRequestDataSchema,
46
CommandRequestResponseData,
@@ -19,6 +21,7 @@ import { PluginMessage } from './plugin-message.js';
1921
export const ipcMessageValidator = ajv.compile(IpcMessageV2Schema);
2022
export const commandRequestValidator = ajv.compile(CommandRequestDataSchema);
2123
export const pressKeyToContinueRequestValidator = ajv.compile(PressKeyToContinueRequestDataSchema);
24+
export const applyNoteRequestValidator = ajv.compile(ApplyNoteRequestDataSchema);
2225

2326
const DEFAULT_NODE_MODULES_DIR = '/usr/local/lib/codify/node_modules/'
2427

@@ -122,6 +125,21 @@ export class PluginProcess {
122125
}
123126

124127

128+
if (message.cmd === MessageCmd.APPLY_NOTE_REQUEST) {
129+
const { data, requestId } = message;
130+
if (!applyNoteRequestValidator(data)) {
131+
throw new Error(`Invalid apply note request from plugin ${pluginName}. ${JSON.stringify(applyNoteRequestValidator.errors, null, 2)}`);
132+
}
133+
134+
process.send({
135+
cmd: returnMessageCmd(MessageCmd.APPLY_NOTE_REQUEST),
136+
requestId,
137+
data: {},
138+
});
139+
140+
return ctx.applyNoteRequested(pluginName, data as unknown as ApplyNoteRequestData);
141+
}
142+
125143
if (message.cmd === MessageCmd.CODIFY_CREDENTIALS_REQUEST) {
126144
if (pluginName !== 'default') {
127145
throw new Error(`Only the default Codify plugin is able to request Codify credentials. ${pluginName}`);

src/ui/components/widgets/ApplyComplete.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ export function ApplyComplete({ result }: { result: ApplyResult }) {
9393
</Box>
9494
)}
9595

96+
{result.notes.length > 0 && (
97+
<Box flexDirection="column" marginTop={1}>
98+
{groupNotesByMessage(result.notes).map(({ message, resourceTypes }) => (
99+
<Box key={message}>
100+
<Text color="yellow" bold>{'⚠ '}</Text>
101+
<Text bold>{resourceTypes.join(', ')}: </Text>
102+
<Text>{message}</Text>
103+
</Box>
104+
))}
105+
</Box>
106+
)}
107+
96108
{!isPartial && (
97109
<Box marginTop={1}>
98110
<Text dimColor>Open a new terminal or source &apos;{path.basename(ShellUtils.getPrimaryShellRc())}&apos; for the new changes to be reflected</Text>
@@ -102,3 +114,18 @@ export function ApplyComplete({ result }: { result: ApplyResult }) {
102114
</Box>
103115
);
104116
}
117+
118+
function groupNotesByMessage(notes: ApplyResult['notes']): { message: string; resourceTypes: string[] }[] {
119+
const map = new Map<string, string[]>();
120+
for (const note of notes) {
121+
const existing = map.get(note.message);
122+
if (existing) {
123+
if (!existing.includes(note.resourceType)) {
124+
existing.push(note.resourceType);
125+
}
126+
} else {
127+
map.set(note.message, [note.resourceType]);
128+
}
129+
}
130+
return [...map.entries()].map(([message, resourceTypes]) => ({ message, resourceTypes }));
131+
}

src/ui/reporters/json-reporter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export class JsonReporter implements Reporter {
9999
resourceType: error.resourceType,
100100
data: error.errorData.data,
101101
})),
102+
notes: result.notes,
102103
}, null, 2));
103104
}
104105
}

src/ui/reporters/plain-reporter.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,5 +200,28 @@ Use this init flow to get started quickly with Codify.
200200
ctx.log('');
201201
ctx.log('Open a new terminal or source \'.zshrc\' for the new changes to be reflected');
202202
}
203+
204+
if (result.notes.length > 0) {
205+
ctx.log('');
206+
const grouped = groupNotesByMessage(result.notes);
207+
for (const { message, resourceTypes } of grouped) {
208+
ctx.log(chalk.yellow(`⚠ ${resourceTypes.join(', ')}: ${message}`));
209+
}
210+
}
211+
}
212+
}
213+
214+
function groupNotesByMessage(notes: ApplyResult['notes']): { message: string; resourceTypes: string[] }[] {
215+
const map = new Map<string, string[]>();
216+
for (const note of notes) {
217+
const existing = map.get(note.message);
218+
if (existing) {
219+
if (!existing.includes(note.resourceType)) {
220+
existing.push(note.resourceType);
221+
}
222+
} else {
223+
map.set(note.message, [note.resourceType]);
224+
}
203225
}
226+
return [...map.entries()].map(([message, resourceTypes]) => ({ message, resourceTypes }));
204227
}

0 commit comments

Comments
 (0)