diff --git a/README.md b/README.md index 435e6211..28996f57 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ When running the console, you can type `dot` commands to instruct the console to | `.editor` | This command opens a simple cli editor that you can type on multiple lines, and then you can type `.done` or `.cancel` to exist editor mode. | | `.load` | This command allows you to load a lua source file from your local directory | | `.load-blueprint [name]` | This command will grab a lua file from the blueprints directory and load it into your process. | +| `.dryrun {tags}` | This command will allow you to perform dryrun. | | `.exit` | This command will exit you console, but you can also do `Ctrl-C` or `Ctrl-D` | ## License @@ -77,5 +78,5 @@ When running the console, you can type `dot` commands to instruct the console to The ao and aos codebases are offered under the BSL 1.1 license for the duration of the testnet period. After the testnet phase is over, the code will be made available under either a new -[evolutionary forking](https://arweave.medium.com/arweave-is-an-evolutionary-protocol-e072f5e69eaa) +[evolutionary forking](https://arweave.medium.com/arweave-is-an-evolutionary-protocol-e072f5e69eaa)s license, or a traditional OSS license (GPLv3/v2, MIT, etc). diff --git a/src/commands/dryrun.js b/src/commands/dryrun.js new file mode 100644 index 00000000..950b639b --- /dev/null +++ b/src/commands/dryrun.js @@ -0,0 +1,176 @@ +import { dryrun } from "@permaweb/aoconnect"; +import chalk from "chalk"; + +export async function performDryRun(input, pid, data, owner, id, anchor) { + await processInput(input, pid, data, owner, id, anchor); +} + +async function extractTags(input, pid) { + let tags = []; + + // Check if the input contains curly braces + if (!input.includes("{")) { + throw new Error("Tags not specified"); + } + + // Check if the input contains curly braces + if (input.includes("{")) { + try { + // Extracting content within curly braces + const content = input.match(/\{(.*?)\}/)[1]; + + const pairs = content.split(","); + + // Iterating through each key-value pair + pairs.forEach((pair) => { + // Check if the pair contains an equal sign + if (pair.includes("=")) { + // Splitting each pair by "=" to get key and value + const [key, value] = pair.split("=").map((item) => item.trim()); + // Normalize the value + let normalizedValue = value; + normalizedValue = normalizedValue.replace(/^['"](.*)['"]$/, "$1"); + + console.log("normalizedValue", normalizedValue); + + // Replace "ao.id" with `pid` + if (normalizedValue === "ao.id") { + normalizedValue = pid; + } + + //use extra quotes to use the "ao.id" word itself + if (normalizedValue === "'ao.id'" || normalizedValue === `ao.id"`) { + normalizedValue = "ao.id"; + } + + // Pushing key-value pair to tags array + tags.push({ name: key, value: normalizedValue }); + } else { + const action = pair.trim().replace(/^'|'$/g, ""); + tags.push({ name: "Action", value: action }); + } + }); + } catch (error) { + throw new Error("Invalid Syntax Usage"); + } + } else { + // If no curly braces, assume single word input, set it as Action + const action = input.trim().replace(/^'|'$/g, ""); + let normalizedAction = action; + normalizedAction = normalizedAction.replace(/^['"](.*)['"]$/, "$1"); + console.log(normalizedAction); + tags.push({ name: "Action", value: normalizedAction }); + } + + // Adding default Target if not already present + let targetIncluded = tags.some((tag) => tag.name === "Target"); + if (!targetIncluded) { + tags.unshift({ name: "Target", value: pid }); + } + + return tags; +} + +async function callDryRun(input, pid, data, owner, id, anchor) { + try { + const tags = await extractTags(input, pid); + // check for Pid and data in params + const customProcessId = getCustomProcessId(input, pid) || pid; + const customData = getCustomData(input, pid) || data; + + const dryrunParams = { + process: customProcessId, + tags, + ...(customData && { data: customData }), + ...(owner && { Owner: owner }), + ...(id && { Id: id }), + ...(anchor && { anchor: anchor }), + }; + + checkForVerboseFlag(input, dryrunParams); + + const result = await dryrun(dryrunParams); + return result; + } catch (error) { + console.error(chalk.red("Error Performing DryRun:", error.message)); + } +} + +function checkForVerboseFlag(input, dryrunParams) { + const regex = /(^|\s)-v($|\s)/; // Regular expression to match -v preceded and followed by whitespace or string boundary + + if (regex.test(input)) { + console.log("dryrunParams:", dryrunParams); + } +} + + +const getCustomProcessId = (input, pid) => { + const match = input.match(/-p=(["'])(.*?)\1/); + if (match) { + let customProcessParam = match[2]; + + // Replace "ao.id" with `pid` + if (customProcessParam === "ao.id") { + customProcessParam = pid; + } + + if (customProcessParam.length !== 43) { + throw new Error("Invalid Process Id"); + } + return customProcessParam; + } + return null; +}; + +const getCustomData = (input, pid) => { + try { + let match = input.match(/-d=(["'])(.*?)\1/); + if (match) { + if (match[2] === "ao.id") { + return pid; + } + + if (match[2] === "'ao.id'" || match[2] === `"ao.id"`) { + return "ao.id"; + } + + return match[2]; + } + } catch (error) { + throw new Error("Invalid Data Format"); + } + return null; +}; + +const getSpecificResult = (obj, path, defaultValue = undefined) => { + const travel = (regexp) => + String.prototype.split + .call(path, regexp) + .filter(Boolean) + .reduce( + (res, key) => (res !== null && res !== undefined ? res[key] : res), + obj, + ); + + const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/); + return result === undefined || result === obj ? defaultValue : result; +}; + +async function processInput(input, pid, data, owner, id, anchor) { + try { + const flagIndex = input.indexOf("-r"); + const flag = flagIndex !== -1 ? getFlag(input, flagIndex) : null; + const result = await callDryRun(input, pid, data, owner, id, anchor); + console.log(chalk.green("result:")); + flag ? console.log(getSpecificResult(result, flag)) : console.log(result); + } catch (error) { + console.error(chalk.red("Error Processing Input:", error.message)); + } +} + +const getFlag = (input, flagIndex) => { + const flagStart = flagIndex + 3; + const flagEnd = input.indexOf(" ", flagStart); + return input.slice(flagStart, flagEnd !== -1 ? flagEnd : undefined).trim(); +}; diff --git a/src/commands/dryrun.md b/src/commands/dryrun.md new file mode 100644 index 00000000..78ed6ce4 --- /dev/null +++ b/src/commands/dryrun.md @@ -0,0 +1,167 @@ +# README for Command: .dryrun + +### Syntax + + .dryrun <{Tags}> + +### Quick Examples + +- Replacing the Send command with `.dryrun`: + + .dryrun ({ Target = 'FgU-RiEaLuC__SHZnI9pSIa_ZI8o-8hUVG9nPJvs92k', Action = 'Balance' }) + +or + +- Using single word Action: + + .dryrun ({Balance}). + +**Note:** +- While using single words, the word inside the braces will be considered as Action ('Action'='Word'), and it takes your Process ID `ao.id` as both the Target and Process ID by default. +- Tags must be enclosed with curly braces `{}` surrounding the circle brackets with parentheses `()` is optional. + + + +## Flags: +These flags determine the output you get on your terminal. + +### TheFlags List: +- `-p` for specifying Process ID. +- `-d` for specifying data. +- `-r` for accessing the Result Object. +- `-v` for viewing the DryRun Parameterss + +### Usage: +- Flags can be used in combinations, and the order of flags can be interchangeable. +- If you don't specify any flags, it will default to the `result object` (`-r`). +- If you don't specify a `-p` Process ID, your Process ID will be used. +- If you don't specify any `-d`, no data will be passed. + + + + +## Tags: +The tags that will be passed to perform DryRun. + +### Usage: +- Tags must be enclosed with curly braces `{}`. Surrounding the circle brackets with parentheses `()` is optional. +- Single word tags must also be enclosed within curly braces `{}`. +- By default, the single word will be considered as Action. It takes your Process ID `ao.id` as both the Target and Process ID by default. + +### Using 'ao.id' +Using "ao.id" to mention Process ID will work fine. By default Process ID and Target ID will be 'ao.id' (your process id). so its better to leave eit without mentioning it. If you want to specify the "ao.id" word itself, then give it enclosed with a single quote enclosed with a double quote. + +Samples : + + # to get ao.id + + .dryrun {'Balance'} + + .dryrun ({ Target = ao.id, Action = 'Balance' }) + + .dryrun ({ Target = 'ao.id', Action = 'Balance' }) + + # to get 'ao.id' + + .dryrun ({ Target = "'ao.id'", Action = 'Balance' }) + + +## Examples: + +- Replacing the Send command with `.dryrun` will give you the Result Object: + + .dryrun ({ Target = 'FgU-RiEaLuC__SHZnI9pSIa_ZI8o-8hUVG9nPJvs92k', Action = 'Balance' }) + +- Using Single word Action: + + .dryrun ({Balance}) + + This will use "Balance" as Action and your Process ID as Target & Process ID: + + # .dryrun ({ Target = ao.id, Action = 'Balance' }) + + **Sample Result Object Output:** + + { + Messages: [ + { + Target: '1234', + Anchor: '00000000000000000000000000000917', + Tags: [Array] + } + ], + Spawns: [], + Output: [], + GasUsed: 466333831 + } + + This will give you the Result Overview. (More details will be available in the Message property.) + + +- Accessing the keys of the Result Object: + + .dryrun -r.Messages[0] ({Balance}) + + This will give the Message Object of index=0 in Messages Array in the result object. + + Example Output: + + { + Target: '1234', + Anchor: '00000000000000000000000000000917', + Tags: [ + { value: 'ao', name: 'Data-Protocol' }, + { value: 'ao.TN.1', name: 'Variant' }, + { value: 'Message', name: 'Type' }, + { + value: 'FgU-RiEaLuC__SHZnI9pSIa_ZI8o-8hUVG9nPJvs92k', + name: 'From-Process' + }, + { + value: '9afQ1PLf2mrshqCTZEzzJTR2gWaC9zNPnYgYEqg1Pt4', + name: 'From-Module' + }, + { value: '917', name: 'Ref_' }, + { value: '1e+14', name: 'Data' }, + { value: '1234', name: 'Target' }, + { value: 'PNTS', name: 'Ticker' }, + { value: '100000000000000', name: 'Balance' } + ] + } + + +- Sending data to the DryRun: + + .dryrun -d="test" ({Balance}) + + or + + .dryrun -d="test" ({ Target = 'FgU-RiEaLuC__SHZnI9pSIa_ZI8o-8hUVG9nPJvs92k', Action = 'Balance' }) + + or sending as Tag **(recommended method)**: + + .dryrun -d="test" ({ Target = 'FgU-RiEaLuC__SHZnI9pSIa_ZI8o-8hUVG9nPJvs92k', Action = 'Balance' , Data = 'test'}) + + +- Passing Process ID: + + .dryrun -p="FgU-RiEaLuC__SHZnI9pSIa_ZI8o-8hUVG9nPJvs92k" ({Balance}) + + +- Viewing DryRun Params : + + .dryrun -v {Message} + + Example Output : + + dryrunParams: { + process: 'FXYu2-c66N90yG2yqL0myafDgRTGONn0xn7xmZxEq7g', + tags: [ + { + name: 'Target', + value: 'FXYu2-c66N90yG2yqL0myafDgRTGONn0xn7xmZxEq7g' + }, + { name: 'Action', value: 'Message' } + ] + } + diff --git a/src/index.js b/src/index.js index 94cd11b3..d1ce33e3 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,7 @@ import { unmonitor } from './commands/unmonitor.js' import { loadBlueprint } from './commands/blueprints.js' import { help, replHelp } from './services/help.js' import { list } from './services/list.js' +import { performDryRun } from './commands/dryrun.js' const argv = minimist(process.argv.slice(2)) let luaData = "" @@ -269,6 +270,15 @@ if (!argv['watch']) { return; } + + if (!editorMode && line.startsWith('.dryrun')) { + let pid = id + let input = line + await performDryRun(input, pid) + rl.prompt(false); + return; + } + if (editorMode && line === ".done") { line = editorData editorData = "" diff --git a/src/services/help.js b/src/services/help.js index 76f4bce7..489d22ef 100644 --- a/src/services/help.js +++ b/src/services/help.js @@ -13,6 +13,7 @@ ${chalk.green('Commands:')} ${chalk.green('.monitor')} Starts monitoring cron messages for this Process ${chalk.green('.unmonitor')} Stops monitoring cron messages for this Process ${chalk.green('.editor')} Simple code editor for writing multi-line lua expressions + ${chalk.green('.dryrun')} To Perform dryrun ${chalk.green('.help')} Print this help screen ${chalk.green('.exit')} Quit console `)