From b29269e020f98f39c954b24dfd9f54a3be19c04c Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Wed, 20 Nov 2024 17:38:49 +0530 Subject: [PATCH 1/2] fix: handle deployment selection inconsistencies --- .../workspace/BuildProject/BuildProject.tsx | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/components/workspace/BuildProject/BuildProject.tsx b/src/components/workspace/BuildProject/BuildProject.tsx index 9d8a40d..cb34664 100644 --- a/src/components/workspace/BuildProject/BuildProject.tsx +++ b/src/components/workspace/BuildProject/BuildProject.tsx @@ -102,23 +102,29 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { const contractsToDeploy = () => { return projectFiles - .filter((f) => { - const _fileExtension = getFileExtension(f.name || ''); - return ( - f.path.startsWith(`${activeProject?.path}/dist`) && - ['abi'].includes(_fileExtension as string) + .filter((file) => { + const fileExtension = getFileExtension(file.name || ''); + const isAbiFile = + file.path.startsWith(`${activeProject?.path}/dist`) && + fileExtension === 'abi'; + + if (activeProject?.language === 'func') { + return isAbiFile; + } + + const hasTsFile = projectFiles.some( + (f) => f.path === file.path.replace('.abi', '.ts'), ); + return isAbiFile && hasTsFile; }) - .map((f) => { - return { - id: f.id, - name: f.name - .replace('.abi', '') - .replace('tact_', '') - .replace('func_', ''), - path: f.path, - }; - }); + .map((file) => ({ + id: file.id, + name: file.name + .replace('.abi', '') + .replace('tact_', '') + .replace('func_', ''), + path: file.path, + })); }; const cellBuilder = (info: string) => { @@ -519,7 +525,7 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { setEnvironment(network); }; - const updateSelectedContract = (contract: string) => { + const updateSelectedContract = (contract: string | undefined) => { setSelectedContract(contract); updateProjectSetting({ selectedContract: contract, @@ -638,9 +644,29 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { if (activeProject?.network) { setEnvironment(activeProject.network); } - if (activeProject?.selectedContract) { - setSelectedContract(activeProject.selectedContract); - deployForm.setFieldsValue({ contract: activeProject.selectedContract }); + const selectedABIPath = activeProject?.selectedContract; + + if (selectedABIPath) { + const correspondingScriptPath = selectedABIPath.replace('.abi', '.ts'); + + let _selectedContract: string | undefined = selectedABIPath; + + if (activeProject.language === 'tact') { + const scriptFile = projectFiles.find( + (file) => file.path === correspondingScriptPath, + ); + const hasValidScriptFile = + scriptFile && + projectFiles.find((file) => file.path === selectedABIPath); + + _selectedContract = hasValidScriptFile ? selectedABIPath : undefined; + } + + deployForm.setFieldsValue({ + contract: _selectedContract, + }); + + updateSelectedContract(_selectedContract); } const handler = ( event: MessageEvent<{ From 8b8b17b8358ffbd11d12d3bd5bec809ba1e6d986 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Fri, 10 Jan 2025 18:14:35 +0530 Subject: [PATCH 2/2] refactor: abi file filter, extractContractName and file manipulation --- .../workspace/BuildProject/BuildProject.tsx | 105 +++++++++--------- src/utility/contract.ts | 24 ++++ src/utility/file.ts | 42 +++++++ src/utility/filePath.ts | 24 ++++ src/utility/utils.ts | 16 +++ 5 files changed, 161 insertions(+), 50 deletions(-) create mode 100644 src/utility/contract.ts create mode 100644 src/utility/file.ts create mode 100644 src/utility/filePath.ts diff --git a/src/components/workspace/BuildProject/BuildProject.tsx b/src/components/workspace/BuildProject/BuildProject.tsx index cb34664..5c5e38e 100644 --- a/src/components/workspace/BuildProject/BuildProject.tsx +++ b/src/components/workspace/BuildProject/BuildProject.tsx @@ -14,9 +14,9 @@ import { Analytics } from '@/utility/analytics'; import { buildTs } from '@/utility/typescriptHelper'; import { delay, - getFileExtension, htmlToAnsi, isIncludesTypeCellOrSlice, + stripPrefix, tonHttpEndpoint, } from '@/utility/utils'; import { Network } from '@orbs-network/ton-access'; @@ -35,6 +35,9 @@ import { useFile } from '@/hooks'; import { useProject } from '@/hooks/projectV2.hooks'; import { useSettingAction } from '@/hooks/setting.hooks'; import { ABIParser, parseInputs } from '@/utility/abi'; +import { extractContractName } from '@/utility/contract'; +import { filterABIFiles } from '@/utility/file'; +import { replaceFileExtension } from '@/utility/filePath'; import { Maybe } from '@ton/core/dist/utils/maybe'; import { TonClient } from '@ton/ton'; import { useForm } from 'antd/lib/form/Form'; @@ -91,9 +94,9 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { const connectedWalletAddress = useTonAddress(); const { sandboxBlockchain } = globalWorkspace; - const tactVersion = packageJson.dependencies['@tact-lang/compiler'].replace( + const tactVersion = stripPrefix( + packageJson.dependencies['@tact-lang/compiler'], '^', - '', ); const [deployForm] = useForm(); @@ -101,30 +104,14 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { const { deployContract } = useContractAction(); const contractsToDeploy = () => { - return projectFiles - .filter((file) => { - const fileExtension = getFileExtension(file.name || ''); - const isAbiFile = - file.path.startsWith(`${activeProject?.path}/dist`) && - fileExtension === 'abi'; - - if (activeProject?.language === 'func') { - return isAbiFile; - } - - const hasTsFile = projectFiles.some( - (f) => f.path === file.path.replace('.abi', '.ts'), - ); - return isAbiFile && hasTsFile; - }) - .map((file) => ({ - id: file.id, - name: file.name - .replace('.abi', '') - .replace('tact_', '') - .replace('func_', ''), - path: file.path, - })); + if (!activeProject?.path || !activeProject.language) { + return []; + } + return filterABIFiles( + projectFiles, + activeProject.path, + activeProject.language, + ); }; const cellBuilder = (info: string) => { @@ -282,8 +269,17 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { const deploy = async () => { createLog(`Deploying contract ...`, 'info'); - const contractBOCPath = selectedContract?.replace('.abi', '.code.boc'); - const contractBOC = (await getFile(contractBOCPath!)) as string; + if (!selectedContract) { + createLog('Select a contract', 'error'); + return; + } + + const contractBOCPath = replaceFileExtension( + selectedContract, + '.abi', + '.code.boc', + ); + const contractBOC = (await getFile(contractBOCPath)) as string; if (!contractBOC) { throw new Error('Contract BOC is missing. Rebuild the contract.'); } @@ -352,10 +348,14 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { }; const createStateInitCell = async (initParams = '') => { - if (!selectedContract) { + if (!selectedContract || !activeProject?.path) { throw new Error('Please select contract'); } - const contractScriptPath = selectedContract.replace('.abi', '.ts'); + const contractScriptPath = replaceFileExtension( + selectedContract, + '.abi', + '.ts', + ); if (!cellBuilderRef.current?.contentWindow) return; let contractScript = ''; try { @@ -363,14 +363,14 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { } catch (error) { /* empty */ } - if (activeProject?.language === 'tact' && !contractScript) { + if (activeProject.language === 'tact' && !contractScript) { throw new Error('Contract script is missing. Rebuild the contract.'); } try { let jsOutout = []; - if (activeProject?.language == 'tact') { + if (activeProject.language == 'tact') { jsOutout = await buildTs( { 'tact.ts': contractScript, @@ -382,7 +382,7 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { let cellCode = ''; try { stateInitContent = (await getFile( - `${activeProject?.path}/stateInit.cell.ts`, + `${activeProject.path}/stateInit.cell.ts`, )) as string; } catch (error) { console.log('stateInit.cell.ts is missing'); @@ -412,9 +412,12 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { const finalJsoutput = fromJSModule((jsOutout as OutputChunk[])[0].code); - const contractName = extractContractName(selectedContract); + const contractName = extractContractName( + selectedContract, + activeProject.path, + ); - if (activeProject?.language == 'tact') { + if (activeProject.language == 'tact') { const _code = `async function main(initParams) { ${finalJsoutput} const contractInit = await ${contractName}.fromInit(...Object.values(initParams)); @@ -435,8 +438,8 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { name: 'ton-web-ide', type: 'state-init-data', code: finalJsoutput, - language: activeProject?.language, - contractName: activeProject?.contractName, + language: activeProject.language, + contractName: activeProject.contractName, initParams, }, '*', @@ -532,15 +535,6 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { } as ProjectSetting); }; - const extractContractName = (currentContractName: string) => { - return currentContractName - .replace(activeProject?.path + '/', '') - .replace('dist/', '') - .replace('.abi', '') - .replace('tact_', '') - .replace('func_', ''); - }; - const fromJSModule = (jsModuleCode: string) => { return jsModuleCode .replace(/^import\s+{/, 'const {') @@ -553,7 +547,11 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { language: 'tact' | 'func', supressErrors = false, ) => { - const contractScriptPath = currentContractName.replace('.abi', '.ts'); + const contractScriptPath = replaceFileExtension( + currentContractName, + '.abi', + '.ts', + ); const contractScript = (await getFile(contractScriptPath)) as string; if (language === 'tact' && !contractScript) { if (supressErrors) { @@ -595,7 +593,10 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { ); if (!output) return; - const contractName = extractContractName(selectedContract); + const contractName = extractContractName( + selectedContract, + activeProject.path!, + ); const _code = `async function main() { ${output.finalJSoutput} @@ -647,7 +648,11 @@ const BuildProject: FC = ({ projectId, contract, updateContract }) => { const selectedABIPath = activeProject?.selectedContract; if (selectedABIPath) { - const correspondingScriptPath = selectedABIPath.replace('.abi', '.ts'); + const correspondingScriptPath = replaceFileExtension( + selectedABIPath, + '.abi', + '.ts', + ); let _selectedContract: string | undefined = selectedABIPath; diff --git a/src/utility/contract.ts b/src/utility/contract.ts new file mode 100644 index 0000000..9b51a9c --- /dev/null +++ b/src/utility/contract.ts @@ -0,0 +1,24 @@ +import { relativePath } from './filePath'; +import { stripPrefix, stripSuffix } from './utils'; + +/** + * Extracts the contract name from a file path like: + * /projects/projectName/dist/func_contractName.abi + */ +export function extractContractName( + contractFilePath: string, + projectPath: string, +): string { + let filePath = relativePath(contractFilePath, projectPath); + + filePath = stripPrefix(filePath, 'dist/'); + + // Remove either 'tact_' or 'func_' from start, if present + filePath = stripPrefix(filePath, 'tact_'); + filePath = stripPrefix(filePath, 'func_'); + + // Remove extension + filePath = stripSuffix(filePath, '.abi'); + + return filePath; +} diff --git a/src/utility/file.ts b/src/utility/file.ts new file mode 100644 index 0000000..d7a3ded --- /dev/null +++ b/src/utility/file.ts @@ -0,0 +1,42 @@ +import { ContractLanguage, Tree } from '@/interfaces/workspace.interface'; +import { replaceFileExtension } from './filePath'; +import { getFileExtension, stripPrefix, stripSuffix } from './utils'; + +export function filterABIFiles( + files: Tree[], + basePath: string, + lang: ContractLanguage, +) { + return files + .filter((file) => { + const fileExtension = getFileExtension(file.name); + const isAbiFile = + file.path.startsWith(`${basePath}/dist`) && fileExtension === 'abi'; + + if (lang === 'func') { + return isAbiFile; + } + + // For tact we have to check if both ABI and It's wrapper TS file is present. + const hasTsFile = files.some( + (f) => f.path === replaceFileExtension(file.path, '.abi', '.ts'), + ); + return isAbiFile && hasTsFile; + }) + .map((file) => ({ + id: file.id, + name: cleanAbiFileName(file.name), + path: file.path, + })); +} + +/** + * A convenience function to remove .abi if at the end, + * and also remove 'tact_' or 'func_' prefixes if at the start. + */ +export function cleanAbiFileName(rawName: string): string { + let name = stripSuffix(rawName, '.abi'); + name = stripPrefix(name, 'tact_'); + name = stripPrefix(name, 'func_'); + return name; +} diff --git a/src/utility/filePath.ts b/src/utility/filePath.ts new file mode 100644 index 0000000..345dcbf --- /dev/null +++ b/src/utility/filePath.ts @@ -0,0 +1,24 @@ +import { stripPrefix } from './utils'; + +export function relativePath(fullPath: string, basePath: string): string { + let path = stripPrefix(fullPath, basePath); + + // If there's a leading slash (after removing basePath), remove it: + if (path.startsWith('/')) { + path = path.slice(1); + } + + return path; +} + +export function replaceFileExtension( + filePath: string, + oldExt: string, + newExt: string, +): string { + if (filePath.endsWith(oldExt)) { + return filePath.slice(0, -oldExt.length) + newExt; + } + // If the file doesn’t end with `oldExt`, return unchanged, or handle otherwise + return filePath; +} diff --git a/src/utility/utils.ts b/src/utility/utils.ts index 59c371d..7ce4508 100644 --- a/src/utility/utils.ts +++ b/src/utility/utils.ts @@ -230,3 +230,19 @@ export function isIncludesTypeCellOrSlice(obj: Record): boolean { } return false; } + +/** + * Removes a specified suffix from the input string if it's present. + * Otherwise, returns the input string unchanged. + */ +export function stripSuffix(input: string, suffix: string): string { + return input.endsWith(suffix) ? input.slice(0, -suffix.length) : input; +} + +/** + * Removes a specified prefix from the input string if it's present. + * Otherwise, returns the input string unchanged. + */ +export function stripPrefix(input: string, prefix: string): string { + return input.startsWith(prefix) ? input.slice(prefix.length) : input; +}