From 5feca437ab1dfba94ac170fa923aeebfa3373b29 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Tue, 4 Jan 2022 10:35:06 +0000 Subject: [PATCH] [server] infer extensions and add a comment This commit adds vcode extensions to the inferred .gitpod.yml and adds a comment to the top, that links to the documentation. --- .../server/src/config/config-inferrer.spec.ts | 92 +++++++++++++++++-- .../server/src/config/config-inferrer.ts | 38 ++++++++ .../src/config/configuration-service.ts | 13 ++- 3 files changed, 131 insertions(+), 12 deletions(-) diff --git a/components/server/src/config/config-inferrer.spec.ts b/components/server/src/config/config-inferrer.spec.ts index 9fa0599187f781..554c56bc496c5d 100644 --- a/components/server/src/config/config-inferrer.spec.ts +++ b/components/server/src/config/config-inferrer.spec.ts @@ -23,6 +23,36 @@ async function expect(files: {[path:string]:string}, config: WorkspaceConfig): P chai.assert.equal(JSON.stringify(result, null, ' '), JSON.stringify(config, null, ' ')); } +describe('config serializer', () => { + it('serialized proper YAML', + async () => { + const config: WorkspaceConfig = { + tasks: [ + { + 'init': "yarn install", + 'command': "yarn run build" + } + ], + vscode: { + extensions: [ + 'foo', 'bar' + ] + } + } + const cf = new ConfigInferrer() + chai.assert.equal(cf.toYaml(config), +`tasks: + - init: yarn install + command: yarn run build +vscode: + extensions: + - foo + - bar +`); + } + ) +}); + describe('config inferrer', () => { it('check node', async () => expect({ @@ -43,7 +73,12 @@ describe('config inferrer', () => { init: "yarn install && yarn run build", command: "yarn run watch" } - ] + ], + vscode: { + extensions: [ + 'dbaeumer.vscode-eslint' + ] + } }) ), it('[java] mvn wrapper', @@ -55,7 +90,14 @@ describe('config inferrer', () => { { init: "./mvnw install -DskipTests=false" } - ] + ], + vscode: { + extensions: [ + 'redhat.java', + 'vscjava.vscode-java-debug', + 'vscjava.vscode-maven' + ] + } }) ), it('[java] mvn', @@ -66,7 +108,14 @@ describe('config inferrer', () => { { init: "mvn install -DskipTests=false" } - ] + ], + vscode: { + extensions: [ + 'redhat.java', + 'vscjava.vscode-java-debug', + 'vscjava.vscode-maven' + ] + } }) ), it('[java] gradle', @@ -78,7 +127,13 @@ describe('config inferrer', () => { { init: "gradle build" } - ] + ], + vscode: { + extensions: [ + 'redhat.java', + 'vscjava.vscode-java-debug' + ] + } }) ), it('[java] gradle wrapper', @@ -90,7 +145,13 @@ describe('config inferrer', () => { { init: "./gradlew build" } - ] + ], + vscode: { + extensions: [ + 'redhat.java', + 'vscjava.vscode-java-debug' + ] + } }) ), it('[python] pip install', @@ -101,7 +162,12 @@ describe('config inferrer', () => { { init: "pip install -r requirements.txt" } - ] + ], + vscode: { + extensions: [ + 'ms-python.python' + ] + } }) ), it('[go] go install', @@ -113,7 +179,12 @@ describe('config inferrer', () => { init: "go get && go build ./... && go test ./...", command: "go run" } - ] + ], + vscode: { + extensions: [ + 'golang.go' + ] + } }) ), it('[rust] cargo', @@ -125,7 +196,12 @@ describe('config inferrer', () => { init: "cargo build", command: "cargo watch -x run" } - ] + ], + vscode: { + extensions: [ + 'matklad.rust-analyzer' + ] + } }) ), it('[make] make', diff --git a/components/server/src/config/config-inferrer.ts b/components/server/src/config/config-inferrer.ts index 3745b17ce21681..5f1b7302595e91 100644 --- a/components/server/src/config/config-inferrer.ts +++ b/components/server/src/config/config-inferrer.ts @@ -65,6 +65,7 @@ export class ConfigInferrer { } catch (e) { console.log(e, pckjsonContent); } + this.addExtension(ctx, 'dbaeumer.vscode-eslint'); } protected async checkJava(ctx: Context): Promise { @@ -74,6 +75,8 @@ export class ConfigInferrer { cmd = './gradlew'; } this.addCommand(ctx.config, cmd + ' build', 'init'); + this.addExtension(ctx, 'redhat.java'); + this.addExtension(ctx, 'vscjava.vscode-java-debug'); return; } if (await ctx.exists('pom.xml')) { @@ -82,10 +85,23 @@ export class ConfigInferrer { cmd = './mvnw'; } this.addCommand(ctx.config, cmd + ' install -DskipTests=false', 'init'); + this.addExtension(ctx, 'redhat.java'); + this.addExtension(ctx, 'vscjava.vscode-java-debug'); + this.addExtension(ctx, 'vscjava.vscode-maven'); return; } } + protected addExtension(ctx: Context, extensionName: string) { + if (!ctx.config.vscode || !ctx.config.vscode.extensions) { + ctx.config.vscode = { + extensions: [] + }; + } + if (ctx.config.vscode.extensions?.indexOf(extensionName) === -1) + ctx.config.vscode.extensions!.push(extensionName); + } + protected async isMake(ctx: Context) { return await ctx.exists('Makefile') || await ctx.exists('makefile'); } @@ -105,15 +121,20 @@ export class ConfigInferrer { } if (await ctx.exists('requirements.txt')) { this.addCommand(ctx.config, 'pip install -r requirements.txt', 'init'); + this.addExtension(ctx, 'ms-python.python'); } else if (await ctx.exists('setup.py')) { this.addCommand(ctx.config, 'pip install .', 'init'); + this.addExtension(ctx, 'ms-python.python'); } if (await ctx.exists('main.py')) { this.addCommand(ctx.config, 'python main.py', 'command'); + this.addExtension(ctx, 'ms-python.python'); } else if (await ctx.exists('app.py')) { this.addCommand(ctx.config, 'python app.py', 'command'); + this.addExtension(ctx, 'ms-python.python'); } else if (await ctx.exists('runserver.py')) { this.addCommand(ctx.config, 'python runserver.py', 'command'); + this.addExtension(ctx, 'ms-python.python'); } } @@ -123,6 +144,7 @@ export class ConfigInferrer { this.addCommand(ctx.config, 'go build ./...', 'init'); this.addCommand(ctx.config, 'go test ./...', 'init'); this.addCommand(ctx.config, 'go run', 'command'); + this.addExtension(ctx, 'golang.go'); } } @@ -130,6 +152,7 @@ export class ConfigInferrer { if (await ctx.exists('Cargo.toml')) { this.addCommand(ctx.config, 'cargo build', 'init'); this.addCommand(ctx.config, 'cargo watch -x run', 'command'); + this.addExtension(ctx, 'matklad.rust-analyzer'); } } @@ -166,4 +189,19 @@ export class ConfigInferrer { } config.tasks[0][phase] = (existing ? existing + ' && ' : '') + command; } + + toYaml(config: WorkspaceConfig): string { + const i = ' '; + let tasks = ''; + if (config.tasks) { + tasks = `tasks:\n${i}- ${config.tasks.map(task => Object.entries(task).map(([phase, command]) => `${phase}: ${command}`).join('\n ')).join('\n - ')}` + } + let vscode = ''; + if (config.vscode?.extensions) { + vscode = `vscode:\n${i}extensions:\n${config.vscode.extensions.map(extension => `${i + i}- ${extension}`).join('\n')}` + } + return `${tasks} +${vscode} +`; + } } diff --git a/components/server/src/config/configuration-service.ts b/components/server/src/config/configuration-service.ts index ea0b2de6e65252..e158739fdc3cf0 100644 --- a/components/server/src/config/configuration-service.ts +++ b/components/server/src/config/configuration-service.ts @@ -36,7 +36,8 @@ export class ConfigurationService { } // eagerly fetch for all files that the inferrer usually asks for. this.requestedPaths.forEach(path => !(path in cache) && readFile(path)); - const config: WorkspaceConfig = await new ConfigInferrer().getConfig({ + const configInferrer = new ConfigInferrer(); + const config: WorkspaceConfig = await configInferrer.getConfig({ config: {}, read: readFile, exists: async (path: string) => !!(await readFile(path)), @@ -44,8 +45,13 @@ export class ConfigurationService { if (!config.tasks) { return; } - const configString = `tasks:\n - ${config.tasks.map(task => Object.entries(task).map(([phase, command]) => `${phase}: ${command}`).join('\n ')).join('\n - ')}`; - return configString; + const configString = configInferrer.toYaml(config); + return `# This configuration file was automatically generated by Gitpod. +# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) +# and commit this file to your remote git repository to share the goodness with others. + +${configString} +`; } async fetchRepositoryConfiguration(ctx: TraceContext, user: User, contextURL: string): Promise { @@ -54,7 +60,6 @@ export class ConfigurationService { return configString; } - protected async getRepositoryFileProviderAndCommitContext(ctx: TraceContext, user: User, contextURLOrContext: string | CommitContext): Promise<{fileProvider: FileProvider, commitContext: CommitContext}> { let commitContext: CommitContext; if (typeof contextURLOrContext === 'string') {