diff --git a/package.json b/package.json index 1186ffaf..f1ff165e 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "onCommand:truffle-vscode.newSolidityProject", "onCommand:truffle-vscode.buildContracts", "onCommand:truffle-vscode.deployContracts", + "onCommand:truffle-vscode.execScript", "onCommand:truffle-vscode.connectProject", "onCommand:truffle-vscode.copyRPCEndpointAddress", "onCommand:truffle-vscode.createProject", @@ -230,6 +231,11 @@ "title": "Deploy Contracts", "category": "Truffle" }, + { + "command": "truffle-vscode.execScript", + "title": "Truffle: Execute Script", + "category": "Truffle" + }, { "command": "truffle-vscode.connectProject", "title": "Connect to network", @@ -439,6 +445,11 @@ "when": "resourceLangId == json", "command": "truffle-contract.copyABI", "group": "9_copyFromContractGroup" + }, + { + "when": "resourceLangId == javascript", + "command": "truffle-vscode.execScript", + "group": "10_execScriptGroup" } ] }, diff --git a/src/Constants.ts b/src/Constants.ts index 4f9b7a55..2398cd13 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -540,6 +540,7 @@ export class Constants { public static statusBarMessages = { buildingContracts: "Building contracts", + executingScript: "Executing script", checkingRequirementDependencies: "Checking requirement dependencies version", createBDMApplication: "Creating BDM app", createBlobs: "Creating blobs", diff --git a/src/commands/SdkCoreCommands.ts b/src/commands/SdkCoreCommands.ts index d2c2cb4d..64714a5d 100644 --- a/src/commands/SdkCoreCommands.ts +++ b/src/commands/SdkCoreCommands.ts @@ -1,7 +1,7 @@ // Copyright (c) Consensys Software Inc. All rights reserved. // Licensed under the MIT license. -import {Memento, window} from "vscode"; +import {Memento, window, Uri} from "vscode"; import {Constants} from "../Constants"; import { // getWorkspaceRoot, @@ -38,6 +38,10 @@ class SdkCoreCommands { return this.extensionAdapter.deploy(); } + public async execScript(uri: Uri): Promise { + return this.extensionAdapter.execScript(uri); + } + private async getCoreSdk() { return userSettings.getConfigurationAsync(Constants.userSettings.coreSdkSettingsKey); } diff --git a/src/commands/TruffleCommands.ts b/src/commands/TruffleCommands.ts index 5975d188..b5cd8e55 100644 --- a/src/commands/TruffleCommands.ts +++ b/src/commands/TruffleCommands.ts @@ -100,6 +100,20 @@ export namespace TruffleCommands { Telemetry.sendEvent("TruffleCommands.deployContracts.commandFinished"); } + export async function execScript(uri: Uri): Promise { + Telemetry.sendEvent("TruffleCommands.execScript.commandStarted"); + + await showIgnorableNotification(Constants.statusBarMessages.executingScript, async () => { + if (!(await required.checkAppsSilent(RequiredApps.truffle))) { + Telemetry.sendEvent("TruffleCommands.execScript.truffleInstallation"); + await required.installTruffle(required.Scope.locally); + } + await outputCommandHelper.executeCommand(getWorkspaceRoot(), "npx", RequiredApps.truffle, "exec", uri.fsPath); + }); + + Telemetry.sendEvent("TruffleCommands.execScript.commandFinished"); + } + export async function writeAbiToBuffer(uri: Uri): Promise { Telemetry.sendEvent("TruffleCommands.writeAbiToBuffer.commandStarted"); const contract = await readCompiledContract(uri); diff --git a/src/extension.ts b/src/extension.ts index c06fc816..704bd44a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -104,6 +104,9 @@ export async function activate(context: ExtensionContext) { const deployContracts = commands.registerCommand("truffle-vscode.deployContracts", async () => { await tryExecute(() => sdkCoreCommands.deploy()); }); + const execScript = commands.registerCommand("truffle-vscode.execScript", async (uri: Uri) => { + await tryExecute(() => sdkCoreCommands.execScript(uri)); + }); const copyByteCode = commands.registerCommand("truffle-contract.copyByteCode", async (uri: Uri) => { await tryExecute(() => TruffleCommands.writeBytecodeToBuffer(uri)); }); @@ -215,6 +218,7 @@ export async function activate(context: ExtensionContext) { newSolidityProject, buildContracts, deployContracts, + execScript, // createNewBDMApplication, createProject, connectProject, diff --git a/src/services/extensionAdapter/IExtensionAdapter.ts b/src/services/extensionAdapter/IExtensionAdapter.ts index 2e1cfbca..50ecca55 100644 --- a/src/services/extensionAdapter/IExtensionAdapter.ts +++ b/src/services/extensionAdapter/IExtensionAdapter.ts @@ -1,8 +1,11 @@ // Copyright (c) Consensys Software Inc. All rights reserved. // Licensed under the MIT license. +import {Uri} from "vscode"; export interface IExtensionAdapter { validateExtension: () => Promise; build: (...args: Array) => Promise; deploy: () => Promise; + + execScript: (uri: Uri) => Promise; } diff --git a/src/services/extensionAdapter/TruffleExtensionAdapter.ts b/src/services/extensionAdapter/TruffleExtensionAdapter.ts index e38ffb0e..14e709b9 100644 --- a/src/services/extensionAdapter/TruffleExtensionAdapter.ts +++ b/src/services/extensionAdapter/TruffleExtensionAdapter.ts @@ -1,6 +1,7 @@ // Copyright (c) Consensys Software Inc. All rights reserved. // Licensed under the MIT license. +import {Uri} from "vscode"; import {TruffleCommands} from "../../commands/TruffleCommands"; import {IExtensionAdapter} from "./IExtensionAdapter"; @@ -15,4 +16,8 @@ export class TruffleExtensionAdapter implements IExtensionAdapter { public async deploy() { return TruffleCommands.deployContracts(); } + + public async execScript(uri: Uri) { + return TruffleCommands.execScript(uri); + } } diff --git a/test/TestConstants.ts b/test/TestConstants.ts index 25e0d501..b6f8c8d4 100644 --- a/test/TestConstants.ts +++ b/test/TestConstants.ts @@ -33,4 +33,6 @@ export class TestConstants { }; public static truffleCommandTestDataFolder: string = "testData"; + + public static truffleExecScriptExample: string = "script.js"; } diff --git a/test/TruffleCommandsTests/execScript.test.ts b/test/TruffleCommandsTests/execScript.test.ts new file mode 100644 index 00000000..9c4db1b8 --- /dev/null +++ b/test/TruffleCommandsTests/execScript.test.ts @@ -0,0 +1,66 @@ +// Copyright (c) Consensys Software Inc. All rights reserved. +// Licensed under the MIT license. + +import assert from "assert"; +import {SinonMock, SinonExpectation, SinonStub, mock, stub, restore} from "sinon"; +import uuid from "uuid"; +import {CancellationToken, Progress, ProgressOptions, window, Uri} from "vscode"; +import {TruffleCommands} from "../../src/commands/TruffleCommands"; +import * as helpers from "../../src/helpers"; +import * as commands from "../../src/helpers/command"; +import {TestConstants} from "../TestConstants"; +import {join} from "path"; + +describe("ExecScript Command", () => { + describe("Integration test", async () => { + let requiredMock: SinonMock; + let getWorkspaceRootMock: any; + let checkAppsSilent: SinonExpectation; + let installTruffle: SinonExpectation; + let commandContextMock: SinonMock; + let executeCommandMock: SinonExpectation; + let withProgressStub: SinonStub<[ProgressOptions, (progress: Progress, token: CancellationToken) => any], any>; + + beforeEach(() => { + requiredMock = mock(helpers.required); + + getWorkspaceRootMock = stub(helpers, "getWorkspaceRoot"); + getWorkspaceRootMock.returns(uuid.v4()); + + checkAppsSilent = requiredMock.expects("checkAppsSilent"); + installTruffle = requiredMock.expects("installTruffle"); + + commandContextMock = mock(commands); + executeCommandMock = commandContextMock.expects("executeCommand"); + + withProgressStub = stub(window, "withProgress"); + withProgressStub.callsFake(async (...args: any[]) => { + return args[1](); + }); + }); + + afterEach(() => { + restore(); + }); + + it("should not throw exception when script executes successfully", async () => { + // Arrange + checkAppsSilent.returns(true); + executeCommandMock.returns(uuid.v4()); + + // Act + const scriptPath = join( + __dirname, + TestConstants.truffleCommandTestDataFolder, + TestConstants.truffleExecScriptExample + ); + await TruffleCommands.execScript(Uri.file(scriptPath)); + + // Assert + assert.strictEqual(checkAppsSilent.calledOnce, true, "checkAppsSilent should be called once"); + assert.strictEqual(getWorkspaceRootMock.calledOnce, true, "getWorkspaceRoot should be called once"); + assert.strictEqual(installTruffle.called, false, "installTruffle should not be called"); + assert.strictEqual(executeCommandMock.called, true, "executeCommand should be called"); + }); + }); +}); diff --git a/test/TruffleCommandsTests/testData/script.js b/test/TruffleCommandsTests/testData/script.js new file mode 100644 index 00000000..ecbeab6f --- /dev/null +++ b/test/TruffleCommandsTests/testData/script.js @@ -0,0 +1,12 @@ +const TestContract = artifacts.require("./TestContract"); + +const main = async (cb) => { + try { + const accounts = await web3.eth.getAccounts(); + } catch(err) { + console.log('Doh! ', err.message); + } + cb(); +} + +module.exports = main; diff --git a/test/TruffleExtensionAdapter.test.ts b/test/TruffleExtensionAdapter.test.ts index 084df58b..198f7bec 100644 --- a/test/TruffleExtensionAdapter.test.ts +++ b/test/TruffleExtensionAdapter.test.ts @@ -5,15 +5,18 @@ import assert from "assert"; import sinon from "sinon"; import {TruffleCommands} from "../src/commands/TruffleCommands"; import {TruffleExtensionAdapter} from "../src/services/extensionAdapter"; +import {Uri} from "vscode"; describe("TruffleExtensionAdapter", () => { let buildContractsMock: sinon.SinonStub; let deployContractsMock: sinon.SinonStub; + let execScriptMock: sinon.SinonStub; let truffleExtensionAdapter: TruffleExtensionAdapter; beforeEach(() => { buildContractsMock = sinon.stub(TruffleCommands, "buildContracts"); deployContractsMock = sinon.stub(TruffleCommands, "deployContracts"); + execScriptMock = sinon.stub(TruffleCommands, "execScript"); truffleExtensionAdapter = new TruffleExtensionAdapter(); }); @@ -37,4 +40,12 @@ describe("TruffleExtensionAdapter", () => { // Assert assert.strictEqual(deployContractsMock.calledOnce, true, "TruffleCommands.deployContracts should be called once"); }); + + it("exec method should call truffleCommands.execScript", async () => { + // Act + await truffleExtensionAdapter.execScript(Uri.file("./test.js")); + + // Assert + assert.strictEqual(execScriptMock.calledOnce, true, "TruffleCommands.execScript should be called once"); + }); });