Skip to content
This repository has been archived by the owner on Mar 11, 2024. It is now read-only.

Feature/command provider poc #204

Draft
wants to merge 24 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
001c69d
feat: command provider poc
michaeljohnbennett Jul 20, 2022
0ed7d89
fix: adding in tests and typings to make chai/mocha work
michaeljohnbennett Jul 26, 2022
dabb72a
Merge remote-tracking branch 'origin/develop' into feature/command-pr…
michaeljohnbennett Aug 1, 2022
af4eaaa
fix: adjusting provider
michaeljohnbennett Aug 1, 2022
e8c58b6
fix: adjusting provider
michaeljohnbennett Aug 5, 2022
8126c63
Merge remote-tracking branch 'origin/develop' into feature/command-pr…
michaeljohnbennett Aug 11, 2022
2e804e2
fix: rework again after merging
michaeljohnbennett Aug 11, 2022
27f20a7
fix: added logging wrapper
michaeljohnbennett Aug 15, 2022
b13ae39
Merge remote-tracking branch 'origin/develop' into feature/command-pr…
michaeljohnbennett Aug 15, 2022
b8904b6
fix: added in compiler
michaeljohnbennett Aug 15, 2022
18b9882
fix: cleanup comments
michaeljohnbennett Aug 24, 2022
5d84c97
fix: merging in changes from develop
michaeljohnbennett Nov 1, 2022
ecd92be
fix: logging fixed
michaeljohnbennett Nov 1, 2022
e002cb6
fix: project based compilation working
michaeljohnbennett Nov 1, 2022
b8f3da9
fix: cleaning up code
michaeljohnbennett Nov 7, 2022
9ae11c7
fix: lots of refactoring
michaeljohnbennett Nov 9, 2022
2c0f847
fix: lots of refactoring
michaeljohnbennett Nov 14, 2022
3c01bfe
fix: tests
michaeljohnbennett Nov 16, 2022
90a7f12
Merge branch 'develop' into feature/command-provider-poc
michaeljohnbennett Nov 16, 2022
7bfb7fe
chore: removing copyright cleanup
michaeljohnbennett Nov 17, 2022
75e4935
chore: post merge refactor/cleanup
michaeljohnbennett Nov 18, 2022
19a3be6
fix: broken integration tests
michaeljohnbennett Nov 21, 2022
5ed78f4
fix: windows paths on CICD
michaeljohnbennett Nov 21, 2022
3addbe8
Merge branch 'develop' into feature/command-provider-poc
michaeljohnbennett Nov 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: lots of refactoring
moving the code to be more workspace aware.

adding code to handle workspaces that have no/unknown frameworks
installed so they fail gracefully.

TODO: somehow add in a notification on UnknownExtensionAdapter.ts to
warn user their commands are benign.
  • Loading branch information
michaeljohnbennett committed Nov 9, 2022
commit 9ae11c7efc64048491bd2a610572034d3b593042
6 changes: 3 additions & 3 deletions src/commands/HardhatCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {Constants, NotificationOptions, OptionalApps} from '@/Constants';
import {outputCommandHelper} from '@/helpers';
import {required} from '@/helpers/required';
import {showIgnorableNotification, showNotification} from '@/helpers/userInteraction';
import {AbstractWorkspaceManager} from '@/helpers/workspace';
import {AbstractWorkspace} from '@/helpers/AbstractWorkspace';
import {Output, OutputLabel} from '@/Output';
import {Telemetry} from '@/TelemetryClient';
import {commands, Uri} from 'vscode';

export async function buildContracts(ws?: AbstractWorkspaceManager.AbstractWorkspace, uri?: Uri): Promise<void> {
export async function buildContracts(ws: AbstractWorkspace, uri?: Uri): Promise<void> {
Telemetry.sendEvent('HardhatCommands.buildContracts.commandStarted');
if (!(await required.checkAppsSilent(OptionalApps.hardhat))) {
Telemetry.sendEvent('HardhatCommands.buildContracts.hardhatInstallationMissing');
Expand All @@ -23,7 +23,7 @@ export async function buildContracts(ws?: AbstractWorkspaceManager.AbstractWorks
return;
}

const workspaceDir = ws!.workspace.fsPath;
const workspaceDir = ws.workspace.fsPath;

Output.outputLine(OutputLabel.hardhatCommands, `compiling: ${JSON.stringify(uri)} : ${workspaceDir}`);
const args: string[] = [OptionalApps.hardhat, 'compile'];
Expand Down
49 changes: 22 additions & 27 deletions src/commands/SdkCoreCommands.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
// Copyright (c) 2022. Consensys Software Inc. All rights reserved.
// Licensed under the MIT license.

import {Constants} from '@/Constants';
import {AbstractWorkspaceManager} from '@/helpers/workspace';
import {getWorkspaceForUri, WorkspaceType} from '@/helpers/AbstractWorkspace';
import {Output, OutputLabel} from '@/Output';

import {HardHatExtensionAdapter, IExtensionAdapter, TruffleExtensionAdapter} from '@/services/extensionAdapter';
import {Memento, Uri, window} from 'vscode';
import {userSettings} from '../helpers';
import {
HardHatExtensionAdapter,
IExtensionAdapter,
TruffleExtensionAdapter,
UnknownExtensionAdapter,
} from '@/services/extensionAdapter';
import {Uri, window} from 'vscode';

class SdkCoreCommands {
public extensionAdapter!: IExtensionAdapter;

private extensionAdapters = new Map<string, IExtensionAdapter>();

public async initialize(_globalState: Memento): Promise<void> {
const sdk = await this.getCoreSdk();
this.getExtensionAdapter(sdk.userValue ? sdk.userValue : sdk.defaultValue);
}

private getExtensionAdapter(sdkVal: string): IExtensionAdapter | undefined {
public getExtensionAdapter(sdkVal: WorkspaceType): IExtensionAdapter | undefined {
if (this.extensionAdapters.has(sdkVal)) {
return this.extensionAdapters.get(sdkVal);
}
// let's initialise it otherwise
const adapter = this.initExtensionAdapter(sdkVal);
console.log(`getExtensionAdapter: `, {adapter, sdkVal});
// console.log(`getExtensionAdapter: `, {adapter, sdkVal});
adapter.validateExtension().then(
(_) => {
Output.outputLine(
Expand All @@ -47,11 +43,11 @@ class SdkCoreCommands {
* @param contractUri if provided, it is the `Uri` of the smart contract to be compiled.
*/
public async build(contractUri?: Uri): Promise<void> {
const ws = await AbstractWorkspaceManager.getWorkspaceForUri(contractUri);
const ws = await getWorkspaceForUri(contractUri);
const buildUri = contractUri ? contractUri : ws.workspace;
console.log(`build: `, {contractUri, buildUri, type: ws.workspaceType, ret: ws});
return this.getExtensionAdapter(ws.workspaceType)!.build(ws, buildUri);
// return this.extensionAdapter.build(contractUri);
const adapter = await this.getExtensionAdapter(ws.workspaceType);
console.debug(`build:debug::`, {ws, buildUri, adapter});
return adapter!.build(ws, buildUri);
}

/**
Expand All @@ -60,21 +56,20 @@ class SdkCoreCommands {
* @param contractUri FIXME: Is this used?
*/
public async deploy(contractUri?: Uri): Promise<void> {
return this.extensionAdapter.deploy(contractUri);
const ws = await getWorkspaceForUri(contractUri);
const deployUri = contractUri ? contractUri : ws.workspace;
const adapter = await this.getExtensionAdapter(ws.workspaceType);
return adapter!.deploy(ws, deployUri);
}

private async getCoreSdk() {
return userSettings.getConfigurationAsync(Constants.userSettings.coreSdkSettingsKey);
}

private initExtensionAdapter(sdk: string): IExtensionAdapter {
private initExtensionAdapter(sdk: WorkspaceType): IExtensionAdapter {
switch (sdk) {
case Constants.coreSdk.hardhat:
case WorkspaceType.HARDHAT:
return new HardHatExtensionAdapter();
case Constants.coreSdk.truffle:
case WorkspaceType.TRUFFLE:
return new TruffleExtensionAdapter();
default:
return new TruffleExtensionAdapter();
return new UnknownExtensionAdapter();
}
}
}
Expand Down
25 changes: 12 additions & 13 deletions src/commands/TruffleCommands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2022. Consensys Software Inc. All rights reserved.
// Licensed under the MIT license.

import {AbstractWorkspace, getWorkspaceForUri} from '@/helpers/AbstractWorkspace';
import {INetwork} from '@/helpers/ConfigurationReader';
import {TruffleConfig} from '@/helpers/TruffleConfiguration';
import {mnemonicToSeed} from 'bip39';
Expand All @@ -10,7 +11,7 @@ import path from 'path';
import {QuickPickItem, Uri, window, commands, QuickPickItemKind} from 'vscode';
import {Constants, RequiredApps} from '@/Constants';
import {outputCommandHelper, telemetryHelper, vscodeEnvironment} from '../helpers';
import {getTruffleWorkspace} from '@/helpers/workspace';
// import {getTruffleWorkspace} from '@/helpers/workspace';
import {required} from '@/helpers/required';

import {showQuickPick, showConfirmPaidOperationDialog, showIgnorableNotification} from '@/helpers/userInteraction';
Expand Down Expand Up @@ -55,10 +56,11 @@ export namespace TruffleCommands {
/**
* Triggers the Truffle command line compiler using `npx`.
*
* @param ws the workspace we are building in
* @param contractUri if provided, compiles only `contractUri`.
* @returns
*/
export async function buildContracts(contractUri?: Uri): Promise<void> {
export async function buildContracts(ws: AbstractWorkspace, contractUri?: Uri): Promise<void> {
Telemetry.sendEvent('TruffleCommands.buildContracts.commandStarted');

if (!(await required.checkAppsSilent(RequiredApps.truffle))) {
Expand All @@ -67,10 +69,10 @@ export namespace TruffleCommands {
return;
}

const truffleWorkspace = await getTruffleWorkspace(contractUri);
const workspace = truffleWorkspace.workspace;
const workspace = ws.workspace;

const contractDirectory = getPathByPlatform(workspace);
const args: string[] = [RequiredApps.truffle, 'compile', '--config', truffleWorkspace.truffleConfigName];
const args: string[] = [RequiredApps.truffle, 'compile', '--config', ws.configName];

if (contractUri) {
if (fs.lstatSync(contractUri.fsPath).isFile()) args.push(path.basename(contractUri.fsPath));
Expand All @@ -91,19 +93,16 @@ export namespace TruffleCommands {
* Triggers the `migrate` option of the Truffle command line interface
* using `npx`.
*
* @param contractUri FIXME: Is this used?
* @param ws the workspace we are deploying from.
*/
export async function deployContracts(contractUri?: Uri) {
export async function deployContracts(ws: AbstractWorkspace) {
Telemetry.sendEvent('TruffleCommands.deployContracts.commandStarted');

const truffleWorkspace = await getTruffleWorkspace(contractUri);
const truffleConfigUri = getPathByPlatform(truffleWorkspace.truffleConfig);
const truffleConfigUri = getPathByPlatform(ws.configPath);

const deployDestinations = [];
deployDestinations.push(...getDefaultDeployDestinations(truffleConfigUri));
deployDestinations.push(
...(await getTruffleDeployDestinations(truffleConfigUri, truffleWorkspace.truffleConfigName))
);
deployDestinations.push(...(await getTruffleDeployDestinations(truffleConfigUri, ws.configName)));
deployDestinations.push(...(await getTreeDeployDestinations(truffleConfigUri)));

const uniqueDestinations = removeDuplicateNetworks(deployDestinations);
Expand Down Expand Up @@ -208,7 +207,7 @@ export namespace TruffleCommands {
let folderPath: string;

if (folderUri === undefined) {
const truffleWorkspace = await getTruffleWorkspace();
const truffleWorkspace = await getWorkspaceForUri(folderUri);
try {
folderPath = await ContractService.getContractsFolderPath(truffleWorkspace);
} catch (err) {
Expand Down
13 changes: 6 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export async function activate(context: ExtensionContext) {
MnemonicRepository.initialize(context.globalState);
TreeManager.initialize(context.globalState);
TreeService.initialize('truffle-vscode.truffle');
await sdkCoreCommands.initialize(context.globalState);

// Starts the status bar item for automatic deploy
const contractStatusBarItem = new StatusBarItems.Contract(context.globalState);
Expand Down Expand Up @@ -228,11 +227,12 @@ export async function activate(context: ExtensionContext) {
//#endregion

//#region workspace subscriptions
const changeCoreSdkConfigurationListener = workspace.onDidChangeConfiguration(async (event) => {
if (event.affectsConfiguration(Constants.userSettings.coreSdkSettingsKey)) {
await sdkCoreCommands.initialize(context.globalState);
}
});
// I think this isn't needed anymore.
// const changeCoreSdkConfigurationListener = workspace.onDidChangeConfiguration(async (event) => {
// if (event.affectsConfiguration(Constants.userSettings.coreSdkSettingsKey)) {
// await sdkCoreCommands.initialize(context.globalState);
// }
// });
const didSaveTextDocumentListener = workspace.onDidSaveTextDocument(async (event) => {
// Calls the action that listens for the save files event
await saveTextDocument(context.globalState, event);
Expand Down Expand Up @@ -270,7 +270,6 @@ export async function activate(context: ExtensionContext) {
signInToInfuraAccount,
signOutOfInfuraAccount,
showProjectsFromInfuraAccount,
changeCoreSdkConfigurationListener,
didSaveTextDocumentListener,
// new view - main views
fileExplorerView,
Expand Down
140 changes: 140 additions & 0 deletions src/helpers/AbstractWorkspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) 2022. Consensys Software Inc. All rights reserved.
// Licensed under the MIT license.

import {Constants} from '@/Constants';
import {showQuickPick} from '@/helpers/userInteraction';
import {Telemetry} from '@/TelemetryClient';
import glob from 'glob';
import * as path from 'path';
import {Uri, workspace} from 'vscode';

/**
* The [glob](https://github.com/isaacs/node-glob#glob-primer) pattern to match Truffle/Other config file names.
*/
export const TRUFFLE_CONFIG_GLOB = 'truffle-config{,.*}.js';
export const HARDHAT_CONFIG_GLOB = 'hardhat.config{,.*}.ts';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would also need to include Hardhat js config files, i.e., we can use the following glob hardhat.config{,.*}.{js,ts}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I'll make that change I just think the majority of HH projects are TS based. I don't think they even give you a choice when bootstrapping a project to use JS anymore?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of HardHat v2.12.2 they do

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change the glob. thanks! 👍🏻


class ResolverConfig {
constructor(public type: WorkspaceType, public glob: string) {}

async resolvePath(_uri: Uri): Promise<AbstractWorkspace | undefined> {
return undefined;
}
}

export enum WorkspaceType {
TRUFFLE = 'Truffle',
HARDHAT = 'Hardhat',
UNKNOWN = 'Unknown',
}

export const WorkspaceResolvers: Array<ResolverConfig> = [
new ResolverConfig(WorkspaceType.TRUFFLE, TRUFFLE_CONFIG_GLOB),
new ResolverConfig(WorkspaceType.HARDHAT, HARDHAT_CONFIG_GLOB),
];

export class AbstractWorkspace {
/**
* Creates a `Workspace` of varying Type.
*
* @param configPath the full path of the config file.
* @param workspaceType - the type of config we have found.
*/
constructor(configPath: string, public readonly workspaceType: WorkspaceType) {
this.configName = path.basename(configPath);
this.dirName = path.dirname(configPath).split(path.sep).pop()!.toString();
this.workspace = Uri.parse(path.dirname(configPath));
this.configPath = Uri.parse(configPath);
}

/**
* Represents the `basename`, _i.e._, the file name portion
*/
readonly configName: string;

/**
* The last directory name where this config file is located.
*/
readonly dirName: string;

/**
* The `Uri` path of the directory where this config file is located.
*/
readonly workspace: Uri;

/**
* The full `Uri` path where this config file is located.
*/
readonly configPath: Uri;
}

/**
* Using all the resolvers, resolve the projects/config files present in the workspaces.
*/
export function resolveAllWorkspaces(includeUnknown = true): AbstractWorkspace[] {
if (workspace.workspaceFolders === undefined) {
return [];
}
return workspace.workspaceFolders.flatMap((ws) => {
const foundWs = findWorkspaces(ws.uri.fsPath);
// patch in the unknown ones.
if (includeUnknown && foundWs?.length === 0) {
const configPath = path.join(ws.uri.fsPath, 'UNKNOWN');
foundWs.push(new AbstractWorkspace(configPath, WorkspaceType.UNKNOWN));
}
return foundWs;
});
}

export const findWorkspaces = (workspaceRootPath: string): AbstractWorkspace[] => {
return WorkspaceResolvers.flatMap((r) =>
glob
.sync(`${workspaceRootPath}/**/${r.glob}`, {
ignore: Constants.workspaceIgnoredFolders,
})
.map((f) => new AbstractWorkspace(f, r.type))
);
};

export async function getWorkspaceForUri(contractUri?: Uri): Promise<AbstractWorkspace> {
const workspaces = contractUri
? findWorkspaces(workspace.getWorkspaceFolder(contractUri)!.uri.fsPath)
: resolveAllWorkspaces();
// console.log(`getWorkspaceForUri: `, {workspaces});
if (workspaces.length === 0) {
const error = new Error(Constants.errorMessageStrings.VariableShouldBeDefined('Workspace root'));
Telemetry.sendException(error);
throw error;
}

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

return await selectConfigFromQuickPick(workspaces);
}

/**
* Shows the list of `workspaces` in a quick pick so the user can select
* the correct config file to use.
*
* @param workspaces list of workspace folders to display to the user.
* @returns the config file of the selected Workspace.
*/
export async function selectConfigFromQuickPick(workspaces: AbstractWorkspace[]): Promise<AbstractWorkspace> {
const folders = workspaces.map((element) => {
return {
label: element.dirName,
description: `Type: ${element.workspaceType} : ${element.configName}`,
detail: process.platform === 'win32' ? element.dirName : element.workspace.fsPath,
workspace: element,
};
});

const result = await showQuickPick(folders, {
ignoreFocusOut: true,
placeHolder: `Select a config file to use`,
});

return result.workspace;
}
5 changes: 3 additions & 2 deletions src/helpers/required.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Copyright (c) 2022. Consensys Software Inc. All rights reserved.
// Licensed under the MIT license.

import {getWorkspaceForUri} from '@/helpers/AbstractWorkspace';
import {getTruffleConfigUri, TruffleConfig} from '@/helpers/TruffleConfiguration';
import fs from 'fs-extra';
import path from 'path';
import semver from 'semver';
import {commands, ProgressLocation, window} from 'vscode';
import {Constants, RequiredApps, OptionalApps, AppTypes} from '@/Constants';
import {AbstractWorkspaceManager, getPathByPlatform, getWorkspaceRoot} from '@/helpers/workspace';
import {getPathByPlatform, getWorkspaceRoot} from '@/helpers/workspace';
import {Output, OutputLabel} from '@/Output';
import {Telemetry} from '@/TelemetryClient';
import {executeCommand, tryExecuteCommand} from './command';
Expand Down Expand Up @@ -191,7 +192,7 @@ export namespace required {
* @param packageName - the npm specific name
*/
const getNpmPackageVersion: (packageName: string) => VersionCallback = (packageName: string) => async () => {
const workspace = await AbstractWorkspaceManager.getWorkspaceForUri();
const workspace = await getWorkspaceForUri();
const platformPath = getPathByPlatform(workspace.workspace);
return await getVersionWithArgs(
platformPath,
Expand Down
Loading