Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit e9fcf75

Browse files
Export configurations of Che tasks to config file (#295)
* Export configurations of Che tasks to config file Signed-off-by: Roman Nikitenko <[email protected]>
1 parent ec29e99 commit e9fcf75

14 files changed

+438
-119
lines changed

plugins/containers-plugin/src/containers-service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ export class ContainersService {
9191
}
9292
if (runtime.commands) {
9393
container.commands = [];
94-
runtime.commands.forEach(command => {
94+
const cheCommands = runtime.commands.filter(command => command.type === 'exec');
95+
cheCommands.forEach(command => {
9596
if (command.attributes && command.attributes.machineName && command.attributes.machineName !== name) {
9697
return;
9798
}

plugins/containers-plugin/src/containers-tree-data-provider.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class ContainersTreeDataProvider implements theia.TreeDataProvider<ITreeN
106106
name: commandName,
107107
tooltip: 'execute the command',
108108
iconPath: 'fa-cogs medium-yellow',
109-
command: { id: 'task:run', arguments: ['che', commandName] }
109+
command: { id: 'task:run', arguments: [this.getRootPath(), commandName] }
110110
});
111111
});
112112
}
@@ -214,6 +214,14 @@ export class ContainersTreeDataProvider implements theia.TreeDataProvider<ITreeN
214214
this.onDidChangeTreeDataEmitter.fire();
215215
}
216216

217+
private getRootPath(): string {
218+
const workspaceFolders = theia.workspace.workspaceFolders;
219+
if (!workspaceFolders || workspaceFolders.length < 1) {
220+
return '/projects';
221+
}
222+
return workspaceFolders[0].uri.path;
223+
}
224+
217225
private getRandId(): string {
218226
let uniqueId = '';
219227
for (let counter = 0; counter < 1000; counter++) {

plugins/task-plugin/src/che-task-backend-module.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ import { PreviewUrlOpenService } from './preview/preview-url-open-service';
2424
import { CheWorkspaceClient } from './che-workspace-client';
2525
import { LaunchConfigurationsExporter } from './export/launch-configs-exporter';
2626
import { TaskConfigurationsExporter } from './export/task-configs-exporter';
27-
import { ConfigurationsExporter, ExportConfigurationsManager } from './export/export-configs-manager';
27+
import { ExportConfigurationsManager, ConfigurationsExporter } from './export/export-configs-manager';
28+
import { CheTaskConfigsExtractor } from './extract/che-task-configs-extractor';
29+
import { ConfigFileLaunchConfigsExtractor } from './extract/config-file-launch-configs-extractor';
30+
import { ConfigFileTasksExtractor } from './extract/config-file-task-configs-extractor';
31+
import { VsCodeLaunchConfigsExtractor } from './extract/vscode-launch-configs-extractor';
32+
import { VsCodeTaskConfigsExtractor } from './extract/vscode-task-configs-extractor';
2833

2934
const container = new Container();
3035
container.bind(CheTaskProvider).toSelf().inSingletonScope();
@@ -42,6 +47,11 @@ container.bind(PreviewUrlOpenService).toSelf().inSingletonScope();
4247
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(TaskConfigurationsExporter).inSingletonScope();
4348
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(LaunchConfigurationsExporter).inSingletonScope();
4449
container.bind(ExportConfigurationsManager).toSelf().inSingletonScope();
50+
container.bind(CheTaskConfigsExtractor).toSelf().inSingletonScope();
51+
container.bind(ConfigFileTasksExtractor).toSelf().inSingletonScope();
52+
container.bind(ConfigFileLaunchConfigsExtractor).toSelf().inSingletonScope();
53+
container.bind(VsCodeLaunchConfigsExtractor).toSelf().inSingletonScope();
54+
container.bind(VsCodeTaskConfigsExtractor).toSelf().inSingletonScope();
4555

4656
container.bind(PreviewUrlsWidget).toSelf().inTransientScope();
4757
container.bind(PreviewUrlsWidgetFactory).toDynamicValue(ctx => ({

plugins/task-plugin/src/export/export-configs-manager.ts

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,21 @@ export const ConfigurationsExporter = Symbol('ConfigurationsExporter');
1818
/** Exports content with configurations in the config file */
1919
export interface ConfigurationsExporter {
2020

21-
/** Type of the exporter corresponds to type of command which brings content with configs */
22-
readonly type: string;
23-
2421
/**
25-
* Exports given content with configurations in the config file of given workspace folder
26-
* @param configsContent content with configurations for export
22+
* Exports configurations in the config file of given workspace folder
2723
* @param workspaceFolder workspace folder for exporting configs in the config file
24+
* @param commands commands with configurations for export
2825
*/
29-
export(configsContent: string, workspaceFolder: theia.WorkspaceFolder): void;
26+
export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void;
27+
}
28+
/** Contains configurations as array of object and as raw content and is used at getting configurations from config file for example */
29+
export interface Configurations<T> {
30+
31+
/** Raw content with configurations from config file */
32+
content: string;
33+
34+
/** Configurations as array of objects */
35+
configs: T[];
3036
}
3137

3238
/** Reads the commands from the current Che workspace and exports task and launch configurations in the config files. */
@@ -47,36 +53,13 @@ export class ExportConfigurationsManager {
4753

4854
const cheCommands = await this.cheWorkspaceClient.getCommands();
4955
for (const exporter of this.exporters) {
50-
const configsContent = this.extractConfigsContent(exporter.type, cheCommands);
51-
if (!configsContent) {
52-
continue;
53-
}
54-
55-
this.exportContent(configsContent, exporter, workspaceFolders);
56+
this.doExport(workspaceFolders, cheCommands, exporter);
5657
}
5758
}
5859

59-
private exportContent(configsContent: string, exporter: ConfigurationsExporter, workspaceFolders: theia.WorkspaceFolder[]) {
60+
private doExport(workspaceFolders: theia.WorkspaceFolder[], cheCommands: cheApi.workspace.Command[], exporter: ConfigurationsExporter) {
6061
for (const workspaceFolder of workspaceFolders) {
61-
exporter.export(configsContent, workspaceFolder);
62+
exporter.export(workspaceFolder, cheCommands);
6263
}
6364
}
64-
65-
private extractConfigsContent(type: string, commands: cheApi.workspace.Command[]): string {
66-
const configCommands = commands.filter(command => command.type === type);
67-
if (configCommands.length === 0) {
68-
return '';
69-
}
70-
71-
if (configCommands.length > 1) {
72-
console.warn(`Found duplicate entry for type ${type}`);
73-
}
74-
75-
const configCommand = configCommands[0];
76-
if (!configCommand || !configCommand.attributes || !configCommand.attributes.actionReferenceContent) {
77-
return '';
78-
}
79-
80-
return configCommand.attributes.actionReferenceContent;
81-
}
8265
}

plugins/task-plugin/src/export/launch-configs-exporter.ts

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,57 +8,93 @@
88
* SPDX-License-Identifier: EPL-2.0
99
**********************************************************************/
1010

11-
import { injectable } from 'inversify';
11+
import { injectable, inject } from 'inversify';
1212
import * as theia from '@theia/plugin';
13+
import { che as cheApi } from '@eclipse-che/api';
1314
import { resolve } from 'path';
14-
import { readFileSync, writeFileSync, format, modify, parse } from '../utils';
15+
import { writeFileSync, modify } from '../utils';
1516
import { ConfigurationsExporter } from './export-configs-manager';
17+
import { ConfigFileLaunchConfigsExtractor } from '../extract/config-file-launch-configs-extractor';
18+
import { VsCodeLaunchConfigsExtractor } from '../extract/vscode-launch-configs-extractor';
1619

1720
const CONFIG_DIR = '.theia';
1821
const LAUNCH_CONFIG_FILE = 'launch.json';
1922
const formattingOptions = { tabSize: 4, insertSpaces: true, eol: '' };
2023

21-
export const VSCODE_LAUNCH_TYPE = 'vscode-launch';
22-
2324
/** Exports content with launch configurations in the config file. */
2425
@injectable()
2526
export class LaunchConfigurationsExporter implements ConfigurationsExporter {
26-
readonly type: string = VSCODE_LAUNCH_TYPE;
2727

28-
export(configsContent: string, workspaceFolder: theia.WorkspaceFolder): void {
28+
@inject(ConfigFileLaunchConfigsExtractor)
29+
protected readonly configFileLaunchConfigsExtractor: ConfigFileLaunchConfigsExtractor;
30+
31+
@inject(VsCodeLaunchConfigsExtractor)
32+
protected readonly vsCodeLaunchConfigsExtractor: VsCodeLaunchConfigsExtractor;
33+
34+
export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void {
2935
const launchConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path);
30-
const existingContent = readFileSync(launchConfigFileUri);
31-
if (configsContent === existingContent) {
32-
return;
33-
}
36+
const configFileConfigs = this.configFileLaunchConfigsExtractor.extract(launchConfigFileUri);
37+
const vsCodeConfigs = this.vsCodeLaunchConfigsExtractor.extract(commands);
3438

35-
const configsJson = parse(configsContent);
36-
if (!configsJson || !configsJson.configurations) {
39+
const configFileContent = configFileConfigs.content;
40+
if (configFileContent) {
41+
this.saveConfigs(launchConfigFileUri, configFileContent, this.merge(configFileConfigs.configs, vsCodeConfigs.configs, this.getConsoleConflictLogger()));
3742
return;
3843
}
3944

40-
const existingJson = parse(existingContent);
41-
if (!existingJson || !existingJson.configurations) {
42-
writeFileSync(launchConfigFileUri, format(configsContent, formattingOptions));
43-
return;
45+
const vsCodeConfigsContent = vsCodeConfigs.content;
46+
if (vsCodeConfigsContent) {
47+
this.saveConfigs(launchConfigFileUri, vsCodeConfigsContent, vsCodeConfigs.configs);
4448
}
45-
46-
const mergedConfigs = this.merge(existingJson.configurations, configsJson.configurations);
47-
const result = modify(configsContent, ['configurations'], mergedConfigs, formattingOptions);
48-
writeFileSync(launchConfigFileUri, result);
4949
}
5050

51-
private merge(existingConfigs: theia.DebugConfiguration[], newConfigs: theia.DebugConfiguration[]): theia.DebugConfiguration[] {
52-
const result: theia.DebugConfiguration[] = Object.assign([], newConfigs);
53-
for (const existing of existingConfigs) {
54-
if (!newConfigs.some(config => config.name === existing.name)) {
55-
result.push(existing);
51+
private merge(configurations1: theia.DebugConfiguration[],
52+
configurations2: theia.DebugConfiguration[],
53+
conflictHandler: (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => void): theia.DebugConfiguration[] {
54+
55+
const result: theia.DebugConfiguration[] = Object.assign([], configurations1);
56+
57+
for (const config2 of configurations2) {
58+
const conflict = configurations1.find(config1 => config1.name === config2.name);
59+
if (!conflict) {
60+
result.push(config2);
61+
continue;
62+
}
63+
64+
if (this.areEqual(config2, conflict)) {
65+
continue;
5666
}
67+
68+
conflictHandler(conflict, config2);
5769
}
70+
5871
return result;
5972
}
6073

74+
private areEqual(config1: theia.DebugConfiguration, config2: theia.DebugConfiguration): boolean {
75+
const { type: type1, name: name1, request: request1, ...properties1 } = config1;
76+
const { type: type2, name: name2, request: request2, ...properties2 } = config2;
77+
78+
if (type1 !== type2 || name1 !== name2 || request1 !== request2) {
79+
return false;
80+
}
81+
82+
return JSON.stringify(properties1) === JSON.stringify(properties2);
83+
}
84+
6185
private getConfigFileUri(rootDir: string): string {
6286
return resolve(rootDir.toString(), CONFIG_DIR, LAUNCH_CONFIG_FILE);
6387
}
88+
89+
private saveConfigs(launchConfigFileUri: string, content: string, configurations: theia.DebugConfiguration[]): void {
90+
const result = modify(content, ['configurations'], configurations, formattingOptions);
91+
writeFileSync(launchConfigFileUri, result);
92+
}
93+
94+
private getConsoleConflictLogger(): (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => void {
95+
return (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => {
96+
console.warn(`Conflict at exporting launch configurations: ${JSON.stringify(config1)} and ${JSON.stringify(config2)}`,
97+
`The configuration: ${JSON.stringify(config2)} is ignored`);
98+
};
99+
}
64100
}

plugins/task-plugin/src/export/task-configs-exporter.ts

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
* SPDX-License-Identifier: EPL-2.0
99
**********************************************************************/
1010

11-
import { injectable } from 'inversify';
11+
import { injectable, inject } from 'inversify';
1212
import * as theia from '@theia/plugin';
13+
import * as startPoint from '../task-plugin-backend';
14+
import { che as cheApi } from '@eclipse-che/api';
1315
import { TaskConfiguration } from '@eclipse-che/plugin';
1416
import { resolve } from 'path';
15-
import { readFileSync, writeFileSync, format, modify, parse } from '../utils';
17+
import { writeFileSync, modify } from '../utils';
18+
import { CheTaskConfigsExtractor } from '../extract/che-task-configs-extractor';
19+
import { VsCodeTaskConfigsExtractor } from '../extract/vscode-task-configs-extractor';
1620
import { ConfigurationsExporter } from './export-configs-manager';
21+
import { ConfigFileTasksExtractor } from '../extract/config-file-task-configs-extractor';
1722

1823
const CONFIG_DIR = '.theia';
1924
const TASK_CONFIG_FILE = 'tasks.json';
@@ -24,42 +29,97 @@ export const VSCODE_TASK_TYPE = 'vscode-task';
2429
/** Exports configurations of tasks in the config file. */
2530
@injectable()
2631
export class TaskConfigurationsExporter implements ConfigurationsExporter {
27-
readonly type: string = VSCODE_TASK_TYPE;
2832

29-
export(tasksContent: string, workspaceFolder: theia.WorkspaceFolder): void {
33+
@inject(ConfigFileTasksExtractor)
34+
protected readonly configFileTasksExtractor: ConfigFileTasksExtractor;
35+
36+
@inject(CheTaskConfigsExtractor)
37+
protected readonly cheTaskConfigsExtractor: CheTaskConfigsExtractor;
38+
39+
@inject(VsCodeTaskConfigsExtractor)
40+
protected readonly vsCodeTaskConfigsExtractor: VsCodeTaskConfigsExtractor;
41+
42+
export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void {
3043
const tasksConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path);
31-
const existingContent = readFileSync(tasksConfigFileUri);
32-
if (tasksContent === existingContent) {
33-
return;
34-
}
44+
const configFileTasks = this.configFileTasksExtractor.extract(tasksConfigFileUri);
45+
46+
const cheTasks = this.cheTaskConfigsExtractor.extract(commands);
47+
const vsCodeTasks = this.vsCodeTaskConfigsExtractor.extract(commands);
48+
const devfileConfigs = this.merge(cheTasks, vsCodeTasks.configs, this.getOutputChannelConflictLogger());
3549

36-
const tasksJson = parse(tasksContent);
37-
if (!tasksJson || !tasksJson.tasks) {
50+
const configFileContent = configFileTasks.content;
51+
if (configFileContent) {
52+
this.saveConfigs(tasksConfigFileUri, configFileContent, this.merge(configFileTasks.configs, devfileConfigs, this.getConsoleConflictLogger()));
3853
return;
3954
}
4055

41-
const existingJson = parse(existingContent);
42-
if (!existingJson || !existingJson.tasks) {
43-
writeFileSync(tasksConfigFileUri, format(tasksContent, formattingOptions));
56+
const vsCodeTasksContent = vsCodeTasks.content;
57+
if (vsCodeTasksContent) {
58+
this.saveConfigs(tasksConfigFileUri, vsCodeTasksContent, devfileConfigs);
4459
return;
4560
}
4661

47-
const mergedConfigs = this.merge(existingJson.tasks, tasksJson.tasks);
48-
const result = modify(tasksContent, ['tasks'], mergedConfigs, formattingOptions);
49-
writeFileSync(tasksConfigFileUri, result);
62+
if (cheTasks) {
63+
this.saveConfigs(tasksConfigFileUri, '', cheTasks);
64+
}
5065
}
5166

52-
private merge(existingConfigs: TaskConfiguration[], newConfigs: TaskConfiguration[]): TaskConfiguration[] {
53-
const result: TaskConfiguration[] = Object.assign([], newConfigs);
54-
for (const existing of existingConfigs) {
55-
if (!newConfigs.some(config => config.label === existing.label)) {
56-
result.push(existing);
67+
private merge(configurations1: TaskConfiguration[],
68+
configurations2: TaskConfiguration[],
69+
conflictHandler: (config1: TaskConfiguration, config2: TaskConfiguration) => void): TaskConfiguration[] {
70+
71+
const result: TaskConfiguration[] = Object.assign([], configurations1);
72+
73+
for (const config2 of configurations2) {
74+
const conflict = configurations1.find(config1 => config1.label === config2.label);
75+
if (!conflict) {
76+
result.push(config2);
77+
continue;
5778
}
79+
80+
if (this.areEqual(config2, conflict)) {
81+
continue;
82+
}
83+
84+
conflictHandler(conflict, config2);
5885
}
86+
5987
return result;
6088
}
6189

90+
private areEqual(config1: TaskConfiguration, config2: TaskConfiguration): boolean {
91+
const { type: type1, label: label1, ...properties1 } = config1;
92+
const { type: type2, label: label2, ...properties2 } = config2;
93+
94+
if (type1 !== type2 || label1 !== label2) {
95+
return false;
96+
}
97+
98+
return JSON.stringify(properties1) === JSON.stringify(properties2);
99+
}
100+
62101
private getConfigFileUri(rootDir: string): string {
63102
return resolve(rootDir.toString(), CONFIG_DIR, TASK_CONFIG_FILE);
64103
}
104+
105+
private saveConfigs(tasksConfigFileUri: string, content: string, configurations: TaskConfiguration[]): void {
106+
const result = modify(content, ['tasks'], configurations, formattingOptions);
107+
writeFileSync(tasksConfigFileUri, result);
108+
}
109+
110+
private getOutputChannelConflictLogger(): (config1: TaskConfiguration, config2: TaskConfiguration) => void {
111+
return (config1: TaskConfiguration, config2: TaskConfiguration) => {
112+
const outputChannel = startPoint.getOutputChannel();
113+
outputChannel.show();
114+
outputChannel.appendLine(`Conflict at exporting task configurations: ${JSON.stringify(config1)} and ${JSON.stringify(config2)}`);
115+
outputChannel.appendLine(`The configuration: ${JSON.stringify(config2)} is ignored`);
116+
};
117+
}
118+
119+
private getConsoleConflictLogger(): (config1: TaskConfiguration, config2: TaskConfiguration) => void {
120+
return (config1: TaskConfiguration, config2: TaskConfiguration) => {
121+
console.warn(`Conflict at exporting task configurations: ${JSON.stringify(config1)} and ${JSON.stringify(config2)}`,
122+
`The configuration: ${JSON.stringify(config2)} is ignored`);
123+
};
124+
}
65125
}

0 commit comments

Comments
 (0)