Skip to content

Commit

Permalink
fix: handle deployment selection inconsistencies (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulyadav-57 committed Jan 14, 2025
1 parent 951f2db commit 35788d4
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 47 deletions.
135 changes: 88 additions & 47 deletions src/components/workspace/BuildProject/BuildProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -91,34 +94,24 @@ const BuildProject: FC<Props> = ({ 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();

const { deployContract } = useContractAction();

const contractsToDeploy = () => {
return projectFiles
.filter((f) => {
const _fileExtension = getFileExtension(f.name || '');
return (
f.path.startsWith(`${activeProject?.path}/dist`) &&
['abi'].includes(_fileExtension as string)
);
})
.map((f) => {
return {
id: f.id,
name: f.name
.replace('.abi', '')
.replace('tact_', '')
.replace('func_', ''),
path: f.path,
};
});
if (!activeProject?.path || !activeProject.language) {
return [];
}
return filterABIFiles(
projectFiles,
activeProject.path,
activeProject.language,
);
};

const cellBuilder = (info: string) => {
Expand Down Expand Up @@ -276,8 +269,17 @@ const BuildProject: FC<Props> = ({ 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.');
}
Expand Down Expand Up @@ -346,25 +348,29 @@ const BuildProject: FC<Props> = ({ 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 {
contractScript = (await getFile(contractScriptPath)) as string;
} 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,
Expand All @@ -376,7 +382,7 @@ const BuildProject: FC<Props> = ({ 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');
Expand Down Expand Up @@ -406,9 +412,12 @@ const BuildProject: FC<Props> = ({ 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));
Expand All @@ -429,8 +438,8 @@ const BuildProject: FC<Props> = ({ 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,
},
'*',
Expand Down Expand Up @@ -519,22 +528,13 @@ const BuildProject: FC<Props> = ({ projectId, contract, updateContract }) => {
setEnvironment(network);
};

const updateSelectedContract = (contract: string) => {
const updateSelectedContract = (contract: string | undefined) => {
setSelectedContract(contract);
updateProjectSetting({
selectedContract: contract,
} 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 {')
Expand All @@ -547,7 +547,11 @@ const BuildProject: FC<Props> = ({ 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) {
Expand Down Expand Up @@ -589,7 +593,10 @@ const BuildProject: FC<Props> = ({ projectId, contract, updateContract }) => {
);
if (!output) return;

const contractName = extractContractName(selectedContract);
const contractName = extractContractName(
selectedContract,
activeProject.path!,
);

const _code = `async function main() {
${output.finalJSoutput}
Expand Down Expand Up @@ -620,6 +627,34 @@ const BuildProject: FC<Props> = ({ projectId, contract, updateContract }) => {
}
};

const getSelectedContractABIPath = () => {
const previousSelectedABIPath = activeProject?.selectedContract;
if (!previousSelectedABIPath) return;

const correspondingScriptPath = replaceFileExtension(
previousSelectedABIPath,
'.abi',
'.ts',
);

let contractABIPath: string | undefined = previousSelectedABIPath;

// in case of Tact, verify the presence of the corresponding contract wrapper script
if (activeProject.language === 'tact') {
const scriptFile = projectFiles.find(
(file) => file.path === correspondingScriptPath,
);
const hasValidScriptFile =
scriptFile &&
projectFiles.find((file) => file.path === previousSelectedABIPath);

contractABIPath = hasValidScriptFile
? previousSelectedABIPath
: undefined;
}
return contractABIPath;
};

useEffect(() => {
updateABI().catch(() => {});
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -638,10 +673,16 @@ const BuildProject: FC<Props> = ({ projectId, contract, updateContract }) => {
if (activeProject?.network) {
setEnvironment(activeProject.network);
}
if (activeProject?.selectedContract) {
setSelectedContract(activeProject.selectedContract);
deployForm.setFieldsValue({ contract: activeProject.selectedContract });

const contractABIPath = getSelectedContractABIPath();
if (contractABIPath) {
deployForm.setFieldsValue({
contract: contractABIPath,
});

updateSelectedContract(contractABIPath);
}

const handler = (
event: MessageEvent<{
name: string;
Expand Down
24 changes: 24 additions & 0 deletions src/utility/contract.ts
Original file line number Diff line number Diff line change
@@ -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;
}
42 changes: 42 additions & 0 deletions src/utility/file.ts
Original file line number Diff line number Diff line change
@@ -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;
}
24 changes: 24 additions & 0 deletions src/utility/filePath.ts
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 16 additions & 0 deletions src/utility/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,19 @@ export function isIncludesTypeCellOrSlice(obj: Record<string, any>): 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;
}

0 comments on commit 35788d4

Please sign in to comment.