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

Commit

Permalink
Export configurations of Che tasks to config file
Browse files Browse the repository at this point in the history
Signed-off-by: Roman Nikitenko <rnikiten@redhat.com>
  • Loading branch information
RomanNikitenko committed Jun 19, 2019
1 parent 5782188 commit 0fe8fa0
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 117 deletions.
12 changes: 11 additions & 1 deletion plugins/task-plugin/src/che-task-backend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import { PreviewUrlOpenService } from './preview/preview-url-open-service';
import { CheWorkspaceClient } from './che-workspace-client';
import { LaunchConfigurationsExporter } from './export/launch-configs-exporter';
import { TaskConfigurationsExporter } from './export/task-configs-exporter';
import { ConfigurationsExporter, ExportConfigurationsManager } from './export/export-configs-manager';
import { ExportConfigurationsManager, ConfigurationsExporter } from './export/export-configs-manager';
import { CheTaskConfigsExtractor } from './extract/che-task-configs-extractor';
import { ConfigFileLaunchConfigsExtractor } from './extract/config-file-launch-configs-extractor';
import { ConfigFileTasksExtractor } from './extract/config-file-task-configs-extractor';
import { VsCodeLaunchConfigsExtractor } from './extract/vscode-launch-configs-extractor';
import { VsCodeTaskConfigsExtractor } from './extract/vscode-task-configs-extractor';

const container = new Container();
container.bind(CheTaskProvider).toSelf().inSingletonScope();
Expand All @@ -42,6 +47,11 @@ container.bind(PreviewUrlOpenService).toSelf().inSingletonScope();
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(TaskConfigurationsExporter).inSingletonScope();
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(LaunchConfigurationsExporter).inSingletonScope();
container.bind(ExportConfigurationsManager).toSelf().inSingletonScope();
container.bind(CheTaskConfigsExtractor).toSelf().inSingletonScope();
container.bind(ConfigFileTasksExtractor).toSelf().inSingletonScope();
container.bind(ConfigFileLaunchConfigsExtractor).toSelf().inSingletonScope();
container.bind(VsCodeLaunchConfigsExtractor).toSelf().inSingletonScope();
container.bind(VsCodeTaskConfigsExtractor).toSelf().inSingletonScope();

container.bind(PreviewUrlsWidget).toSelf().inTransientScope();
container.bind(PreviewUrlsWidgetFactory).toDynamicValue(ctx => ({
Expand Down
47 changes: 15 additions & 32 deletions plugins/task-plugin/src/export/export-configs-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ export const ConfigurationsExporter = Symbol('ConfigurationsExporter');
/** Exports content with configurations in the config file */
export interface ConfigurationsExporter {

/** Type of the exporter corresponds to type of command which brings content with configs */
readonly type: string;

/**
* Exports given content with configurations in the config file of given workspace folder
* @param configsContent content with configurations for export
* Exports configurations in the config file of given workspace folder
* @param workspaceFolder workspace folder for exporting configs in the config file
* @param commands commands with configurations for export
*/
export(configsContent: string, workspaceFolder: theia.WorkspaceFolder): void;
export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void;
}
/** Contains configurations as array of object and as raw content and is used at getting configurations from config file for example */
export interface Configurations<T> {

/** Raw content with configurations from config file */
content: string;

/** Configurations as array of objects */
configs: T[];
}

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

const cheCommands = await this.cheWorkspaceClient.getCommands();
for (const exporter of this.exporters) {
const configsContent = this.extractConfigsContent(exporter.type, cheCommands);
if (!configsContent) {
continue;
}

this.exportContent(configsContent, exporter, workspaceFolders);
this.doExport(workspaceFolders, cheCommands, exporter);
}
}

private exportContent(configsContent: string, exporter: ConfigurationsExporter, workspaceFolders: theia.WorkspaceFolder[]) {
private doExport(workspaceFolders: theia.WorkspaceFolder[], cheCommands: cheApi.workspace.Command[], exporter: ConfigurationsExporter) {
for (const workspaceFolder of workspaceFolders) {
exporter.export(configsContent, workspaceFolder);
exporter.export(workspaceFolder, cheCommands);
}
}

private extractConfigsContent(type: string, commands: cheApi.workspace.Command[]): string {
const configCommands = commands.filter(command => command.type === type);
if (configCommands.length === 0) {
return '';
}

if (configCommands.length > 1) {
console.warn(`Found duplicate entry for type ${type}`);
}

const configCommand = configCommands[0];
if (!configCommand || !configCommand.attributes || !configCommand.attributes.actionReferenceContent) {
return '';
}

return configCommand.attributes.actionReferenceContent;
}
}
89 changes: 64 additions & 25 deletions plugins/task-plugin/src/export/launch-configs-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,96 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { injectable } from 'inversify';
import { injectable, inject } from 'inversify';
import * as theia from '@theia/plugin';
import { che as cheApi } from '@eclipse-che/api';
import { resolve } from 'path';
import { readFileSync, writeFileSync, format, modify, parse } from '../utils';
import { writeFileSync, modify } from '../utils';
import { ConfigurationsExporter } from './export-configs-manager';
import { ConfigFileLaunchConfigsExtractor } from '../extract/config-file-launch-configs-extractor';
import { VsCodeLaunchConfigsExtractor } from '../extract/vscode-launch-configs-extractor';

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

export const VSCODE_LAUNCH_TYPE = 'vscode-launch';

/** Exports content with launch configurations in the config file. */
@injectable()
export class LaunchConfigurationsExporter implements ConfigurationsExporter {
readonly type: string = VSCODE_LAUNCH_TYPE;

export(configsContent: string, workspaceFolder: theia.WorkspaceFolder): void {
@inject(ConfigFileLaunchConfigsExtractor)
protected readonly configFileLaunchConfigsExtractor: ConfigFileLaunchConfigsExtractor;

@inject(VsCodeLaunchConfigsExtractor)
protected readonly vsCodeLaunchConfigsExtractor: VsCodeLaunchConfigsExtractor;

export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void {
const launchConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path);
const existingContent = readFileSync(launchConfigFileUri);
if (configsContent === existingContent) {
return;
}
const configFileConfigs = this.configFileLaunchConfigsExtractor.extract(launchConfigFileUri);
const vsCodeConfigs = this.vsCodeLaunchConfigsExtractor.extract(commands);

const configsJson = parse(configsContent);
if (!configsJson || !configsJson.configurations) {
const configFileContent = configFileConfigs.content;
if (configFileContent) {
this.saveConfigs(launchConfigFileUri, configFileContent, this.merge(vsCodeConfigs.configs, configFileConfigs.configs));
return;
}

const existingJson = parse(existingContent);
if (!existingJson || !existingJson.configurations) {
writeFileSync(launchConfigFileUri, format(configsContent, formattingOptions));
return;
const vsCodeConfigsContent = vsCodeConfigs.content;
if (vsCodeConfigsContent) {
this.saveConfigs(launchConfigFileUri, vsCodeConfigsContent, vsCodeConfigs.configs);
}

const mergedConfigs = this.merge(existingJson.configurations, configsJson.configurations);
const result = modify(configsContent, ['configurations'], mergedConfigs, formattingOptions);
writeFileSync(launchConfigFileUri, result);
}

private merge(existingConfigs: theia.DebugConfiguration[], newConfigs: theia.DebugConfiguration[]): theia.DebugConfiguration[] {
const result: theia.DebugConfiguration[] = Object.assign([], newConfigs);
for (const existing of existingConfigs) {
if (!newConfigs.some(config => config.name === existing.name)) {
result.push(existing);
private merge(configurations1: theia.DebugConfiguration[], configurations2: theia.DebugConfiguration[]): theia.DebugConfiguration[] {
const result: theia.DebugConfiguration[] = Object.assign([], configurations1);

for (const config2 of configurations2) {
const conflict = configurations1.find(config1 => config1.name === config2.name);
if (!conflict) {
result.push(config2);
continue;
}

if (this.areEqual(config2, conflict)) {
continue;
}

const newName = this.getUniqueName(config2.name, [...configurations1, ...configurations2]);
result.push({ ...config2, name: newName });
}

return result;
}

private areEqual(config1: theia.DebugConfiguration, config2: theia.DebugConfiguration): boolean {
const { type: type1, name: name1, request: request1, ...properties1 } = config1;
const { type: type2, name: name2, request: request2, ...properties2 } = config2;

if (type1 !== type2 || name1 !== name2 || request1 !== request2) {
return false;
}

return JSON.stringify(properties1) === JSON.stringify(properties2);
}

private getUniqueName(name: string, configs: theia.DebugConfiguration[]): string {
let counter = 1;
let newName = '';

do {
newName = `${name}_${counter}`;
counter++;
} while (configs.some(config => config.name === newName));

return newName;
}

private getConfigFileUri(rootDir: string): string {
return resolve(rootDir.toString(), CONFIG_DIR, LAUNCH_CONFIG_FILE);
}

private saveConfigs(launchConfigFileUri: string, content: string, configurations: theia.DebugConfiguration[]) {
const result = modify(content, ['configurations'], configurations, formattingOptions);
writeFileSync(launchConfigFileUri, result);
}
}
95 changes: 74 additions & 21 deletions plugins/task-plugin/src/export/task-configs-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { injectable } from 'inversify';
import { injectable, inject } from 'inversify';
import * as theia from '@theia/plugin';
import { che as cheApi } from '@eclipse-che/api';
import { TaskConfiguration } from '@eclipse-che/plugin';
import { resolve } from 'path';
import { readFileSync, writeFileSync, format, modify, parse } from '../utils';
import { writeFileSync, modify } from '../utils';
import { CheTaskConfigsExtractor } from '../extract/che-task-configs-extractor';
import { VsCodeTaskConfigsExtractor } from '../extract/vscode-task-configs-extractor';
import { ConfigurationsExporter } from './export-configs-manager';
import { ConfigFileTasksExtractor } from '../extract/config-file-task-configs-extractor';

const CONFIG_DIR = '.theia';
const TASK_CONFIG_FILE = 'tasks.json';
Expand All @@ -24,42 +28,91 @@ export const VSCODE_TASK_TYPE = 'vscode-task';
/** Exports configurations of tasks in the config file. */
@injectable()
export class TaskConfigurationsExporter implements ConfigurationsExporter {
readonly type: string = VSCODE_TASK_TYPE;

export(tasksContent: string, workspaceFolder: theia.WorkspaceFolder): void {
@inject(ConfigFileTasksExtractor)
protected readonly configFileTasksExtractor: ConfigFileTasksExtractor;

@inject(CheTaskConfigsExtractor)
protected readonly cheTaskConfigsExtractor: CheTaskConfigsExtractor;

@inject(VsCodeTaskConfigsExtractor)
protected readonly vsCodeTaskConfigsExtractor: VsCodeTaskConfigsExtractor;

export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void {
const tasksConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path);
const existingContent = readFileSync(tasksConfigFileUri);
if (tasksContent === existingContent) {
return;
}
const configFileTasks = this.configFileTasksExtractor.extract(tasksConfigFileUri);

const cheTasks = this.cheTaskConfigsExtractor.extract(commands);
const vsCodeTasks = this.vsCodeTaskConfigsExtractor.extract(commands);
const newConfigs = this.merge(vsCodeTasks.configs, cheTasks);

const tasksJson = parse(tasksContent);
if (!tasksJson || !tasksJson.tasks) {
const configFileContent = configFileTasks.content;
if (configFileContent) {
this.saveConfigs(tasksConfigFileUri, configFileContent, this.merge(newConfigs, configFileTasks.configs));
return;
}

const existingJson = parse(existingContent);
if (!existingJson || !existingJson.tasks) {
writeFileSync(tasksConfigFileUri, format(tasksContent, formattingOptions));
const vsCodeTasksContent = vsCodeTasks.content;
if (vsCodeTasksContent) {
this.saveConfigs(tasksConfigFileUri, vsCodeTasksContent, newConfigs);
return;
}

const mergedConfigs = this.merge(existingJson.tasks, tasksJson.tasks);
const result = modify(tasksContent, ['tasks'], mergedConfigs, formattingOptions);
writeFileSync(tasksConfigFileUri, result);
if (cheTasks) {
this.saveConfigs(tasksConfigFileUri, '', cheTasks);
}
}

private merge(existingConfigs: TaskConfiguration[], newConfigs: TaskConfiguration[]): TaskConfiguration[] {
const result: TaskConfiguration[] = Object.assign([], newConfigs);
for (const existing of existingConfigs) {
if (!newConfigs.some(config => config.label === existing.label)) {
result.push(existing);
private merge(configurations1: TaskConfiguration[], configurations2: TaskConfiguration[]): TaskConfiguration[] {
const result: TaskConfiguration[] = Object.assign([], configurations1);

for (const config2 of configurations2) {
const conflict = configurations1.find(config1 => config1.label === config2.label);
if (!conflict) {
result.push(config2);
continue;
}

if (this.areEqual(config2, conflict)) {
continue;
}

const newLabel = this.getUniqueLabel(config2.label, [...configurations1, ...configurations2]);
result.push({ ...config2, label: newLabel });
}

return result;
}

private areEqual(config1: TaskConfiguration, config2: TaskConfiguration): boolean {
const { type: type1, label: label1, ...properties1 } = config1;
const { type: type2, label: label2, ...properties2 } = config2;

if (type1 !== type2 || label1 !== label2) {
return false;
}

return JSON.stringify(properties1) === JSON.stringify(properties2);
}

private getUniqueLabel(label: string, configs: TaskConfiguration[]): string {
let counter = 1;
let newLabel = '';

do {
newLabel = `${label}_${counter}`;
counter++;
} while (configs.some(config => config.label === newLabel));

return newLabel;
}

private getConfigFileUri(rootDir: string): string {
return resolve(rootDir.toString(), CONFIG_DIR, TASK_CONFIG_FILE);
}

private saveConfigs(tasksConfigFileUri: string, content: string, configurations: TaskConfiguration[]) {
const result = modify(content, ['tasks'], configurations, formattingOptions);
writeFileSync(tasksConfigFileUri, result);
}
}
34 changes: 34 additions & 0 deletions plugins/task-plugin/src/extract/che-task-configs-extractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { injectable } from 'inversify';
import { che as cheApi } from '@eclipse-che/api';
import { TaskConfiguration } from '@eclipse-che/plugin';
import { toTaskConfiguration } from '../task/converter';
import { VSCODE_TASK_TYPE } from './vscode-task-configs-extractor';
import { VSCODE_LAUNCH_TYPE } from './vscode-launch-configs-extractor';

/** Extracts CHE configurations of tasks. */
@injectable()
export class CheTaskConfigsExtractor {

extract(commands: cheApi.workspace.Command[]): TaskConfiguration[] {
// TODO filter should be changed according to task type after resolving https://github.com/eclipse/che/issues/12710
const filteredCommands = commands.filter(command =>
command.type !== VSCODE_TASK_TYPE &&
command.type !== VSCODE_LAUNCH_TYPE);

if (filteredCommands.length === 0) {
return [];
}

return filteredCommands.map(command => toTaskConfiguration(command));
}
}
Loading

0 comments on commit 0fe8fa0

Please sign in to comment.