Skip to content

Commit 5924bbf

Browse files
committed
feat: Added rendering for imports and added custom diff implementation to display the file changes
1 parent 6587cd5 commit 5924bbf

File tree

10 files changed

+137
-26
lines changed

10 files changed

+137
-26
lines changed

src/entities/project.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@ export class Project {
1616
resourceConfigs: ResourceConfig[];
1717
stateConfigs: ResourceConfig[] | null = null;
1818
evaluationOrder: null | string[] = null;
19-
path: string;
19+
20+
codifyFiles: string[];
2021

2122
sourceMaps?: SourceMapCache;
2223
planRequestsCache?: Map<string, PlanRequestData>
2324

2425
isDestroyProject = false;
2526

26-
static create(configs: ConfigBlock[], path: string, sourceMaps?: SourceMapCache): Project {
27+
static empty(): Project {
28+
return Project.create([], []);
29+
}
30+
31+
static create(configs: ConfigBlock[], codifyFiles: string[], sourceMaps?: SourceMapCache): Project {
2732
const projectConfigs = configs.filter((u) => u.configClass === ConfigType.PROJECT);
2833
if (projectConfigs.length > 1) {
2934
throw new Error(`Only one project config can be specified. Found ${projectConfigs.length}. \n\n
@@ -33,16 +38,16 @@ ${JSON.stringify(projectConfigs, null, 2)}`);
3338
return new Project(
3439
(projectConfigs[0] as ProjectConfig) ?? null,
3540
configs.filter((u) => u.configClass !== ConfigType.PROJECT) as ResourceConfig[],
36-
path,
41+
codifyFiles,
3742
sourceMaps,
3843
);
3944
}
4045

41-
constructor(projectConfig: ProjectConfig | null, resourceConfigs: ResourceConfig[], path: string, sourceMaps?: SourceMapCache) {
46+
constructor(projectConfig: ProjectConfig | null, resourceConfigs: ResourceConfig[], codifyFiles: string[], sourceMaps?: SourceMapCache) {
4247
this.projectConfig = projectConfig;
4348
this.resourceConfigs = resourceConfigs;
4449
this.sourceMaps = sourceMaps;
45-
this.path = path;
50+
this.codifyFiles = codifyFiles;
4651

4752
this.addUniqueNamesForDuplicateResources()
4853
}
@@ -107,7 +112,7 @@ ${JSON.stringify(projectConfigs, null, 2)}`);
107112
const uninstallProject = new Project(
108113
this.projectConfig,
109114
this.resourceConfigs,
110-
this.path,
115+
this.codifyFiles,
111116
this.sourceMaps,
112117
)
113118

src/orchestrators/import.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CodifyParser } from '../parser/index.js';
88
import { DependencyMap, PluginManager } from '../plugins/plugin-manager.js';
99
import { PromptType, Reporter } from '../ui/reporters/reporter.js';
1010
import { FileUtils } from '../utils/file.js';
11+
import { FileModificationCalculator, ModificationType } from '../utils/file-modification-calculator.js';
1112
import { InitializeOrchestrator } from './initialize.js';
1213

1314
export type RequiredParameters = Map<string, RequiredParameter[]>;
@@ -73,8 +74,10 @@ export class ImportOrchestrator {
7374
ctx.processFinished(ProcessName.IMPORT)
7475
reporter.displayImportResult(importResult);
7576

76-
ImportOrchestrator.attachResourceInfo(importResult, resourceInfoList);
77-
await ImportOrchestrator.saveResults(reporter, importResult, project)
77+
const additionalResourceInfo = await pluginManager.getMultipleResourceInfo(project.resourceConfigs.map((r) => r.type));
78+
resourceInfoList.push(...additionalResourceInfo);
79+
80+
await ImportOrchestrator.saveResults(reporter, importResult, project, resourceInfoList)
7881
}
7982

8083
static async getImportedConfigs(
@@ -133,7 +136,7 @@ ${JSON.stringify(unsupportedTypeIds)}`);
133136
ctx.subprocessFinished(SubProcessName.VALIDATE)
134137
}
135138

136-
private static async saveResults(reporter: Reporter, importResult: ImportResult, project: Project): Promise<void> {
139+
private static async saveResults(reporter: Reporter, importResult: ImportResult, project: Project, resourceInfoList: ResourceInfo[]): Promise<void> {
137140
const projectExists = !project.isEmpty();
138141
const multipleCodifyFiles = project.codifyFiles.length > 1;
139142

@@ -147,25 +150,40 @@ ${JSON.stringify(unsupportedTypeIds)}`);
147150
'\nWhich file would you like to update?',
148151
project.codifyFiles,
149152
)
150-
await ImportOrchestrator.saveToFile(file, importResult);
153+
await ImportOrchestrator.updateExistingFile(reporter, file, importResult, resourceInfoList);
151154

152155
} else if (promptResult.startsWith('Update existing file')) {
153-
await ImportOrchestrator.saveToFile(project.codifyFiles[0], importResult);
156+
await ImportOrchestrator.updateExistingFile(reporter, project.codifyFiles[0], importResult, resourceInfoList);
154157

155158
} else if (promptResult === 'In a new file') {
156159
const newFileName = await ImportOrchestrator.generateNewImportFileName();
157-
await ImportOrchestrator.saveToFile(newFileName, importResult);
160+
await ImportOrchestrator.saveNewFile(newFileName, importResult);
158161
}
159162
}
160163

161-
private static async saveToFile(filePath: string, importResult: ImportResult): Promise<void> {
164+
private static async updateExistingFile(reporter: Reporter, filePath: string, importResult: ImportResult, resourceInfoList: ResourceInfo[]): Promise<void> {
162165
const existing = await CodifyParser.parse(filePath);
163-
164-
for (const resource of importResult.result) {
165-
existing.addOrReplace(resource);
166+
ImportOrchestrator.attachResourceInfo(importResult.result, resourceInfoList);
167+
ImportOrchestrator.attachResourceInfo(existing.resourceConfigs, resourceInfoList);
168+
169+
const modificationCalculator = new FileModificationCalculator(existing);
170+
const result = modificationCalculator.calculate(importResult.result.map((resource) => ({
171+
modification: ModificationType.INSERT_OR_UPDATE,
172+
resource
173+
})));
174+
175+
reporter.displayFileModification(result.diff);
176+
const shouldSave = await reporter.promptConfirmation(`Save to file (${filePath})?`);
177+
if (!shouldSave) {
178+
process.exit(0);
166179
}
167180

168-
console.log(JSON.stringify(existing.resourceConfigs.map((r) => r.raw), null, 2))
181+
await FileUtils.writeFile(filePath, result.newFile);
182+
}
183+
184+
private static async saveNewFile(filePath: string, importResult: ImportResult): Promise<void> {
185+
const newFile = JSON.stringify(importResult, null, 2);
186+
await FileUtils.writeFile(filePath, newFile);
169187
}
170188

171189
private static async generateNewImportFileName(): Promise<string> {
@@ -185,9 +203,13 @@ ${JSON.stringify(unsupportedTypeIds)}`);
185203
}
186204

187205
// We have to attach additional info to the imported configs to make saving easier
188-
private static attachResourceInfo(importResult: ImportResult, resourceInfoList: ResourceInfo[]): void {
189-
importResult.result.forEach((resource) => {
206+
private static attachResourceInfo(resources: ResourceConfig[], resourceInfoList: ResourceInfo[]): void {
207+
resources.forEach((resource) => {
190208
const matchedInfo = resourceInfoList.find((info) => info.type === resource.type)!;
209+
if (!matchedInfo) {
210+
throw new Error(`Could not find type ${resource.type} in the resource info`);
211+
}
212+
191213
resource.attachResourceInfo(matchedInfo);
192214
})
193215
}

src/ui/components/default-component.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Plan } from '../../entities/plan.js';
1010
import { ImportResult } from '../../orchestrators/import.js';
1111
import { RenderEvent } from '../reporters/reporter.js';
1212
import { RenderStatus, store } from '../store/index.js';
13+
import { FileModificationDisplay } from './file-modification/FileModification.js';
1314
import { ImportResultComponent } from './import/import-result.js';
1415
import { PlanComponent } from './plan/plan.js';
1516
import { ProgressDisplay } from './progress/progress-display.js';
@@ -103,5 +104,12 @@ export function DefaultComponent(props: {
103104
}</Static>
104105
)
105106
}
107+
{
108+
renderStatus === RenderStatus.DISPLAY_FILE_MODIFICATION && (
109+
<Static items={[renderData as string]}>{
110+
(diff, idx) => <FileModificationDisplay diff={diff} key={idx}/>
111+
}</Static>
112+
)
113+
}
106114
</Box>
107115
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Box, Text } from 'ink';
2+
import React from 'react';
3+
4+
export function FileModificationDisplay(props: {
5+
diff: string,
6+
}) {
7+
return <Box flexDirection="column">
8+
<Box borderColor="green" borderStyle="round">
9+
<Text>File Modification</Text>
10+
</Box>
11+
<Text>The following changes will be made</Text>
12+
<Text> </Text>
13+
<Text>{props.diff}</Text>
14+
</Box>
15+
}

src/ui/components/import/import-result.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ export function ImportResultComponent(props: {
1717
<Text>{ JSON.stringify(result.map((r) => r.raw), null, 2)}</Text>
1818
{
1919
errors.length > 0 && (<Box flexDirection="column">
20-
<Text bold={true} color={'red'}>The following configs failed to import:</Text>
20+
<Text bold={true} color={'yellow'}>The following configs failed to import:</Text>
2121
<OrderedList>
2222
{
2323
errors.map((e, idx) => <OrderedList.Item key={idx}>
24-
<Text color={'red'}>{e}</Text>
24+
<Text color={'yellow'}>{e}</Text>
2525
</OrderedList.Item>)
2626
}
2727
</OrderedList>

src/ui/reporters/default-reporter.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ export class DefaultReporter implements Reporter {
152152
await sleep(100); // This gives the renderer enough time to complete before the prompt exits
153153
}
154154

155+
displayFileModification(diff: string) {
156+
this.updateRenderState(RenderStatus.DISPLAY_FILE_MODIFICATION, diff);
157+
}
158+
155159
private log(args: string): void {
156160
this.renderEmitter.emit(RenderEvent.LOG, args);
157161
}

src/ui/reporters/reporter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export interface Reporter {
5454
promptUserForValues(resources: Array<ResourceInfo>, promptType: PromptType): Promise<ResourceConfig[]>;
5555

5656
displayImportResult(importResult: ImportResult): void;
57+
58+
displayFileModification(diff: string): void
5759
}
5860

5961
export enum ReporterType {

src/ui/store/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum RenderStatus {
1111
PROGRESS,
1212
DISPLAY_PLAN,
1313
DISPLAY_IMPORT_RESULT,
14+
DISPLAY_FILE_MODIFICATION,
1415
IMPORT_PROMPT,
1516
PROMPT_CONFIRMATION,
1617
PROMPT_OPTIONS,

src/utils/file-modification-calculator.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class FileModificationCalculator {
4242
this.indentString = fileIndents.indent;
4343
}
4444

45-
async calculate(modifications: ModifiedResource[]): Promise<FileModificationResult> {
45+
calculate(modifications: ModifiedResource[]): FileModificationResult {
4646
this.validate(modifications);
4747

4848
let newFile = this.existingFile!.contents.trimEnd();
@@ -120,16 +120,65 @@ export class FileModificationCalculator {
120120
}
121121

122122
diff(a: string, b: string): string {
123-
const diff = Diff.diffLines(a, b);
123+
const diff = Diff.diffChars(a, b);
124124

125-
let result = '';
125+
const diffedLines = Diff.diffLines(a, b)
126+
.flatMap((change) => change.value.split(/\n/).map(l => ({ added: change.added, removed: change.removed})))
127+
128+
let diffGroups = [];
129+
let pointerStart = -1;
130+
131+
for (let counter = 0; counter < diffedLines.length; counter++) {
132+
const changeAhead = diffedLines.slice(counter, counter + 5).some((change) => change.added || change.removed);
133+
const changeBehind = diffedLines.slice(counter - 5, counter).some((change) => change.added || change.removed);
134+
135+
if (pointerStart === -1 && changeAhead) {
136+
pointerStart = counter;
137+
continue;
138+
}
139+
140+
if (pointerStart !== -1 && !changeAhead && !changeBehind) {
141+
diffGroups.push({ start: pointerStart, end: counter })
142+
pointerStart = -1;
143+
continue;
144+
}
145+
146+
if (pointerStart !== -1 && counter === diffedLines.length - 1) {
147+
diffGroups.push({ start: pointerStart, end: counter })
148+
}
149+
}
150+
151+
let diffString = '';
126152
diff.forEach((part) => {
127-
result += part.added ? chalk.green(part.value) :
153+
diffString += part.added ? chalk.green(part.value) :
128154
part.removed ? chalk.red(part.value) :
129155
part.value;
130156
});
131157

132-
return result;
158+
const diffLines = diffString.split(/\n/);
159+
const result = [];
160+
161+
for (const group of diffGroups) {
162+
const maxLineNumberWidth = group.end.toString().length;
163+
164+
result.push(`${chalk.bold(`Lines ${group.start} to line ${group.end}:`)}
165+
${diffLines.slice(group.start, group.end).map((l, idx) => {
166+
const change = diffedLines[group.start + idx];
167+
168+
if (change.added && change.removed) {
169+
return `${chalk.gray((group.start + idx).toString().padEnd(maxLineNumberWidth, ' '))} ${chalk.yellow('~')}${l}`
170+
} else if (change.added) {
171+
return `${chalk.gray((group.start + idx).toString().padEnd(maxLineNumberWidth, ' '))} ${chalk.green('+')}${l}`
172+
} else if (change.removed) {
173+
return `${chalk.gray((group.start + idx.toString()).padEnd(maxLineNumberWidth, ' '))} ${chalk.red('-')}${l}`
174+
} else {
175+
return `${chalk.gray((group.start + idx).toString().padEnd(maxLineNumberWidth, ' '))} ${l}`
176+
}
177+
}).join('\n')}`
178+
);
179+
}
180+
181+
return result.join('\n\n');
133182
}
134183

135184
// Insert always works at the end

src/utils/file.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,9 @@ export class FileUtils {
3131
const resolvedPath = path.resolve(filePath);
3232
return fs.readFile(resolvedPath, 'utf8')
3333
}
34+
35+
static async writeFile(filePath: string, contents: string): Promise<void> {
36+
const resolvedPath = path.resolve(filePath);
37+
await fs.writeFile(resolvedPath, contents, 'utf8')
38+
}
3439
}

0 commit comments

Comments
 (0)