Skip to content

Commit

Permalink
RESTEasy can be excluded if using code.quarkus.io
Browse files Browse the repository at this point in the history
Reads the data from code.quarkus.io or code.quarkus.redhat.com
to see which quarkus extensions are required.
This means if code.quarkus.io is used, then resteasy is not required.
Reads the Open API of the code quarkus instance to determine if
the option to prevent sample code being generated is present.
If it is, then it adds another step to the project
generation wizard to specify if sample code should be generated.

Closes redhat-developer#322

Signed-off-by: David Thompson <davthomp@redhat.com>
  • Loading branch information
datho7561 committed May 13, 2021
1 parent 478d2c7 commit f6d0879
Show file tree
Hide file tree
Showing 12 changed files with 1,862 additions and 2,204 deletions.
3,854 changes: 1,690 additions & 2,164 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@
]
},
"scripts": {
"preinstall": "npx npm-force-resolutions",
"vscode:prepublish": "webpack --mode production",
"compile": "webpack --mode none",
"watch": "webpack --mode development --watch --info-verbosity verbose",
Expand All @@ -256,7 +255,9 @@
"devDependencies": {
"@types/chai": "^4.2.3",
"@types/chai-fs": "^2.0.2",
"@types/ejs": "^3.0.5",
"@types/fs-extra": "^7.0.0",
"@types/js-yaml": "^4.0.0",
"@types/lodash": "^4.14.149",
"@types/mocha": "^5.2.6",
"@types/node": "^10.14.16",
Expand Down Expand Up @@ -290,6 +291,7 @@
"find-up": "^4.1.0",
"fs-extra": "^8.0.1",
"glob": "^7.1.4",
"js-yaml": "^4.0.0",
"lodash": "^4.17.21",
"request": "^2.88.0",
"request-promise": "^4.2.4",
Expand Down
7 changes: 4 additions & 3 deletions src/definitions/QExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export class QExtension {
artifactId: string;
isRequired: boolean;

constructor(name: string, category: string, description: string, labels: string[], groupId: string, artifactId: string) {
constructor(name: string, category: string, description: string, labels: string[], groupId: string, artifactId: string, isRequired) {
this.name = name;
this.category = category;
this.description = description;
this.labels = labels;
this.groupId = groupId;
this.artifactId = artifactId;
this.isRequired = name === 'RESTEasy JAX-RS'; // 'RESTEasy JAX-RS' is included in every Quarkus project
this.isRequired = isRequired;
}

getGroupIdArtifactIdString() {
Expand All @@ -61,7 +61,7 @@ export function convertToQExtension(extension: APIExtension): QExtension {
artifactId = extension.id;
}
return new QExtension(extension.name, extension.category, extension.description,
extension.labels, groupId, artifactId);
extension.labels, groupId, artifactId, extension.default);
}

/**
Expand All @@ -76,4 +76,5 @@ export interface APIExtension {
shortName: string;
category: string;
order: Number;
default: boolean; // Uses a TypeScript builtin :|
}
1 change: 1 addition & 0 deletions src/definitions/inputState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface ProjectGenState extends State {
packageName: string;
resourceName: string;
targetDir: Uri;
isGenerateSampleCode: boolean;
}

export interface AddExtensionsState extends State {
Expand Down
2 changes: 2 additions & 0 deletions src/test/vscodeUiTest/ProjectGenerationWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export class ProjectGenerationWizard extends InputBox {
}
}
await wizard.next();
await wizard.selectQuickPick(0); // Don't generate example code
await wizard.confirm();

const dialog: OpenDialog = await DialogHandler.getOpenDialog();
await dialog.selectPath(options.dest);
Expand Down
53 changes: 31 additions & 22 deletions src/test/vscodeUiTest/suite/projectGenerationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,74 +102,83 @@ describe('Project generation tests', function() {

await wizard.sendKeys(Key.DOWN, Key.UP);

expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('1 extension selected');
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('0 extensions selected');
await wizard.sendKeys(Key.DOWN, Key.DOWN);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('2 extensions selected');
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('1 extension selected');
await wizard.sendKeys(Key.DOWN, Key.DOWN, Key.DOWN);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('3 extensions selected');
await wizard.sendKeys(Key.DOWN);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('2 extensions selected');
await wizard.sendKeys(Key.DOWN);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('1 extension selected');
await wizard.sendKeys(Key.DOWN);
await wizard.confirm();
expect(await wizard.getNthQuickPickItemLabel(0)).to.have.string('0 extension selected');
await wizard.cancel();
});

/**
* Tests if the project generation wizard has correct
* step values at the wizard's title bar: (1/7), (2/7)
* step values at the wizard's title bar: (1/8), (2/8)
*/
it('should have correct step values', async function() {
this.timeout(30000);
const wizard: ProjectGenerationWizard = await ProjectGenerationWizard.openWizard(driver);
expect(await wizard.getInputBoxTitle()).to.have.string('1/7');
expect(await wizard.getInputBoxTitle()).to.have.string('1/8');
expect(await wizard.getBackButton()).to.not.be.ok;
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('2/7');
expect(await wizard.getInputBoxTitle()).to.have.string('2/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('3/7');
expect(await wizard.getInputBoxTitle()).to.have.string('3/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('4/7');
expect(await wizard.getInputBoxTitle()).to.have.string('4/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('5/7');
expect(await wizard.getInputBoxTitle()).to.have.string('5/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('6/7');
expect(await wizard.getInputBoxTitle()).to.have.string('6/8');
await wizard.next();

expect(await wizard.getInputBoxTitle()).to.have.string('7/7');
expect(await wizard.getInputBoxTitle()).to.have.string('7/8');
await wizard.selectQuickPick(0);
await wizard.confirm();

expect(await wizard.getInputBoxTitle()).to.have.string('8/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('7/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('6/7');
expect(await wizard.getInputBoxTitle()).to.have.string('6/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('5/7');
expect(await wizard.getInputBoxTitle()).to.have.string('5/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('4/7');
expect(await wizard.getInputBoxTitle()).to.have.string('4/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('3/7');
expect(await wizard.getInputBoxTitle()).to.have.string('3/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('2/7');
expect(await wizard.getInputBoxTitle()).to.have.string('2/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('1/7');
expect(await wizard.getInputBoxTitle()).to.have.string('1/8');
expect(await wizard.getBackButton()).to.not.be.ok;
await wizard.next();
await wizard.sendKeys(Key.DOWN);
await wizard.selectQuickPick(0);
await wizard.confirm();

expect(await wizard.getInputBoxTitle()).to.have.string('2/7');
expect(await wizard.getInputBoxTitle()).to.have.string('2/8');
await wizard.prev();

expect(await wizard.getInputBoxTitle()).to.have.string('1/7');
expect(await wizard.getInputBoxTitle()).to.have.string('1/8');
expect(await wizard.getBackButton()).to.not.be.ok;

await wizard.cancel();
Expand Down
75 changes: 75 additions & 0 deletions src/utils/codeQuarkusApiUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { IncomingMessage } from "http";
import * as https from "https";
import * as yaml from "js-yaml";
import * as path from "path";
import { QuarkusConfig } from "../QuarkusConfig";
import { read } from "fs-extra";
import { URL } from "url";

const HTTP_MATCHER = new RegExp('^http://');

export namespace CodeQuarkusApiUtils {

export interface Functionality {
canExcludeSampleCode: boolean;
}

/**
* Returns the capabilities of the Code Quarkus API instance that is defined in the user settings
*
* @returns the capabilities of the Code Quarkus API instance that is defined in the user settings
* @throws if something goes wrong when getting the functionality from OpenAPI
*/
export async function getCodeQuarkusApiFunctionality(): Promise<Functionality> {
let oldOpenApiUrl: string = path.dirname(QuarkusConfig.getApiUrl()) + '/openapi';
let newOpenApiUrl: string = path.dirname(QuarkusConfig.getApiUrl()) + '/q/openapi';
oldOpenApiUrl = oldOpenApiUrl.replace(HTTP_MATCHER, "https://");
newOpenApiUrl = oldOpenApiUrl.replace(HTTP_MATCHER, "https://");
let openApiYaml: string;
try {
openApiYaml = await httpsGet(newOpenApiUrl);
} catch {
openApiYaml = await httpsGet(oldOpenApiUrl);
}
const openApiData: any = yaml.load(openApiYaml);

return {
canExcludeSampleCode: openApiData?.paths?.['/api/download']?.get?.parameters?.filter(p => p?.name === 'ne').length > 0
} as Functionality;
}

/**
* Returns a set of capabilities that are implemented by all Code Quarkus APIs
*
* @returns a set of capabilities that are implemented by all Code Quarkus APIs
*/
export function getDefaultFunctionality() {
return {
canExcludeSampleCode: false
} as Functionality;
}

/**
* Returns the GET response body if the code is 200 and rejects otherwise
*
* @param url URL to GET
* @returns the response body if the code is 200 and rejects otherwise
* @throws if anything goes wrong (not 200 response, any other errors during get)
*/
async function httpsGet(url: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
https.get(url, (res: IncomingMessage) => {
if (res.statusCode === 301 || res.statusCode === 302) {
httpsGet(new URL(url).origin + res.headers.location) //
.then(resolve, reject);
} else if (res.statusCode !== 200) {
reject(`${res.statusCode}: ${res.statusMessage}`);
}
res.on('data', (d: Buffer) => {
resolve(d.toString('utf8'));
});
})
.on('error', reject);
});
}
}
1 change: 1 addition & 0 deletions src/utils/requestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export async function downloadProject(state: ProjectGenState): Promise<ZipFile>
`a=${state.artifactId}&` +
`v=${state.projectVersion}&` +
`c=${state.packageName}.${state.resourceName}&` +
`${(state.isGenerateSampleCode === undefined ? '' : `ne=${!state.isGenerateSampleCode}&`)}` +
`e=${chosenIds.join('&e=')}`;

const buffer: Buffer = await tryGetProjectBuffer(qProjectUrl);
Expand Down
31 changes: 22 additions & 9 deletions src/utils/tasksUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
import * as _ from 'lodash';

import { tasks, ProcessExecution, ShellExecution, Task, TaskExecution, WorkspaceFolder } from 'vscode';
import { tasks, ProcessExecution, ShellExecution, Task, TaskExecution, WorkspaceFolder, CustomExecution } from 'vscode';
import { BuildSupport } from '../buildSupport/BuildSupport';

export async function getQuarkusDevTaskNames(workspaceFolder: WorkspaceFolder, projectBuildSupport: BuildSupport) {
Expand Down Expand Up @@ -51,11 +51,18 @@ export async function getRunningQuarkusDevTasks(workspaceFolder: WorkspaceFolder
export function getTaskExecutionWorkingDir(task: Task): string|undefined {
if (!task.execution) return undefined;

if (!task.execution.options || !task.execution.options.cwd) {
const execution: ProcessExecution | ShellExecution | CustomExecution = task.execution;
if (!execution || !(execution as ProcessExecution | ShellExecution)) {
return undefined;
}

const executionWithOptions = execution as ProcessExecution | ShellExecution;

if (!executionWithOptions.options || !executionWithOptions.options.cwd) {
return './';
}

return task.execution.options.cwd;
return executionWithOptions.options.cwd;
}

async function getTasksFromWorkspace(workspaceFolder: WorkspaceFolder): Promise<Task[]> {
Expand All @@ -67,12 +74,18 @@ async function getTasksFromWorkspace(workspaceFolder: WorkspaceFolder): Promise<

function isQuarkusDevTask(task: Task, projectBuildSupport: BuildSupport): boolean {

const execution: ProcessExecution | ShellExecution = task.execution;
return execution &&
'commandLine' in execution &&
(execution.commandLine.includes(projectBuildSupport.getDefaultExecutable()) ||
execution.commandLine.includes(projectBuildSupport.getWrapper()) ||
execution.commandLine.includes(projectBuildSupport.getWrapperWindows()));
const execution: ProcessExecution | ShellExecution | CustomExecution = task.execution;
if (!execution || !(execution as ShellExecution)) {
return false;
}

const shellExecution: ShellExecution = execution as ShellExecution;

return shellExecution
&& shellExecution.commandLine
&& (shellExecution.commandLine.includes(projectBuildSupport.getDefaultExecutable())
|| shellExecution.commandLine.includes(projectBuildSupport.getWrapper())
|| shellExecution.commandLine.includes(projectBuildSupport.getWrapperWindows()));
}

function isTaskFromWorkspace(workspaceFolder: WorkspaceFolder, task: Task): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/wizards/generateProject/ExtensionsPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export class ExtensionsPicker {
const numSelected: number = this.selectedExtensions.length;
items.push({
type: Type.Stop,
label: `$(tasklist) ${numSelected} extension${numSelected > 1 ? 's' : ''} selected`,
label: `$(tasklist) ${numSelected} extension${numSelected !== 1 ? 's' : ''} selected`,
description: '',
detail: 'Press <Enter> to continue'
});
Expand Down
33 changes: 30 additions & 3 deletions src/wizards/generateProject/generationWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { BuildToolName, INPUT_TITLE } from '../../definitions/constants';
import { ProjectGenState } from '../../definitions/inputState';
import { QExtension } from '../../definitions/QExtension';
import { QuarkusContext } from '../../QuarkusContext';
import { MultiStepInput } from '../../utils/multiStepUtils';
import { MultiStepInput, QuickPickParameters } from '../../utils/multiStepUtils';
import { downloadProject } from '../../utils/requestUtils';
import { ExtensionsPicker } from './ExtensionsPicker';
import { validateArtifactId, validateGroupId, validatePackageName, validateResourceName, validateVersion } from './validateInput';
import { CodeQuarkusApiUtils } from '../../utils/codeQuarkusApiUtils';

/**
* A multi-step input using window.createQuickPick() and window.createInputBox().
Expand All @@ -24,8 +25,15 @@ import { validateArtifactId, validateGroupId, validatePackageName, validateResou
*/
export async function generateProjectWizard() {

let apiCapabilities: CodeQuarkusApiUtils.Functionality;
try {
apiCapabilities = await CodeQuarkusApiUtils.getCodeQuarkusApiFunctionality();
} catch (e) {
apiCapabilities = CodeQuarkusApiUtils.getDefaultFunctionality();
}

const state: Partial<ProjectGenState> = {
totalSteps: 7
totalSteps: 7 + (apiCapabilities.canExcludeSampleCode ? 1 : 0)
};

async function collectInputs(state: Partial<ProjectGenState>) {
Expand Down Expand Up @@ -139,7 +147,26 @@ export async function generateProjectWizard() {
prompt: 'Your resource name',
validate: validateResourceName
});
return (input: MultiStepInput) => ExtensionsPicker.createExtensionsPicker(input, state, { showLastUsed: true, showRequiredExtensions: true, allowZeroExtensions: true });
return (input: MultiStepInput) => ExtensionsPicker.createExtensionsPicker(
input, state, { showLastUsed: true, showRequiredExtensions: true, allowZeroExtensions: true },
(apiCapabilities.canExcludeSampleCode ? inputGenerateSampleCode: undefined));
}

async function inputGenerateSampleCode(input: MultiStepInput, state: Partial<ProjectGenState>) {
const YES: string = 'Include sample code';
const NO: string = 'Do not include sample code';
const quickPickItems: QuickPickItem[] = [
{label: YES, picked: true},
{label: NO}
];

state.isGenerateSampleCode = (await input.showQuickPick<QuickPickItem, QuickPickParameters<QuickPickItem>>({
title: INPUT_TITLE,
placeholder: 'Should sample code be included? Additional dependencies may be added along with the sample.',
step: input.getStepNumber(),
totalSteps: state.totalSteps,
items: quickPickItems,
})).label === YES;
}

try {
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
},
"linterOptions": {
"exclude": [
"test-resources"
"test-resources",
"out/**/*"
]
}
}

0 comments on commit f6d0879

Please sign in to comment.