New Command for Bootstrapping a project for ISV debugging (#282)
@W-4543894@
This commit is contained in:
Родитель
74f153f69d
Коммит
3ae33f4cdd
|
@ -111,6 +111,7 @@ function registerCommands(): vscode.Disposable {
|
|||
session.customRequest(EXCEPTION_BREAKPOINT_REQUEST, args);
|
||||
});
|
||||
});
|
||||
|
||||
return vscode.Disposable.from(
|
||||
customEventHandler,
|
||||
exceptionBreakpointCmd,
|
||||
|
|
|
@ -61,7 +61,8 @@
|
|||
},
|
||||
"activationEvents": [
|
||||
"workspaceContains:sfdx-project.json",
|
||||
"onCommand:sfdx.force.project.create"
|
||||
"onCommand:sfdx.force.project.create",
|
||||
"onCommand:sfdx.debug.isv.bootstrap"
|
||||
],
|
||||
"main": "./out/src",
|
||||
"contributes": {
|
||||
|
@ -253,6 +254,10 @@
|
|||
{
|
||||
"command": "sfdx.force.stop.apex.debug.logging",
|
||||
"when": "sfdx:project_opened && sfdx:replay_debugger_extension"
|
||||
},
|
||||
{
|
||||
"command": "sfdx.debug.isv.bootstrap",
|
||||
"when": "config.salesforcedx-vscode-core.isv-debugger.enabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -388,6 +393,10 @@
|
|||
{
|
||||
"command": "sfdx.force.stop.apex.debug.logging",
|
||||
"title": "%force_stop_apex_debug_logging%"
|
||||
},
|
||||
{
|
||||
"command": "sfdx.debug.isv.bootstrap",
|
||||
"title": "%isv_bootstrap_command_text%"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
|
@ -398,6 +407,11 @@
|
|||
"type": ["boolean"],
|
||||
"default": true,
|
||||
"description": "%show_cli_success_msg_description%"
|
||||
},
|
||||
"salesforcedx-vscode-core.isv-debugger.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%isv_debugger_enabled_description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,5 +50,10 @@
|
|||
"force_start_apex_debug_logging":
|
||||
"SFDX: Turn on Apex Debug Log for Replay Debugger",
|
||||
"force_stop_apex_debug_logging":
|
||||
"SFDX: Turn off Apex Debug Log for Replay Debugger"
|
||||
"SFDX: Turn off Apex Debug Log for Replay Debugger",
|
||||
|
||||
"isv_bootstrap_command_text":
|
||||
"SFDX: Create and Set Up Project for ISV Debugging",
|
||||
"isv_debugger_enabled_description":
|
||||
"Set to true to enable a feature preview of the ISV Debugger."
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2017, salesforce.com, inc.
|
||||
* All rights reserved.
|
||||
* Licensed under the BSD 3-Clause license.
|
||||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
import {
|
||||
CliCommandExecutor,
|
||||
Command,
|
||||
SfdxCommandBuilder
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/cli';
|
||||
import {
|
||||
CancelResponse,
|
||||
ContinueResponse,
|
||||
ParametersGatherer
|
||||
} from '@salesforce/salesforcedx-utils-vscode/out/src/types';
|
||||
import * as path from 'path';
|
||||
import * as querystring from 'querystring';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import * as vscode from 'vscode';
|
||||
import { Uri } from 'vscode';
|
||||
import { CommandExecution } from '../../../../salesforcedx-utils-vscode/out/src/cli/commandExecutor';
|
||||
import { channelService } from '../../channels';
|
||||
import { nls } from '../../messages';
|
||||
import { notificationService } from '../../notifications';
|
||||
import { CancellableStatusBar, taskViewService } from '../../statuses';
|
||||
import {
|
||||
CompositeParametersGatherer,
|
||||
EmptyPreChecker,
|
||||
SfdxCommandlet,
|
||||
SfdxCommandletExecutor
|
||||
} from '../commands';
|
||||
import {
|
||||
PathExistsChecker,
|
||||
ProjectNameAndPath,
|
||||
SelectProjectFolder,
|
||||
SelectProjectName
|
||||
} from '../forceProjectCreate';
|
||||
|
||||
export class IsvDebugBootstrapExecutor extends SfdxCommandletExecutor<{}> {
|
||||
public build(data: {}): Command {
|
||||
throw new Error('not in use');
|
||||
}
|
||||
|
||||
public buildCreateProjectCommand(data: IsvDebugBootstrapConfig): Command {
|
||||
return new SfdxCommandBuilder()
|
||||
.withDescription(nls.localize('isv_debug_bootstrap_step1_create_project'))
|
||||
.withArg('force:project:create')
|
||||
.withFlag('--projectname', data.projectName)
|
||||
.withFlag('--outputdir', data.projectUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
public buildConfigureProjectCommand(data: IsvDebugBootstrapConfig): Command {
|
||||
return new SfdxCommandBuilder()
|
||||
.withDescription(
|
||||
nls.localize('isv_debug_bootstrap_step1_configure_project')
|
||||
)
|
||||
.withArg('force:config:set')
|
||||
.withArg(`isvDebuggerSid=${data.sessionId}`)
|
||||
.withArg(`isvDebuggerUrl=${data.loginUrl}`)
|
||||
.withArg(`instanceUrl=${data.loginUrl}`)
|
||||
.build();
|
||||
}
|
||||
|
||||
public execute(response: ContinueResponse<IsvDebugBootstrapConfig>): void {
|
||||
const cancellationTokenSource = new vscode.CancellationTokenSource();
|
||||
const cancellationToken = cancellationTokenSource.token;
|
||||
|
||||
const projectParentPath = response.data.projectUri;
|
||||
const projectPath = path.join(projectParentPath, response.data.projectName);
|
||||
|
||||
const createProjectExecution = new CliCommandExecutor(
|
||||
this.buildCreateProjectCommand(response.data),
|
||||
{
|
||||
cwd: projectParentPath
|
||||
}
|
||||
).execute(cancellationToken);
|
||||
|
||||
createProjectExecution.processExitSubject.subscribe(async data => {
|
||||
if (data != undefined && data.toString() === '0') {
|
||||
const configureProjectExecution = new CliCommandExecutor(
|
||||
this.buildConfigureProjectCommand(response.data),
|
||||
{
|
||||
cwd: projectPath
|
||||
}
|
||||
).execute(cancellationToken);
|
||||
|
||||
configureProjectExecution.processExitSubject.subscribe(async data2 => {
|
||||
if (data2 != undefined && data2.toString() === '0') {
|
||||
// last step is open the folder
|
||||
await vscode.commands.executeCommand(
|
||||
'vscode.openFolder',
|
||||
vscode.Uri.parse(projectPath)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.attachExecution(
|
||||
configureProjectExecution,
|
||||
cancellationTokenSource,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.attachExecution(
|
||||
createProjectExecution,
|
||||
cancellationTokenSource,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
protected attachExecution(
|
||||
execution: CommandExecution,
|
||||
cancellationTokenSource: vscode.CancellationTokenSource,
|
||||
cancellationToken: vscode.CancellationToken
|
||||
) {
|
||||
channelService.streamCommandOutput(execution);
|
||||
channelService.showChannelOutput();
|
||||
notificationService.reportExecutionError(
|
||||
execution.command.toString(),
|
||||
(execution.stderrSubject as any) as Observable<Error | undefined>
|
||||
);
|
||||
CancellableStatusBar.show(execution, cancellationTokenSource);
|
||||
taskViewService.addCommandExecution(execution, cancellationTokenSource);
|
||||
}
|
||||
}
|
||||
|
||||
export type IsvDebugBootstrapConfig = ProjectNameAndPath & ForceIdeUri;
|
||||
|
||||
export interface ForceIdeUri {
|
||||
loginUrl: string;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export class EnterForceIdeUri implements ParametersGatherer<ForceIdeUri> {
|
||||
public async gather(): Promise<
|
||||
CancelResponse | ContinueResponse<ForceIdeUri>
|
||||
> {
|
||||
const forceIdeUri = await vscode.window.showInputBox({
|
||||
prompt: nls.localize('parameter_gatherer_paste_forceide_url')
|
||||
});
|
||||
|
||||
if (forceIdeUri) {
|
||||
const url = Uri.parse(forceIdeUri);
|
||||
const parameter = querystring.parse(url.query);
|
||||
if (parameter.url && parameter.sessionId) {
|
||||
return {
|
||||
type: 'CONTINUE',
|
||||
data: {
|
||||
loginUrl: parameter.url,
|
||||
sessionId: parameter.sessionId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
vscode.window.showErrorMessage(
|
||||
nls.localize('parameter_gatherer_invalid_forceide_url')
|
||||
);
|
||||
}
|
||||
|
||||
return { type: 'CANCEL' };
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceChecker = new EmptyPreChecker();
|
||||
const parameterGatherer = new CompositeParametersGatherer(
|
||||
new EnterForceIdeUri(),
|
||||
new SelectProjectName(),
|
||||
new SelectProjectFolder()
|
||||
);
|
||||
const pathExistsChecker = new PathExistsChecker();
|
||||
|
||||
const executor = new IsvDebugBootstrapExecutor();
|
||||
const commandlet = new SfdxCommandlet(
|
||||
workspaceChecker,
|
||||
parameterGatherer,
|
||||
executor,
|
||||
pathExistsChecker
|
||||
);
|
||||
|
||||
export function isvDebugBootstrap() {
|
||||
commandlet.run();
|
||||
}
|
|
@ -43,6 +43,7 @@ import {
|
|||
SfdxWorkspaceChecker
|
||||
} from './commands';
|
||||
import { restoreDebugLevels } from './commands/forceStopApexDebugLogging';
|
||||
import { isvDebugBootstrap } from './commands/isvdebugging/bootstrapCmd';
|
||||
import {
|
||||
CLIENT_ID,
|
||||
SFDX_CLIENT_ENV_VAR,
|
||||
|
@ -203,6 +204,11 @@ function registerCommands(): vscode.Disposable {
|
|||
forceStopApexDebugLogging
|
||||
);
|
||||
|
||||
const isvDebugBootstrapCmd = vscode.commands.registerCommand(
|
||||
'sfdx.debug.isv.bootstrap',
|
||||
isvDebugBootstrap
|
||||
);
|
||||
|
||||
// Internal commands
|
||||
const internalCancelCommandExecution = vscode.commands.registerCommand(
|
||||
CANCEL_EXECUTION_COMMAND,
|
||||
|
@ -243,6 +249,7 @@ function registerCommands(): vscode.Disposable {
|
|||
forceApexTriggerCreateCmd,
|
||||
forceStartApexDebugLoggingCmd,
|
||||
forceStopApexDebugLoggingCmd,
|
||||
isvDebugBootstrapCmd,
|
||||
internalCancelCommandExecution
|
||||
);
|
||||
}
|
||||
|
@ -287,6 +294,15 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
sfdxProjectOpened
|
||||
);
|
||||
|
||||
const sfdxApexDebuggerExtension = vscode.extensions.getExtension(
|
||||
'salesforce.salesforcedx-vscode-apex-debugger'
|
||||
);
|
||||
vscode.commands.executeCommand(
|
||||
'setContext',
|
||||
'sfdx:apex_debug_extension_installed',
|
||||
sfdxApexDebuggerExtension && sfdxApexDebuggerExtension.id
|
||||
);
|
||||
|
||||
// Commands
|
||||
const commands = registerCommands();
|
||||
context.subscriptions.push(commands);
|
||||
|
|
|
@ -49,6 +49,9 @@ export const messages = {
|
|||
parameter_gatherer_enter_alias_name:
|
||||
'Enter a scratch org alias or use default alias',
|
||||
parameter_gatherer_enter_project_name: 'Enter project name',
|
||||
parameter_gatherer_paste_forceide_url: 'Paste forceide:// URL from Setup',
|
||||
parameter_gatherer_invalid_forceide_url:
|
||||
"The forceide:// URL is invalid. From your subscriber's org, copy and paste the forceide:// URL shown on the Apex Debugger page in Setup.",
|
||||
|
||||
force_org_create_default_scratch_org_text:
|
||||
'SFDX: Create a Default Scratch Org...',
|
||||
|
@ -116,5 +119,10 @@ export const messages = {
|
|||
'SFDX: Turn on Apex Debug Log for Replay Debugger',
|
||||
force_apex_debug_log_status_bar_text: 'Logging ends at %s',
|
||||
force_stop_apex_debug_logging:
|
||||
'SFDX: Turn off Apex Debug Log for Replay Debugger'
|
||||
'SFDX: Turn off Apex Debug Log for Replay Debugger',
|
||||
|
||||
isv_debug_bootstrap_step1_create_project:
|
||||
'SFDX: ISV Debugger Setup, Step 1 of 2: Create Project',
|
||||
isv_debug_bootstrap_step1_configure_project:
|
||||
'SFDX: ISV Debugger Setup, Step 2 of 2: Configure Project'
|
||||
};
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import { expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
EnterForceIdeUri,
|
||||
IsvDebugBootstrapExecutor
|
||||
} from '../../../src/commands/isvdebugging/bootstrapCmd';
|
||||
import { nls } from '../../../src/messages';
|
||||
|
||||
// tslint:disable:no-unused-expression
|
||||
describe('ISV Debugging Project Bootstrap Command', () => {
|
||||
const LOGIN_URL = 'a.b.c';
|
||||
const SESSION_ID = '0x123';
|
||||
const PROJECT_NAME = 'sfdx-simple';
|
||||
const WORKSPACE_PATH = path.join(vscode.workspace.rootPath!, '..');
|
||||
const PROJECT_DIR: vscode.Uri[] = [vscode.Uri.parse(WORKSPACE_PATH)];
|
||||
|
||||
describe('EnterForceIdeUri Gatherer', () => {
|
||||
let inputBoxSpy: sinon.SinonStub;
|
||||
let showErrorMessageSpy: sinon.SinonStub;
|
||||
|
||||
before(() => {
|
||||
inputBoxSpy = sinon.stub(vscode.window, 'showInputBox');
|
||||
inputBoxSpy.onCall(0).returns(undefined);
|
||||
inputBoxSpy.onCall(1).returns('');
|
||||
inputBoxSpy
|
||||
.onCall(2)
|
||||
.returns(`forceide://abc?url=${LOGIN_URL}&sessionId=${SESSION_ID}`);
|
||||
inputBoxSpy.onCall(3).returns(`forceide://abc?url=${LOGIN_URL}`);
|
||||
inputBoxSpy.onCall(4).returns(`forceide://abc?sessionId=${SESSION_ID}`);
|
||||
showErrorMessageSpy = sinon.stub(vscode.window, 'showErrorMessage');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
inputBoxSpy.restore();
|
||||
showErrorMessageSpy.restore();
|
||||
});
|
||||
|
||||
it('Should return cancel if forceide url is undefined', async () => {
|
||||
const gatherer = new EnterForceIdeUri();
|
||||
const response = await gatherer.gather();
|
||||
expect(inputBoxSpy.calledOnce).to.be.true;
|
||||
expect(response.type).to.equal('CANCEL');
|
||||
expect(showErrorMessageSpy.notCalled).to.be.true;
|
||||
});
|
||||
|
||||
it('Should return cancel if user input is empty string', async () => {
|
||||
const gatherer = new EnterForceIdeUri();
|
||||
const response = await gatherer.gather();
|
||||
expect(inputBoxSpy.calledTwice).to.be.true;
|
||||
expect(response.type).to.equal('CANCEL');
|
||||
expect(showErrorMessageSpy.notCalled).to.be.true;
|
||||
});
|
||||
|
||||
it('Should return Continue with inputted url if not undefined or empty', async () => {
|
||||
const gatherer = new EnterForceIdeUri();
|
||||
const response = await gatherer.gather();
|
||||
expect(inputBoxSpy.calledThrice).to.be.true;
|
||||
if (response.type === 'CONTINUE') {
|
||||
expect(response.data.loginUrl).to.equal(LOGIN_URL);
|
||||
expect(response.data.sessionId).to.equal(SESSION_ID);
|
||||
} else {
|
||||
expect.fail('Response should be of type ContinueResponse');
|
||||
}
|
||||
});
|
||||
|
||||
it('Should return cancel and show error if forceide url is missing sessionId', async () => {
|
||||
expect(showErrorMessageSpy.calledOnce).to.be.false;
|
||||
const gatherer = new EnterForceIdeUri();
|
||||
const response = await gatherer.gather();
|
||||
expect(inputBoxSpy.callCount).equal(4);
|
||||
expect(response.type).to.equal('CANCEL');
|
||||
expect(showErrorMessageSpy.calledOnce).to.be.true;
|
||||
});
|
||||
it('Should return cancel and show error if forceide url is missing login address', async () => {
|
||||
expect(showErrorMessageSpy.calledTwice).to.be.false;
|
||||
const gatherer = new EnterForceIdeUri();
|
||||
const response = await gatherer.gather();
|
||||
expect(inputBoxSpy.callCount).equal(5);
|
||||
expect(response.type).to.equal('CANCEL');
|
||||
expect(showErrorMessageSpy.calledTwice).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('CLI Builder', () => {
|
||||
it('Should build the project create command', async () => {
|
||||
const forceProjectCreateBuilder = new IsvDebugBootstrapExecutor();
|
||||
const createCommand = forceProjectCreateBuilder.buildCreateProjectCommand(
|
||||
{
|
||||
loginUrl: LOGIN_URL,
|
||||
sessionId: SESSION_ID,
|
||||
projectName: PROJECT_NAME,
|
||||
projectUri: PROJECT_DIR[0].fsPath
|
||||
}
|
||||
);
|
||||
expect(createCommand.toCommand()).to.equal(
|
||||
`sfdx force:project:create --projectname ${PROJECT_NAME} --outputdir ${PROJECT_DIR[0]
|
||||
.fsPath}`
|
||||
);
|
||||
expect(createCommand.description).to.equal(
|
||||
nls.localize('isv_debug_bootstrap_step1_create_project')
|
||||
);
|
||||
});
|
||||
|
||||
it('Should build the project configure command', async () => {
|
||||
const forceProjectConfigBuilder = new IsvDebugBootstrapExecutor();
|
||||
const configureCommand = forceProjectConfigBuilder.buildConfigureProjectCommand(
|
||||
{
|
||||
loginUrl: LOGIN_URL,
|
||||
sessionId: SESSION_ID,
|
||||
projectName: PROJECT_NAME,
|
||||
projectUri: PROJECT_DIR[0].fsPath
|
||||
}
|
||||
);
|
||||
expect(configureCommand.toCommand()).to.equal(
|
||||
`sfdx force:config:set isvDebuggerSid=${SESSION_ID} isvDebuggerUrl=${LOGIN_URL} instanceUrl=${LOGIN_URL}`
|
||||
);
|
||||
expect(configureCommand.description).to.equal(
|
||||
nls.localize('isv_debug_bootstrap_step1_configure_project')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -280,6 +280,6 @@ describe('Empty VSCode workspace', () => {
|
|||
await common.type('>SFDX:');
|
||||
await app.wait();
|
||||
const quickOpenText = await common.getQuickOpenElementsText();
|
||||
expect(quickOpenText).to.equal('SFDX: Create Project');
|
||||
expect(quickOpenText).to.contain('SFDX: Create Project');
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче