diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index 8a7ed9a9ec..d0cdfacf13 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -230,7 +230,7 @@ module.exports = { }, 'Should get current files #group7': async function (browser: NightwatchBrowser) { - await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, tests: { isDirectory: true }, scripts: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, '/') + await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, '/') }, 'Should throw error on current file #group7': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'Error from IDE : Error: No such file or directory No file selected', null, null) @@ -285,7 +285,7 @@ module.exports = { 'Should create workspace #group2': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace') await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null) - await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, tests: { isDirectory: true }, scripts: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, null) + await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, scripts: { isDirectory: true }, tests: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, null) }, 'Should get all workspaces #group2': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['default_workspace', 'emptyworkspace', 'testspace'], null, null) diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index b9daeaa543..4b14321d6f 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -31,9 +31,83 @@ module.exports = { .waitForElementNotPresent('[data-id="landingPageHomeContainer"]') }, - 'Should create two workspace and switch to the first one #group1': function (browser: NightwatchBrowser) { + // WORKSPACE TEMPLATES E2E START + + 'Should create Remix default workspace with files': function (browser: NightwatchBrowser) { browser .clickLaunchIcon('filePanel') + .click('*[data-id="workspaceCreate"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + // eslint-disable-next-line dot-notation + .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' }) + .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) + .pause(1000) + .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/storage.test.js"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/Ballot_test.sol"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemREADME.txt"]') + }, + + 'Should create blank workspace with no files': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspaceCreate"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + // eslint-disable-next-line dot-notation + .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' }) + .click('select[id="wstemplate"]') + .click('select[id="wstemplate"] option[value=blank]') + .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) + .pause(1000) + .assert.elementPresent('*[data-id="treeViewUltreeViewMenu"]') + .execute(function () { + const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]') + return fileList.getElementsByTagName('li').length; + }, [], function(result){ + // check there are no files in FE + browser.assert.equal(result.value, 0, 'Incorrect number of files'); + }); + }, + + 'Should create ERC20 workspace with files': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspaceCreate"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') + // eslint-disable-next-line dot-notation + .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' }) + .click('select[id="wstemplate"]') + .click('select[id="wstemplate"] option[value=erc20]') + .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) + .pause(1000) + .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemcontracts/SampleERC20.sol"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_web3.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/web3.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemscripts/ethers.ts"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests"]') + .assert.elementPresent('*[data-id="treeViewLitreeViewItemtests/SampleERC20_test.sol"]') + }, + + // WORKSPACE TEMPLATES E2E END + + 'Should create two workspace and switch to the first one': function (browser: NightwatchBrowser) { + browser .click('*[data-id="workspaceCreate"]') // create workspace_name .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > span') diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 25c41920f0..f2a423264b 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -121,9 +121,9 @@ module.exports = class Filepanel extends ViewPlugin { }) } - createWorkspace (workspaceName, isEmpty) { + createWorkspace (workspaceName, workspaceTemplateName, isEmpty) { return new Promise((resolve, reject) => { - this.emit('createWorkspaceReducerEvent', workspaceName, isEmpty, (err, data) => { + this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => { if (err) reject(err) else resolve(data || true) }) diff --git a/apps/remix-ide/src/assets/img/sourcifyLogo.webp b/apps/remix-ide/src/assets/img/sourcifyLogo.webp deleted file mode 100644 index 1a2b3f0003..0000000000 Binary files a/apps/remix-ide/src/assets/img/sourcifyLogo.webp and /dev/null differ diff --git a/apps/remix-ide/src/assets/img/sourcifyNewLogo.webp b/apps/remix-ide/src/assets/img/sourcifyNewLogo.webp new file mode 100644 index 0000000000..16d652b3d9 Binary files /dev/null and b/apps/remix-ide/src/assets/img/sourcifyNewLogo.webp differ diff --git a/gulpfile.js b/gulpfile.js index 0d5d2ead87..c32e229f8f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -51,6 +51,7 @@ task('syncLibVersions', async function () { 'remix-solidity', 'remix-tests', 'remix-url-resolver', + 'remix-ws-templates', 'remixd' ] diff --git a/jest.config.js b/jest.config.js index d68c1af4eb..b2722cad44 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,6 +17,9 @@ module.exports = { "@remix-project/remix-url-resolver": "/../../dist/libs/remix-url-resolver/src/index.js" , + "@remix-project/remix-ws-templates": + "/../../dist/libs/remix-ws-templates/src/index.js" + , "@remix-project/remixd": "/../../dist/libs/remixd/index.js" } }; diff --git a/lerna.json b/lerna.json index 35e3a8c36f..708004a98a 100644 --- a/lerna.json +++ b/lerna.json @@ -7,7 +7,8 @@ "dist/libs/remix-simulator", "dist/libs/remix-solidity", "dist/libs/remix-tests", - "dist/libs/remix-url-resolver" + "dist/libs/remix-url-resolver", + "dist/libs/remix-ws-templates" ], "version": "independent", "command": { diff --git a/libs/README.md b/libs/README.md index e8db9a2ad3..498defef08 100644 --- a/libs/README.md +++ b/libs/README.md @@ -23,6 +23,7 @@ Here is the brief description of Remix libraries. + [`remix-lib`](remix-lib/README.md): Common place for libraries being used across multiple modules + [`remix-tests`](remix-tests/README.md): Unit test Solidity smart contracts. It works as a plugin & as CLI both + [`remix-url-resolver`](remix-url-resolver/README.md): Provide helpers for resolving the content from external URL ( including github, swarm, ipfs etc.). ++ [`remix-ws-templates`](remix-ws-templates/README.md): To create a workspace using different templates on Remix IDE + [`remixd`](remixd/README.md): Allow accessing local filesystem from Remix IDE by running a daemon Each library is an NPM package and has basic documentation about its usage in its own `README`. diff --git a/libs/remix-ui/README.md b/libs/remix-ui/README.md index 7bb33ea706..8dd1b4b48d 100644 --- a/libs/remix-ui/README.md +++ b/libs/remix-ui/README.md @@ -3,7 +3,7 @@ This library was generated with [Nx](https://nx.dev). ## Pre-requisite -- Install **NxConsole** vscose extension +- Install **NxConsole** vscode extension ## Steps To Generate React App - Open **NxConsole** extension - Click generate option @@ -32,4 +32,4 @@ This library was generated with [Nx](https://nx.dev). - Select the name of the project/library that uses the component. (e.g TreeView library) - Set component directory if needed. - Click the run button in the top right corner of the generate page. -- Your react component should be created with the project/library name specified. \ No newline at end of file +- Your react component should be created with the project/library name specified. diff --git a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx index 66df92afc1..52c50c6dba 100644 --- a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx +++ b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx @@ -255,7 +255,7 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { startStarkNet()} /> startSolhint()} /> startLearnEth()} /> - startSourceVerify()} /> + startSourceVerify()} /> diff --git a/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx b/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx index 30da897734..984257915d 100644 --- a/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/mainnet.tsx @@ -92,7 +92,7 @@ export function MainnetPrompt (props: MainnetProps) {
Gas limit: - ${props.tx.gas} + {props.tx.gas}
{ props.network.lastBlock.baseFeePerGas diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts index ffd7c9e05a..188087d4fb 100644 --- a/libs/remix-ui/workspace/src/lib/actions/events.ts +++ b/libs/remix-ui/workspace/src/lib/actions/events.ts @@ -1,6 +1,6 @@ import { extractParentFromKey } from '@remix-ui/helper' import React from 'react' -import { action } from '../types' +import { action, WorkspaceTemplate } from '../types' import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode } from './payload' import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' @@ -10,8 +10,8 @@ let plugin, dispatch: React.Dispatch export const listenOnPluginEvents = (filePanelPlugin) => { plugin = filePanelPlugin - plugin.on('filePanel', 'createWorkspaceReducerEvent', (name: string, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void) => { - createWorkspace(name, isEmpty, cb) + plugin.on('filePanel', 'createWorkspaceReducerEvent', (name: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void) => { + createWorkspace(name, workspaceTemplateName, isEmpty, cb) }) plugin.on('filePanel', 'renameWorkspaceReducerEvent', (oldName: string, workspaceName: string, cb: (err: Error, result?: string | number | boolean | Record) => void) => { diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index ac50afd17a..81566828ac 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -46,10 +46,10 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React. plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath)) } else { if (workspaces.length === 0) { - await createWorkspaceTemplate('default_workspace', 'default-template') + await createWorkspaceTemplate('default_workspace', 'remixDefault') plugin.setWorkspace({ name: 'default_workspace', isLocalhost: false }) dispatch(setCurrentWorkspace('default_workspace')) - await loadWorkspacePreset('default-template') + await loadWorkspacePreset('remixDefault') } else { if (workspaces.length > 0) { workspaceProvider.setWorkspace(workspaces[workspaces.length - 1]) diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 14ce0ec355..909b88d0c9 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -4,9 +4,9 @@ import axios, { AxiosResponse } from 'axios' import { addInputFieldSuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace } from './payload' import { checkSlash, checkSpecialChars } from '@remix-ui/helper' -import { JSONStandardInput } from '../types' -import { examples } from '../templates/examples' +import { JSONStandardInput, WorkspaceTemplate } from '../types' import { QueryParams } from '@remix-project/remix-lib' +import * as templateWithContent from '@remix-project/remix-ws-templates' const LOCALHOST = ' - connect to localhost - ' @@ -41,9 +41,9 @@ export const addInputField = async (type: 'file' | 'folder', path: string, cb?: return promise } -export const createWorkspace = async (workspaceName: string, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record) => void) => { +export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record) => void) => { await plugin.fileManager.closeAllFiles() - const promise = createWorkspaceTemplate(workspaceName, 'default-template') + const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName) dispatch(createWorkspaceRequest(promise)) promise.then(async () => { @@ -51,7 +51,7 @@ export const createWorkspace = async (workspaceName: string, isEmpty = false, cb await plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) await plugin.setWorkspaces(await getWorkspaces()) await plugin.workspaceCreated(workspaceName) - if (!isEmpty) await loadWorkspacePreset('default-template') + if (!isEmpty) await loadWorkspacePreset(workspaceTemplateName) cb && cb(null, workspaceName) }).catch((error) => { dispatch(createWorkspaceError({ error })) @@ -60,10 +60,10 @@ export const createWorkspace = async (workspaceName: string, isEmpty = false, cb return promise } -export const createWorkspaceTemplate = async (workspaceName: string, template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => { +export const createWorkspaceTemplate = async (workspaceName: string, template: WorkspaceTemplate = 'remixDefault') => { if (!workspaceName) throw new Error('workspace name cannot be empty') if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') - if (await workspaceExists(workspaceName) && template === 'default-template') throw new Error('workspace already exists') + if (await workspaceExists(workspaceName) && template === 'remixDefault') throw new Error('workspace already exists') else { const workspaceProvider = plugin.fileProviders.workspace @@ -77,7 +77,7 @@ export type UrlParametersType = { url: string } -export const loadWorkspacePreset = async (template: 'gist-template' | 'code-template' | 'default-template' = 'default-template') => { +export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDefault') => { const workspaceProvider = plugin.fileProviders.workspace const params = queryParams.get() as UrlParametersType @@ -150,15 +150,22 @@ export const loadWorkspacePreset = async (template: 'gist-template' | 'code-temp } break - case 'default-template': - // creates a new workspace and populates it with default project template. - // insert example contracts - for (const file in examples) { - try { - await workspaceProvider.set(examples[file].name, examples[file].content) - } catch (error) { - console.error(error) + default: + try { + const templateList = Object.keys(templateWithContent) + if (!templateList.includes(template)) break + // @ts-ignore + const files = await templateWithContent[template]() + for (const file in files) { + try { + await workspaceProvider.set(file, files[file]) + } catch (error) { + console.error(error) + } } + } catch (e) { + dispatch(displayNotification('Workspace load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null)) + console.error(e) } break } diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index b5daf01cc9..792bed8188 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -9,7 +9,7 @@ export const FileSystemContext = createContext<{ dispatchFetchDirectory:(path: string) => Promise, dispatchAddInputField:(path: string, type: 'file' | 'folder') => Promise, dispatchRemoveInputField:(path: string) => Promise, - dispatchCreateWorkspace: (workspaceName: string) => Promise, + dispatchCreateWorkspace: (workspaceName: string, workspaceTemplateName: string) => Promise, toast: (toasterMsg: string) => void, dispatchFetchWorkspaceDirectory: (path: string) => Promise, dispatchSwitchToWorkspace: (name: string) => Promise, diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index cb4d4ce864..fab8e652b5 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -6,7 +6,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import { FileSystemContext } from '../contexts' import { browserReducer, browserInitialState } from '../reducers/workspace' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip } from '../actions' -import { Modal, WorkspaceProps } from '../types' +import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Workspace } from '../remix-ui-workspace' import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel/type' @@ -43,8 +43,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => { await removeInputField(path) } - const dispatchCreateWorkspace = async (workspaceName: string) => { - await createWorkspace(workspaceName) + const dispatchCreateWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate) => { + await createWorkspace(workspaceName, workspaceTemplateName) } const dispatchFetchWorkspaceDirectory = async (path: string) => { diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index ceb4c08b07..4ab6fb3000 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -12,6 +12,7 @@ export function Workspace () { const global = useContext(FileSystemContext) const workspaceRenameInput = useRef() const workspaceCreateInput = useRef() + const workspaceCreateTemplateInput = useRef() useEffect(() => { resetFocus() @@ -83,9 +84,11 @@ export function Workspace () { if (workspaceCreateInput.current === undefined) return // @ts-ignore: Object is possibly 'null'. const workspaceName = workspaceCreateInput.current.value + // @ts-ignore: Object is possibly 'null'. + const workspaceTemplateName = workspaceCreateTemplateInput.current.value || 'remixDefault' try { - await global.dispatchCreateWorkspace(workspaceName) + await global.dispatchCreateWorkspace(workspaceName, workspaceTemplateName) } catch (e) { global.modal('Create Workspace', e.message, 'OK', () => {}, '') console.error(e) @@ -119,7 +122,14 @@ export function Workspace () { const createModalMessage = () => { return ( <> - + +
+ + ) } diff --git a/libs/remix-ui/workspace/src/lib/templates/blank.ts b/libs/remix-ui/workspace/src/lib/templates/blank.ts new file mode 100644 index 0000000000..56004c9f9e --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/templates/blank.ts @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/templates/erc20.ts b/libs/remix-ui/workspace/src/lib/templates/erc20.ts new file mode 100644 index 0000000000..348f9dbc73 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/templates/erc20.ts @@ -0,0 +1,125 @@ +'use strict' + +const erc20 = `// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title SampleERC20 + * @dev Create a sample ERC20 standard token + */ + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract SampleERC20 is ERC20 { + + constructor(string memory tokenName, string memory tokenSymbol) ERC20(tokenName, tokenSymbol) {} +}` + +const erc20_test = `// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; +import "remix_tests.sol"; +import "../contracts/SampleERC20.sol"; + +contract SampleERC20Test { + + SampleERC20 s; + function beforeAll () public { + s = new SampleERC20("TestToken", "TST"); + } + + function testTokenNameAndSymbol () public { + Assert.equal(s.name(), "TestToken", "token name did not match"); + Assert.equal(s.symbol(), "TST", "token symbol did not match"); + } +} +` + +/* eslint-disable no-useless-escape */ +const deployWithWeb3 = `import { deploy } from './web3.ts' + +(async () => { + try { + const result = await deploy('SampleERC20', ['testToken', 'TST']) + console.log(\`address: \${result.address\}\`) + } catch (e) { + console.log(e.message) + } +})()` + +const deployWithEthers = `import { deploy } from './ethers.ts' + +(async () => { + try { + const result = await deploy('SampleERC20', ['testToken', 'TST']) + console.log(\`address: \${result.address\}\`) + } catch (e) { + console.log(e.message) + } + })()` + +const libWeb3 = ` +export const deploy = async (contractName: string, args: Array, from?: string, gas?: number) => { + + console.log(\`deploying \${contractName\}\`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = \`browser/contracts/artifacts/\${contractName\}.json\` + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + + const accounts = await web3.eth.getAccounts() + + let contract = new web3.eth.Contract(metadata.abi) + + contract = contract.deploy({ + data: metadata.data.bytecode.object, + arguments: args + }) + + const newContractInstance = await contract.send({ + from: from || accounts[0], + gas: gas || 1500000 + }) + return newContractInstance.options +}: Promise` + +const libEthers = ` +export const deploy = async (contractName: string, args: Array, from?: string) => { + + console.log(\`deploying \${contractName\}\`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = \`browser/contracts/artifacts/\${contractName\}.json\` + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + // 'web3Provider' is a remix global variable object + const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + + let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); + + let contract + if (from) { + contract = await factory.connect(from).deploy(...args); + } else { + contract = await factory.deploy(...args); + } + + // The contract is NOT deployed yet; we must wait until it is mined + await contract.deployed() + return contract +}: Promise` +/* eslint-enable no-useless-escape */ + + + + +export default { + erc20: { name: 'contracts/SampleERC20.sol', content: erc20 }, + erc20_test: { name: 'tests/SampleERC20_test.sol', content: erc20_test }, + deployWithWeb3: { name: 'scripts/deploy_with_web3.ts', content: deployWithWeb3 }, + deployWithEthers: { name: 'scripts/deploy_with_ethers.ts', content: deployWithEthers }, + web3: { name: 'scripts/web3.ts', content: libWeb3 }, + ethers: { name: 'scripts/ethers.ts', content: libEthers }, +} diff --git a/libs/remix-ui/workspace/src/lib/templates/examples.ts b/libs/remix-ui/workspace/src/lib/templates/examples.ts deleted file mode 100644 index 6ccfac3160..0000000000 --- a/libs/remix-ui/workspace/src/lib/templates/examples.ts +++ /dev/null @@ -1,406 +0,0 @@ -'use strict' - -const storage = `// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; - -/** - * @title Storage - * @dev Store & retrieve value in a variable - * @custom:dev-run-script ./scripts/deploy_with_ethers.ts - */ -contract Storage { - - uint256 number; - - /** - * @dev Store value in variable - * @param num value to store - */ - function store(uint256 num) public { - number = num; - } - - /** - * @dev Return value - * @return value of 'number' - */ - function retrieve() public view returns (uint256){ - return number; - } -}` - -const owner = `// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; - -import "hardhat/console.sol"; - -/** - * @title Owner - * @dev Set & change owner - */ -contract Owner { - - address private owner; - - // event for EVM logging - event OwnerSet(address indexed oldOwner, address indexed newOwner); - - // modifier to check if caller is owner - modifier isOwner() { - // If the first argument of 'require' evaluates to 'false', execution terminates and all - // changes to the state and to Ether balances are reverted. - // This used to consume all gas in old EVM versions, but not anymore. - // It is often a good idea to use 'require' to check if functions are called correctly. - // As a second argument, you can also provide an explanation about what went wrong. - require(msg.sender == owner, "Caller is not owner"); - _; - } - - /** - * @dev Set contract deployer as owner - */ - constructor() { - console.log("Owner contract deployed by:", msg.sender); - owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor - emit OwnerSet(address(0), owner); - } - - /** - * @dev Change owner - * @param newOwner address of new owner - */ - function changeOwner(address newOwner) public isOwner { - emit OwnerSet(owner, newOwner); - owner = newOwner; - } - - /** - * @dev Return owner address - * @return address of owner - */ - function getOwner() external view returns (address) { - return owner; - } -}` - -const ballot = `// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; - -/** - * @title Ballot - * @dev Implements voting process along with vote delegation - */ -contract Ballot { - - struct Voter { - uint weight; // weight is accumulated by delegation - bool voted; // if true, that person already voted - address delegate; // person delegated to - uint vote; // index of the voted proposal - } - - struct Proposal { - // If you can limit the length to a certain number of bytes, - // always use one of bytes1 to bytes32 because they are much cheaper - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes - } - - address public chairperson; - - mapping(address => Voter) public voters; - - Proposal[] public proposals; - - /** - * @dev Create a new ballot to choose one of 'proposalNames'. - * @param proposalNames names of proposals - */ - constructor(bytes32[] memory proposalNames) { - chairperson = msg.sender; - voters[chairperson].weight = 1; - - for (uint i = 0; i < proposalNames.length; i++) { - // 'Proposal({...})' creates a temporary - // Proposal object and 'proposals.push(...)' - // appends it to the end of 'proposals'. - proposals.push(Proposal({ - name: proposalNames[i], - voteCount: 0 - })); - } - } - - /** - * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. - * @param voter address of voter - */ - function giveRightToVote(address voter) public { - require( - msg.sender == chairperson, - "Only chairperson can give right to vote." - ); - require( - !voters[voter].voted, - "The voter already voted." - ); - require(voters[voter].weight == 0); - voters[voter].weight = 1; - } - - /** - * @dev Delegate your vote to the voter 'to'. - * @param to address to which vote is delegated - */ - function delegate(address to) public { - Voter storage sender = voters[msg.sender]; - require(!sender.voted, "You already voted."); - require(to != msg.sender, "Self-delegation is disallowed."); - - while (voters[to].delegate != address(0)) { - to = voters[to].delegate; - - // We found a loop in the delegation, not allowed. - require(to != msg.sender, "Found loop in delegation."); - } - sender.voted = true; - sender.delegate = to; - Voter storage delegate_ = voters[to]; - if (delegate_.voted) { - // If the delegate already voted, - // directly add to the number of votes - proposals[delegate_.vote].voteCount += sender.weight; - } else { - // If the delegate did not vote yet, - // add to her weight. - delegate_.weight += sender.weight; - } - } - - /** - * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. - * @param proposal index of proposal in the proposals array - */ - function vote(uint proposal) public { - Voter storage sender = voters[msg.sender]; - require(sender.weight != 0, "Has no right to vote"); - require(!sender.voted, "Already voted."); - sender.voted = true; - sender.vote = proposal; - - // If 'proposal' is out of the range of the array, - // this will throw automatically and revert all - // changes. - proposals[proposal].voteCount += sender.weight; - } - - /** - * @dev Computes the winning proposal taking all previous votes into account. - * @return winningProposal_ index of winning proposal in the proposals array - */ - function winningProposal() public view - returns (uint winningProposal_) - { - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winningProposal_ = p; - } - } - } - - /** - * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then - * @return winnerName_ the name of the winner - */ - function winnerName() public view - returns (bytes32 winnerName_) - { - winnerName_ = proposals[winningProposal()].name; - } -} -` - -const ballotTest = `// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; -import "remix_tests.sol"; // this import is automatically injected by Remix. -import "hardhat/console.sol"; -import "../contracts/3_Ballot.sol"; - -contract BallotTest { - - bytes32[] proposalNames; - - Ballot ballotToTest; - function beforeAll () public { - proposalNames.push(bytes32("candidate1")); - ballotToTest = new Ballot(proposalNames); - } - - function checkWinningProposal () public { - console.log("Running checkWinningProposal"); - ballotToTest.vote(0); - Assert.equal(ballotToTest.winningProposal(), uint(0), "proposal at index 0 should be the winning proposal"); - Assert.equal(ballotToTest.winnerName(), bytes32("candidate1"), "candidate1 should be the winner name"); - } - - function checkWinninProposalWithReturnValue () public view returns (bool) { - return ballotToTest.winningProposal() == 0; - } -} -` - -/* eslint-disable no-useless-escape */ -const deployWithWeb3 = ` -// This script can be used to deploy the "Storage" contract using Web3 library. -// Please make sure to compile "./contracts/1_Storage.sol" file before running this script. -// And use Right click -> "Run" from context menu of the file to run the script. Shortcut: Ctrl+Shift+S - -import { deploy } from './web3.ts' - -(async () => { - try { - const result = await deploy('Storage', []) - console.log(\`address: \${result.address\}\`) - } catch (e) { - console.log(e.message) - } -})()` - -const deployWithEthers = ` -// This script can be used to deploy the "Storage" contract using ethers.js library. -// Please make sure to compile "./contracts/1_Storage.sol" file before running this script. -// And use Right click -> "Run" from context menu of the file to run the script. Shortcut: Ctrl+Shift+S - -import { deploy } from './ethers.ts' - -(async () => { - try { - const result = await deploy('Storage', []) - console.log(\`address: \${result.address\}\`) - } catch (e) { - console.log(e.message) - } - })()` - -const libWeb3 = ` -export const deploy = async (contractName: string, arguments: Array, from?: string, gas?: number) => { - - console.log(\`deploying \${contractName\}\`) - // Note that the script needs the ABI which is generated from the compilation artifact. - // Make sure contract is compiled and artifacts are generated - const artifactsPath = \`browser/contracts/artifacts/\${contractName\}.json\` // Change this for different path - - const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) - - const accounts = await web3.eth.getAccounts() - - let contract = new web3.eth.Contract(metadata.abi) - - contract = contract.deploy({ - data: metadata.data.bytecode.object, - arguments - }) - - const newContractInstance = await contract.send({ - from: from || accounts[0], - gas: gas || 1500000 - }) - return newContractInstance.options -}: Promise` - -const libEthers = ` -export const deploy = async (contractName: string, arguments: Array, from?: string) => { - - console.log(\`deploying \${contractName\}\`) - // Note that the script needs the ABI which is generated from the compilation artifact. - // Make sure contract is compiled and artifacts are generated - const artifactsPath = \`browser/contracts/artifacts/\${contractName\}.json\` // Change this for different path - - const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) - // 'web3Provider' is a remix global variable object - const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() - - let factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); - - let contract - if (from) { - contract = await factory.connect(from).deploy(...arguments); - } else { - contract = await factory.deploy(...arguments); - } - - // The contract is NOT deployed yet; we must wait until it is mined - await contract.deployed() - return contract -}: Promise` -/* eslint-enable no-useless-escape */ - -const storageTestJs = `// Right click on the script name and hit "Run" to execute -const { expect } = require("chai"); -const { ethers } = require("hardhat"); - -describe("Storage", function () { - it("test initial value", async function () { - const Storage = await ethers.getContractFactory("Storage"); - const storage = await Storage.deploy(); - await storage.deployed(); - console.log('storage deployed at:'+ storage.address) - expect((await storage.retrieve()).toNumber()).to.equal(0); - }); - it("test updating and retrieving updated value", async function () { - const Storage = await ethers.getContractFactory("Storage"); - const storage = await Storage.deploy(); - await storage.deployed(); - const storage2 = await ethers.getContractAt("Storage", storage.address); - const setValue = await storage2.store(56); - await setValue.wait(); - expect((await storage2.retrieve()).toNumber()).to.equal(56); - }); -});` - -const readme = `REMIX DEFAULT WORKSPACE - -Remix default workspace is present when: -i. Remix loads for the very first time -ii. A new workspace is created -iii. There are no files existing in the File Explorer - -This workspace contains 3 directories: - -1. 'contracts': Holds three contracts with different complexity level, denoted with number prefix in file name. -2. 'scripts': Holds two scripts to deploy a contract. It is explained below. -3. 'tests': Contains one Solidity test file for 'Ballot' contract & one JS test file for 'Storage' contract - -SCRIPTS - -The 'scripts' folder contains two example async/await scripts for deploying the 'Storage' contract. -For the deployment of any other contract, 'contractName' and 'constructorArgs' should be updated (along with other code if required). - -Also, there is a script containing some unit tests for Storage contract inside tests directory. - -To run a script, right click on file name in the file explorer and click 'Run'. Remember, Solidity file must already be compiled. -Output from script will appear in remix terminal. - -Please note, 'require' statement is supported in a limited manner for Remix supported modules. -For now, modules supported by Remix are ethers, web3, swarmgw, chai, remix and hardhat only for hardhat.ethers object/plugin. -For unsupported modules, an error like this will be thrown: ' module require is not supported by Remix IDE will be shown.' -` - -export const examples = { - storage: { name: 'contracts/1_Storage.sol', content: storage }, - owner: { name: 'contracts/2_Owner.sol', content: owner }, - ballot: { name: 'contracts/3_Ballot.sol', content: ballot }, - storageTestJs: { name: 'tests/storage.test.js', content: storageTestJs }, - ballot_test: { name: 'tests/Ballot_test.sol', content: ballotTest }, - deployWithWeb3: { name: 'scripts/deploy_with_web3.ts', content: deployWithWeb3 }, - deployWithEthers: { name: 'scripts/deploy_with_ethers.ts', content: deployWithEthers }, - web3: { name: 'scripts/web3.ts', content: libWeb3 }, - ethers: { name: 'scripts/ethers.ts', content: libEthers }, - readme: { name: 'README.txt', content: readme } -} diff --git a/libs/remix-ui/workspace/src/lib/templates/index.ts b/libs/remix-ui/workspace/src/lib/templates/index.ts new file mode 100644 index 0000000000..0fff14d78b --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/templates/index.ts @@ -0,0 +1,2 @@ +export { default as erc20 } from './erc20' +export { default as blank } from './blank' diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index d7ce409d72..b2d490713b 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -13,10 +13,11 @@ export interface JSONStandardInput { }; } export type MenuItems = action[] +export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'erc20' export interface WorkspaceProps { plugin: { setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void, - createWorkspace: (name: string) => void, + createWorkspace: (name: string, workspaceTemplateName: string) => void, renameWorkspace: (oldName: string, newName: string) => void workspaceRenamed: ({ name: string }) => void, workspaceCreated: ({ name: string }) => void, diff --git a/libs/remix-ws-templates/.eslintrc b/libs/remix-ws-templates/.eslintrc new file mode 100644 index 0000000000..0a49d6ddc4 --- /dev/null +++ b/libs/remix-ws-templates/.eslintrc @@ -0,0 +1,12 @@ +{ + "extends": "../../.eslintrc", + "rules": { + }, + "env": { + "browser": true, + "amd": true, + "node": true, + "es6": true + }, + "ignorePatterns": ["!**/*"] +} \ No newline at end of file diff --git a/libs/remix-ws-templates/README.md b/libs/remix-ws-templates/README.md new file mode 100644 index 0000000000..379f5702de --- /dev/null +++ b/libs/remix-ws-templates/README.md @@ -0,0 +1,20 @@ +## remix-ws-templates +[![npm version](https://badge.fury.io/js/%40remix-project%2Fremix-ws-templates.svg)](https://www.npmjs.com/package/@remix-project/remix-ws-templates) + + +`@remix-project/remix-ws-templates` is used to create a workspace using different templates on Remix IDE. + +### Installation + +`@remix-project/remix-ws-templates` is an NPM package and can be installed using NPM as: + +`npm install @remix-project/remix-ws-templates` + +### Contribute + +Please feel free to open an issue or a pull request. + +In case you want to add some code, do have a look to our contribution guidelnes [here](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md). Reach us on [Gitter](https://gitter.im/ethereum/remix) in case of any queries. + +### License +MIT © 2022 Remix Team diff --git a/libs/remix-ws-templates/package.json b/libs/remix-ws-templates/package.json new file mode 100644 index 0000000000..949c7b21f8 --- /dev/null +++ b/libs/remix-ws-templates/package.json @@ -0,0 +1,23 @@ +{ + "name": "@remix-project/remix-ws-templates", + "version": "1.0.0", + "description": "Create a Remix IDE workspace using different templates", + "main": "src/index.js", + "types": "src/index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ethereum/remix-project.git" + }, + "author": "Aniket-Engg", + "license": "MIT", + "bugs": { + "url": "https://github.com/ethereum/remix-project/issues" + }, + "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-ws-templates#readme" + } \ No newline at end of file diff --git a/libs/remix-ws-templates/src/index.ts b/libs/remix-ws-templates/src/index.ts new file mode 100644 index 0000000000..0b1893126b --- /dev/null +++ b/libs/remix-ws-templates/src/index.ts @@ -0,0 +1,3 @@ +export { default as remixDefault } from './templates/remixDefault' +export { default as erc20 } from './templates/erc20' +export { default as blank } from './templates/blank' \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/blank/index.ts b/libs/remix-ws-templates/src/templates/blank/index.ts new file mode 100644 index 0000000000..6840e8d560 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/blank/index.ts @@ -0,0 +1 @@ +export default async () => { return {}} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/contracts/SampleERC20.sol b/libs/remix-ws-templates/src/templates/erc20/contracts/SampleERC20.sol new file mode 100644 index 0000000000..23aa792466 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/contracts/SampleERC20.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title SampleERC20 + * @dev Create a sample ERC20 standard token + */ + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract SampleERC20 is ERC20 { + + constructor(string memory tokenName, string memory tokenSymbol) ERC20(tokenName, tokenSymbol) {} +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/index.ts b/libs/remix-ws-templates/src/templates/erc20/index.ts new file mode 100644 index 0000000000..cc1f087b55 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/index.ts @@ -0,0 +1,16 @@ +export default async () => { + return { + // @ts-ignore + 'contracts/SampleERC20.sol': (await import('raw-loader!./contracts/SampleERC20.sol')).default, + // @ts-ignore + 'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default, + // @ts-ignore + 'scripts/deploy_with_web3.ts': (await import('!!raw-loader!./scripts/deploy_with_web3.ts')).default, + // @ts-ignore + 'scripts/ethers.ts': (await import('!!raw-loader!./scripts/ethers.ts')).default, + // @ts-ignore + 'scripts/web3.ts': (await import('!!raw-loader!./scripts/web3.ts')).default, + // @ts-ignore + 'tests/SampleERC20_test.sol': (await import('raw-loader!./tests/SampleERC20_test.sol')).default + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/erc20/scripts/deploy_with_ethers.ts new file mode 100644 index 0000000000..f863a6bcd5 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/scripts/deploy_with_ethers.ts @@ -0,0 +1,10 @@ +import { deploy } from './ethers' + +(async () => { + try { + const result = await deploy('SampleERC20', ['testToken', 'TST']) + console.log(`address: ${result.address}`) + } catch (e) { + console.log(e.message) + } + })() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/scripts/deploy_with_web3.ts b/libs/remix-ws-templates/src/templates/erc20/scripts/deploy_with_web3.ts new file mode 100644 index 0000000000..653b1f244e --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/scripts/deploy_with_web3.ts @@ -0,0 +1,10 @@ +import { deploy } from './web3' + +(async () => { + try { + const result = await deploy('SampleERC20', ['testToken', 'TST']) + console.log(`address: ${result.address}`) + } catch (e) { + console.log(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/scripts/ethers.ts b/libs/remix-ws-templates/src/templates/erc20/scripts/ethers.ts new file mode 100644 index 0000000000..04c363322a --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/scripts/ethers.ts @@ -0,0 +1,27 @@ + + + +export const deploy = async (contractName: string, args: Array, from?: string): Promise => { + + console.log(`deploying ${contractName}`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = `browser/contracts/artifacts/${contractName}.json` + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + // 'web3Provider' is a remix global variable object + const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + + const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); + + let contract + if (from) { + contract = await factory.connect(from).deploy(...args); + } else { + contract = await factory.deploy(...args); + } + + // The contract is NOT deployed yet; we must wait until it is mined + await contract.deployed() + return contract +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/scripts/web3.ts b/libs/remix-ws-templates/src/templates/erc20/scripts/web3.ts new file mode 100644 index 0000000000..62f43b6611 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/scripts/web3.ts @@ -0,0 +1,24 @@ +export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => { + + console.log(`deploying ${contractName}`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = `browser/contracts/artifacts/${contractName}.json` + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + + const accounts = await web3.eth.getAccounts() + + let contract = new web3.eth.Contract(metadata.abi) + + contract = contract.deploy({ + data: metadata.data.bytecode.object, + arguments: args + }) + + const newContractInstance = await contract.send({ + from: from || accounts[0], + gas: gas || 1500000 + }) + return newContractInstance.options +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/erc20/tests/SampleERC20_test.sol b/libs/remix-ws-templates/src/templates/erc20/tests/SampleERC20_test.sol new file mode 100644 index 0000000000..19eb11d97d --- /dev/null +++ b/libs/remix-ws-templates/src/templates/erc20/tests/SampleERC20_test.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; +import "remix_tests.sol"; +import "../contracts/SampleERC20.sol"; + +contract SampleERC20Test { + + SampleERC20 s; + function beforeAll () public { + s = new SampleERC20("TestToken", "TST"); + } + + function testTokenNameAndSymbol () public { + Assert.equal(s.name(), "TestToken", "token name did not match"); + Assert.equal(s.symbol(), "TST", "token symbol did not match"); + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/README.txt b/libs/remix-ws-templates/src/templates/remixDefault/README.txt new file mode 100644 index 0000000000..5e243584b2 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/README.txt @@ -0,0 +1,26 @@ +REMIX DEFAULT WORKSPACE + +Remix default workspace is present when: +i. Remix loads for the very first time +ii. A new workspace is created +iii. There are no files existing in the File Explorer + +This workspace contains 3 directories: + +1. 'contracts': Holds three contracts with different complexity level, denoted with number prefix in file name. +2. 'scripts': Holds two scripts to deploy a contract. It is explained below. +3. 'tests': Contains one Solidity test file for 'Ballot' contract & one JS test file for 'Storage' contract + +SCRIPTS + +The 'scripts' folder contains two example async/await scripts for deploying the 'Storage' contract. +For the deployment of any other contract, 'contractName' and 'constructorArgs' should be updated (along with other code if required). + +Also, there is a script containing some unit tests for Storage contract inside tests directory. + +To run a script, right click on file name in the file explorer and click 'Run'. Remember, Solidity file must already be compiled. +Output from script will appear in remix terminal. + +Please note, 'require' statement is supported in a limited manner for Remix supported modules. +For now, modules supported by Remix are ethers, web3, swarmgw, chai, remix and hardhat only for hardhat.ethers object/plugin. +For unsupported modules, an error like this will be thrown: ' module require is not supported by Remix IDE will be shown.' \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/contracts/1_Storage.sol b/libs/remix-ws-templates/src/templates/remixDefault/contracts/1_Storage.sol new file mode 100644 index 0000000000..6aa93223af --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/contracts/1_Storage.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Storage + * @dev Store & retrieve value in a variable + * @custom:dev-run-script ./scripts/deploy_with_ethers.ts + */ +contract Storage { + + uint256 number; + + /** + * @dev Store value in variable + * @param num value to store + */ + function store(uint256 num) public { + number = num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/contracts/2_Owner.sol b/libs/remix-ws-templates/src/templates/remixDefault/contracts/2_Owner.sol new file mode 100644 index 0000000000..9949f8cdd6 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/contracts/2_Owner.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +import "hardhat/console.sol"; + +/** + * @title Owner + * @dev Set & change owner + */ +contract Owner { + + address private owner; + + // event for EVM logging + event OwnerSet(address indexed oldOwner, address indexed newOwner); + + // modifier to check if caller is owner + modifier isOwner() { + // If the first argument of 'require' evaluates to 'false', execution terminates and all + // changes to the state and to Ether balances are reverted. + // This used to consume all gas in old EVM versions, but not anymore. + // It is often a good idea to use 'require' to check if functions are called correctly. + // As a second argument, you can also provide an explanation about what went wrong. + require(msg.sender == owner, "Caller is not owner"); + _; + } + + /** + * @dev Set contract deployer as owner + */ + constructor() { + console.log("Owner contract deployed by:", msg.sender); + owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor + emit OwnerSet(address(0), owner); + } + + /** + * @dev Change owner + * @param newOwner address of new owner + */ + function changeOwner(address newOwner) public isOwner { + emit OwnerSet(owner, newOwner); + owner = newOwner; + } + + /** + * @dev Return owner address + * @return address of owner + */ + function getOwner() external view returns (address) { + return owner; + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/contracts/3_Ballot.sol b/libs/remix-ws-templates/src/templates/remixDefault/contracts/3_Ballot.sol new file mode 100644 index 0000000000..ffcc6c3609 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/contracts/3_Ballot.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Ballot + * @dev Implements voting process along with vote delegation + */ +contract Ballot { + + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + struct Proposal { + // If you can limit the length to a certain number of bytes, + // always use one of bytes1 to bytes32 because they are much cheaper + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + mapping(address => Voter) public voters; + + Proposal[] public proposals; + + /** + * @dev Create a new ballot to choose one of 'proposalNames'. + * @param proposalNames names of proposals + */ + constructor(bytes32[] memory proposalNames) { + chairperson = msg.sender; + voters[chairperson].weight = 1; + + for (uint i = 0; i < proposalNames.length; i++) { + // 'Proposal({...})' creates a temporary + // Proposal object and 'proposals.push(...)' + // appends it to the end of 'proposals'. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + /** + * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. + * @param voter address of voter + */ + function giveRightToVote(address voter) public { + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /** + * @dev Delegate your vote to the voter 'to'. + * @param to address to which vote is delegated + */ + function delegate(address to) public { + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + require(to != msg.sender, "Self-delegation is disallowed."); + + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /** + * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. + * @param proposal index of proposal in the proposals array + */ + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If 'proposal' is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /** + * @dev Computes the winning proposal taking all previous votes into account. + * @return winningProposal_ index of winning proposal in the proposals array + */ + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + /** + * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then + * @return winnerName_ the name of the winner + */ + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/index.ts b/libs/remix-ws-templates/src/templates/remixDefault/index.ts new file mode 100644 index 0000000000..683c616397 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/index.ts @@ -0,0 +1,24 @@ +export default async () => { + return { + // @ts-ignore + 'contracts/1_Storage.sol': (await import('raw-loader!./contracts/1_Storage.sol')).default, + // @ts-ignore + 'contracts/2_Owner.sol': (await import('raw-loader!./contracts/2_Owner.sol')).default, + // @ts-ignore + 'contracts/3_Ballot.sol': (await import('raw-loader!./contracts/3_Ballot.sol')).default, + // @ts-ignore + 'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default, + // @ts-ignore + 'scripts/deploy_with_web3.ts': (await import('!!raw-loader!./scripts/deploy_with_web3.ts')).default, + // @ts-ignore + 'scripts/ethers.ts': (await import('!!raw-loader!./scripts/ethers.ts')).default, + // @ts-ignore + 'scripts/web3.ts': (await import('!!raw-loader!./scripts/web3.ts')).default, + // @ts-ignore + 'tests/Ballot_test.sol': (await import('raw-loader!./tests/Ballot_test.sol')).default, + // @ts-ignore + 'tests/storage.test.js': (await import('!!raw-loader!./tests/storage.test.js')).default, + // @ts-ignore + 'README.txt': (await import('raw-loader!./README.txt')).default, + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts new file mode 100644 index 0000000000..e5fbf05264 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_ethers.ts @@ -0,0 +1,14 @@ +// This script can be used to deploy the "Storage" contract using ethers.js library. +// Please make sure to compile "./contracts/1_Storage.sol" file before running this script. +// And use Right click -> "Run" from context menu of the file to run the script. Shortcut: Ctrl+Shift+S + +import { deploy } from './ethers' + +(async () => { + try { + const result = await deploy('Storage', []) + console.log(`address: ${result.address}`) + } catch (e) { + console.log(e.message) + } + })() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_web3.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_web3.ts new file mode 100644 index 0000000000..d2fe1ff4ff --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/deploy_with_web3.ts @@ -0,0 +1,14 @@ +// This script can be used to deploy the "Storage" contract using Web3 library. +// Please make sure to compile "./contracts/1_Storage.sol" file before running this script. +// And use Right click -> "Run" from context menu of the file to run the script. Shortcut: Ctrl+Shift+S + +import { deploy } from './web3' + +(async () => { + try { + const result = await deploy('Storage', []) + console.log(`address: ${result.address}`) + } catch (e) { + console.log(e.message) + } +})() \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers.ts new file mode 100644 index 0000000000..5f11f62e5f --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/ethers.ts @@ -0,0 +1,24 @@ +export const deploy = async (contractName: string, args: Array, from?: string): Promise => { + + console.log(`deploying ${contractName}`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + // 'web3Provider' is a remix global variable object + const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() + + const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer); + + let contract + if (from) { + contract = await factory.connect(from).deploy(...args); + } else { + contract = await factory.deploy(...args); + } + + // The contract is NOT deployed yet; we must wait until it is mined + await contract.deployed() + return contract +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/scripts/web3.ts b/libs/remix-ws-templates/src/templates/remixDefault/scripts/web3.ts new file mode 100644 index 0000000000..975a7176b4 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/scripts/web3.ts @@ -0,0 +1,24 @@ +export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => { + + console.log(`deploying ${contractName}`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + + const accounts = await web3.eth.getAccounts() + + let contract = new web3.eth.Contract(metadata.abi) + + contract = contract.deploy({ + data: metadata.data.bytecode.object, + arguments: args + }) + + const newContractInstance = await contract.send({ + from: from || accounts[0], + gas: gas || 1500000 + }) + return newContractInstance.options +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/tests/Ballot_test.sol b/libs/remix-ws-templates/src/templates/remixDefault/tests/Ballot_test.sol new file mode 100644 index 0000000000..49146e99fa --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/tests/Ballot_test.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; +import "remix_tests.sol"; // this import is automatically injected by Remix. +import "hardhat/console.sol"; +import "../contracts/3_Ballot.sol"; + +contract BallotTest { + + bytes32[] proposalNames; + + Ballot ballotToTest; + function beforeAll () public { + proposalNames.push(bytes32("candidate1")); + ballotToTest = new Ballot(proposalNames); + } + + function checkWinningProposal () public { + console.log("Running checkWinningProposal"); + ballotToTest.vote(0); + Assert.equal(ballotToTest.winningProposal(), uint(0), "proposal at index 0 should be the winning proposal"); + Assert.equal(ballotToTest.winnerName(), bytes32("candidate1"), "candidate1 should be the winner name"); + } + + function checkWinninProposalWithReturnValue () public view returns (bool) { + return ballotToTest.winningProposal() == 0; + } +} \ No newline at end of file diff --git a/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js b/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js new file mode 100644 index 0000000000..aa14b35a00 --- /dev/null +++ b/libs/remix-ws-templates/src/templates/remixDefault/tests/storage.test.js @@ -0,0 +1,22 @@ +// Right click on the script name and hit "Run" to execute +const { expect } = require("chai"); +const { ethers } = require("hardhat"); + +describe("Storage", function () { + it("test initial value", async function () { + const Storage = await ethers.getContractFactory("Storage"); + const storage = await Storage.deploy(); + await storage.deployed(); + console.log('storage deployed at:'+ storage.address) + expect((await storage.retrieve()).toNumber()).to.equal(0); + }); + it("test updating and retrieving updated value", async function () { + const Storage = await ethers.getContractFactory("Storage"); + const storage = await Storage.deploy(); + await storage.deployed(); + const storage2 = await ethers.getContractAt("Storage", storage.address); + const setValue = await storage2.store(56); + await setValue.wait(); + expect((await storage2.retrieve()).toNumber()).to.equal(56); + }); +}); \ No newline at end of file diff --git a/libs/remix-ws-templates/tsconfig.json b/libs/remix-ws-templates/tsconfig.json new file mode 100644 index 0000000000..eb1415fa71 --- /dev/null +++ b/libs/remix-ws-templates/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node"], + }, + "include": ["**/*.ts"] +} \ No newline at end of file diff --git a/libs/remix-ws-templates/tsconfig.lib.json b/libs/remix-ws-templates/tsconfig.lib.json new file mode 100644 index 0000000000..165195b789 --- /dev/null +++ b/libs/remix-ws-templates/tsconfig.lib.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "rootDir": "./src", + "types": ["node"] + }, + "exclude": [ + "**/*.spec.ts", + "tests/" + ], + "include": ["**/index.ts"] +} diff --git a/libs/remix-ws-templates/types/index.d.ts b/libs/remix-ws-templates/types/index.d.ts new file mode 100644 index 0000000000..a7238130df --- /dev/null +++ b/libs/remix-ws-templates/types/index.d.ts @@ -0,0 +1,4 @@ +declare const remix:any +declare const ethers:any +declare const web3:any +declare const web3Provider:any \ No newline at end of file diff --git a/nx.json b/nx.json index bbf5b08f6f..c6fca5f4ad 100644 --- a/nx.json +++ b/nx.json @@ -50,6 +50,9 @@ "remix-url-resolver": { "tags": [] }, + "remix-ws-templates": { + "tags": [] + }, "remix-ide": { "tags": [], "implicitDependencies": [ @@ -60,7 +63,8 @@ "remix-solidity", "remix-tests", "remix-astwalker", - "remix-url-resolver" + "remix-url-resolver", + "remix-ws-templates" ] }, "remix-ide-e2e": { @@ -133,7 +137,7 @@ "remix-ui-app": { "tags": [] }, - "remix-ui-helper": { + "remix-ui-helper": { "tags": [] }, "remix-ui-vertical-icons-panel": { diff --git a/package.json b/package.json index fb127e9f7f..20b0ecb93b 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,8 @@ "workspace-schematic": "nx workspace-schematic", "dep-graph": "nx dep-graph", "help": "nx help", - "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search", - "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", + "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search", + "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remix-ws-templates,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs", "build:e2e": "node apps/remix-ide-e2e/src/buildGroupTests.js && tsc -p apps/remix-ide-e2e/tsconfig.e2e.json", diff --git a/tsconfig.base.json b/tsconfig.base.json index d9e148acdb..409590c800 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -34,6 +34,9 @@ "@remix-project/remix-url-resolver": [ "dist/libs/remix-url-resolver/src/index.js" ], + "@remix-project/remix-ws-templates": [ + "dist/libs/remix-ws-templates/src/index.js" + ], "@remixproject/debugger-plugin": ["apps/debugger/src/index.ts"], "@remixproject/solidity-compiler-plugin": [ "apps/solidity-compiler/src/index.ts" @@ -75,10 +78,16 @@ ], "@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"], "@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"], - "@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"], - "@remix-ui/solidity-unit-testing": ["libs/remix-ui/solidity-unit-testing/src/index.ts"], + "@remix-ui/editor-context-view": [ + "libs/remix-ui/editor-context-view/src/index.ts" + ], + "@remix-ui/solidity-unit-testing": [ + "libs/remix-ui/solidity-unit-testing/src/index.ts" + ], "@remix-ui/run-tab": ["libs/remix-ui/run-tab/src/index.ts"], - "@remix-ui/permission-handler": ["libs/remix-ui/permission-handler/src/index.ts"] + "@remix-ui/permission-handler": [ + "libs/remix-ui/permission-handler/src/index.ts" + ] } }, "exclude": ["node_modules", "tmp"] diff --git a/workspace.json b/workspace.json index 149905a868..7ead3dd32b 100644 --- a/workspace.json +++ b/workspace.json @@ -1,4 +1,3 @@ - { "version": 1, "projects": { @@ -756,8 +755,8 @@ } }, "remix-ui-plugin-manager": { - "root": "libs/remix-ui/plugin-manager", - "sourceRoot": "libs/remix-ui/plugin-manager/src", + "root": "libs/remix-ui/plugin-manager", + "sourceRoot": "libs/remix-ui/plugin-manager/src", "projectType": "library", "schematics": {}, "architect": { @@ -1010,7 +1009,6 @@ "tsConfig": ["libs/remix-ui/home-tab/tsconfig.lib.json"], "exclude": ["**/node_modules/**", "!libs/remix-ui/home-tab/**/*"] } - } } }, @@ -1039,8 +1037,8 @@ "builder": "@nrwl/linter:lint", "options": { "linter": "eslint", - "tsConfig": ["libs/remix-ui/editor/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "!libs/remix-ui/editor/**/*"] + "tsConfig": ["libs/remix-ui/editor/tsconfig.lib.json"], + "exclude": ["**/node_modules/**", "!libs/remix-ui/editor/**/*"] } } } @@ -1101,8 +1099,13 @@ "builder": "@nrwl/linter:lint", "options": { "linter": "eslint", - "tsConfig": ["libs/remix-ui/vertical-icons-panel/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "!libs/remix-ui/vertical-icons-panel/**/*"] + "tsConfig": [ + "libs/remix-ui/vertical-icons-panel/tsconfig.lib.json" + ], + "exclude": [ + "**/node_modules/**", + "!libs/remix-ui/vertical-icons-panel/**/*" + ] } } } @@ -1136,17 +1139,19 @@ } } } - }, + }, "solidity-unit-testing": { "root": "libs/remix-ui/solidity-unit-testing", - "sourceRoot": "libs/remix-ui/solidity-unit-testing/src", + "sourceRoot": "libs/remix-ui/solidity-unit-testing/src", "projectType": "library", "architect": { "lint": { "builder": "@nrwl/linter:lint", "options": { "linter": "eslint", - "tsConfig": ["libs/remix-ui/solidity-unit-testing/tsconfig.lib.json"], + "tsConfig": [ + "libs/remix-ui/solidity-unit-testing/tsconfig.lib.json" + ], "exclude": [ "**/node_modules/**", "!libs/remix-ui/solidity-unit-testing/**/*" @@ -1165,7 +1170,10 @@ "options": { "linter": "eslint", "tsConfig": ["libs/remix-ui/editor-context-view/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "!libs/remix-ui/editor-context-view/**/*"] + "exclude": [ + "**/node_modules/**", + "!libs/remix-ui/editor-context-view/**/*" + ] } } } @@ -1180,7 +1188,11 @@ "options": { "linter": "eslint", "tsConfig": ["libs/remix-ui/run-tab/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "libs/remix-ui/run-tab/**/*.d.ts", "!libs/remix-ui/run-tab/**/*"] + "exclude": [ + "**/node_modules/**", + "libs/remix-ui/run-tab/**/*.d.ts", + "!libs/remix-ui/run-tab/**/*" + ] } } } @@ -1195,7 +1207,49 @@ "options": { "linter": "eslint", "tsConfig": ["libs/remix-ui/permission-handler/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "libs/remix-ui/permission-handler/**/*.d.ts", "!libs/remix-ui/permission-handler/**/*"] + "exclude": [ + "**/node_modules/**", + "libs/remix-ui/permission-handler/**/*.d.ts", + "!libs/remix-ui/permission-handler/**/*" + ] + } + } + } + }, + "remix-ws-templates": { + "root": "libs/remix-ws-templates", + "sourceRoot": "libs/remix-ws-templates/src", + "projectType": "library", + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "config": "libs/remix-ws-templates/.eslintrc", + "tsConfig": ["libs/remix-ws-templates/tsconfig.lib.json"], + "exclude": ["**/node_modules/**"] + } + }, + "build": { + "builder": "@nrwl/node:package", + "options": { + "outputPath": "dist/libs/remix-ws-templates", + "tsConfig": "libs/remix-ws-templates/tsconfig.lib.json", + "packageJson": "libs/remix-ws-templates/package.json", + "main": "libs/remix-ws-templates/src/index.ts", + "assets": [ + { + "glob": "templates/**/*", + "ignore": ["templates/**/*/index.ts"], + "input": "libs/remix-ws-templates/src/", + "output": "src/" + }, + { + "glob": "*.md", + "input": "libs/remix-ws-templates/", + "output": "/" + } + ] } } }