From 31fe40036978397ca6aba91f14026b48c1ed2d3a Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 13:54:24 +0545 Subject: [PATCH 1/9] feat: add on boot process initialization config during deployment --- src/cli.ts | 13 +++-- src/lib/deploy.ts | 122 ++++++++++++++++++++++++++------------------- src/types/index.ts | 11 +++- 3 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index e5d8a24..a5a4916 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -43,14 +43,15 @@ function getPackageJson() { function logDeploymentDetails(result: DeployResult) { const { messageId, processId, isNewProcess, configName } = result; const processUrl = chalk.green(`${aoExplorerUrl}/#/entity/${processId}`); - const messageUrl = chalk.green(`${aoExplorerUrl}/#/message/${messageId}`); const logger = Logger.init(configName); - console.log(""); if (isNewProcess) { logger.log(`Deployed Process: ${processUrl}`); } - logger.log(`Deployment Message: ${messageUrl}`); + if (messageId) { + const messageUrl = chalk.green(`${aoExplorerUrl}/#/message/${messageId}`); + logger.log(`Deployment Message: ${messageUrl}`); + } } function logBundleDetails(result: BundleResult) { @@ -151,7 +152,8 @@ program parseToInt, 3000 ) - .option("--minify", "Reduce the size of the contract before deployment."); + .option("--minify", "Reduce the size of the contract before deployment.") + .option("--on-boot", "Load contract at process startup."); program.parse(process.argv); @@ -196,7 +198,8 @@ async function deploymentHandler() { cuUrl: options.cuUrl, muUrl: options.muUrl }, - minify: options.minify + minify: options.minify, + onBoot: options.onBoot }); logDeploymentDetails(result); } else { diff --git a/src/lib/deploy.ts b/src/lib/deploy.ts index d4d7ff5..56a8690 100644 --- a/src/lib/deploy.ts +++ b/src/lib/deploy.ts @@ -131,7 +131,8 @@ export class DeploymentsManager { sqlite, services, minify, - contractTransformer + contractTransformer, + onBoot }: DeployConfig): Promise { name = name || "default"; configName = configName || name; @@ -168,7 +169,20 @@ export class DeploymentsManager { const isNewProcess = !processId; - if (!processId) { + const loader = new LuaProjectLoader(configName, luaPath); + let contractSrc = await loader.loadContract(contractPath); + + if (contractTransformer && typeof contractTransformer === "function") { + logger.log("Transforming contract...", false, false); + contractSrc = await contractTransformer(contractSrc); + } + + if (minify) { + logger.log("Minifying contract...", false, false); + contractSrc = await loader.minifyContract(contractSrc); + } + + if (isNewProcess) { logger.log("Spawning new process...", false, true); tags = Array.isArray(tags) ? tags : []; tags = [ @@ -179,6 +193,11 @@ export class DeploymentsManager { ...tags ]; + if (onBoot) { + logger.log(`Deploying: ${contractPath}`, false, true); + tags = [...tags, { name: "On-Boot", value: "Data" }]; + } + if (cron) { this.#validateCron(cron); tags = [ @@ -188,7 +207,7 @@ export class DeploymentsManager { ]; } - const data = "1984"; + const data = onBoot ? contractSrc : "1984"; processId = await retryWithDelay( () => aoInstance.spawn({ module, signer, tags, data, scheduler }), retry.count, @@ -196,64 +215,65 @@ export class DeploymentsManager { ); await pollForProcessSpawn({ processId }); - } else { - logger.log("Updating existing process...", false, true); - } - const loader = new LuaProjectLoader(configName, luaPath); - let contractSrc = await loader.loadContract(contractPath); - - if (contractTransformer && typeof contractTransformer === "function") { - logger.log("Transforming contract...", false, false); - contractSrc = await contractTransformer(contractSrc); - } - - if (minify) { - logger.log("Minifying contract...", false, false); - contractSrc = await loader.minifyContract(contractSrc); + if (onBoot) { + return { name, processId, isNewProcess, configName }; + } } - logger.log(`Deploying: ${contractPath}`, false, true); - // Load contract to process - const messageId = await retryWithDelay( - async () => - aoInstance.message({ - process: processId, - tags: [{ name: "Action", value: "Eval" }], - data: contractSrc, - signer - }), - retry.count, - retry.delay - ); + let messageId: string; + if (!onBoot || !isNewProcess) { + if (!isNewProcess) { + logger.log("Updating existing process...", false, true); + } + logger.log(`Deploying: ${contractPath}`, false, true); + // Load contract to process + messageId = await retryWithDelay( + async () => + aoInstance.message({ + process: processId!, + tags: [{ name: "Action", value: "Eval" }], + data: contractSrc, + signer + }), + retry.count, + retry.delay + ); - const { Output, Error: error } = await retryWithDelay( - async () => - aoInstance.result({ - process: processId, - message: messageId - }), - retry.count, - retry.delay - ); + const { Output, Error: error } = await retryWithDelay( + async () => + aoInstance.result({ + process: processId!, + message: messageId + }), + retry.count, + retry.delay + ); - let errorMessage = null; + let errorMessage = null; - if (Output?.data?.output) { - errorMessage = Output.data.output; - } else if (error) { - if (typeof error === "object" && Object.keys(error).length > 0) { - errorMessage = JSON.stringify(error); - } else { - errorMessage = String(error); + if (Output?.data?.output) { + errorMessage = Output.data.output; + } else if (error) { + if (typeof error === "object" && Object.keys(error).length > 0) { + errorMessage = JSON.stringify(error); + } else { + errorMessage = String(error); + } } - } - if (errorMessage) { - throw new Error(errorMessage); + if (errorMessage) { + throw new Error(errorMessage); + } } - return { name, processId, messageId, isNewProcess, configName }; + return { + name, + processId: processId!, + messageId: messageId!, + isNewProcess, + configName + }; } /** diff --git a/src/types/index.ts b/src/types/index.ts index 7b1fc98..73cf6ea 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -132,6 +132,15 @@ export interface DeployConfig { * ``` */ contractTransformer?: (source: string) => string | Promise; + + /** + * Enable AOS On-Boot loading to execute contract at process startup. + * Sets "On-Boot=Data" tag during deployment. + * CLI: --on-boot + * @see https://github.com/permaweb/aos?tab=readme-ov-file#boot-loading + * @default false + */ + onBoot?: boolean; } export type Config = Record; @@ -139,7 +148,7 @@ export type Config = Record; export interface DeployResult { name: string; configName: string; - messageId: string; + messageId?: string; processId: string; isNewProcess: boolean; } From 57f66c61a231f6be694219852850e8f8c1ed55ba Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 14:17:05 +0545 Subject: [PATCH 2/9] refactor: update readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index d51964e..1cd8fee 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ Options: --retry-count [count] Number of retries for deploying contract. (default: 10) --retry-delay [delay] Delay between retries in milliseconds. (default: 3000) --minify Reduce the size of the contract before deployment. (default: false) + --on-boot Load contract at process startup. (default: false) -h, --help display help for command ``` @@ -108,6 +109,12 @@ ao-deploy process.lua -n tictactoe -w wallet.json --tags name1:value1 name2:valu ao-deploy process.lua -n tictactoe -w wallet.json --tags name1:value1 name2:value2 --minify ``` +#### Example: Deploy contract with on-boot + +```sh +ao-deploy process.lua -n tictactoe -w wallet.json --tags name1:value1 name2:value2 --on-boot +``` + #### Example: Deploy contracts with configuration Here is an example using a deployment configuration: From 3c937d585ab6d70f0e287935f292e6e6b81f2055 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 14:27:41 +0545 Subject: [PATCH 3/9] refactor: update readme & config --- README.md | 4 ++-- src/cli.ts | 2 +- src/lib/config.ts | 9 +++++++++ src/types/index.ts | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1cd8fee..57cabf7 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Arguments: Options: -V, --version output the version number -n, --name [name] Specify the process name. (default: "default") - -w, --wallet [wallet] Path to the wallet JWK file. + -w, --wallet [wallet] Path to the wallet JWK file. (Autogenerated if not passed) -l, --lua-path [luaPath] Specify the Lua modules path seperated by semicolon. -d, --deploy [deploy] List of deployment configuration names, separated by commas. -b, --build [build] List of deployment configuration names, separated by commas. @@ -90,7 +90,7 @@ Options: --retry-count [count] Number of retries for deploying contract. (default: 10) --retry-delay [delay] Delay between retries in milliseconds. (default: 3000) --minify Reduce the size of the contract before deployment. (default: false) - --on-boot Load contract at process startup. (default: false) + --on-boot Load contract when process is spawned. (default: false) -h, --help display help for command ``` diff --git a/src/cli.ts b/src/cli.ts index a5a4916..758917f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -153,7 +153,7 @@ program 3000 ) .option("--minify", "Reduce the size of the contract before deployment.") - .option("--on-boot", "Load contract at process startup."); + .option("--on-boot", "Load contract when process is spawned."); program.parse(process.argv); diff --git a/src/lib/config.ts b/src/lib/config.ts index 15eb325..406300f 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -203,6 +203,15 @@ export class ConfigManager { ); } + if ( + deployConfig.onBoot !== undefined && + typeof deployConfig.onBoot !== "boolean" + ) { + throw new Error( + `Invalid onBoot value in configuration for "${name}": ${jsonStringify(deployConfig.onBoot)}` + ); + } + if ( deployConfig.contractTransformer !== undefined && typeof deployConfig.contractTransformer !== "function" diff --git a/src/types/index.ts b/src/types/index.ts index 73cf6ea..27aecbd 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -134,7 +134,7 @@ export interface DeployConfig { contractTransformer?: (source: string) => string | Promise; /** - * Enable AOS On-Boot loading to execute contract at process startup. + * Enable AOS On-Boot loading to load contract when process is spawned. * Sets "On-Boot=Data" tag during deployment. * CLI: --on-boot * @see https://github.com/permaweb/aos?tab=readme-ov-file#boot-loading From f2753315cd484e7e504ab443fcc02761b3d3198b Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 14:33:45 +0545 Subject: [PATCH 4/9] refactor: update readme --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 57cabf7..0090192 100644 --- a/README.md +++ b/README.md @@ -264,10 +264,11 @@ async function main() { } }); const processUrl = `https://www.ao.link/#/entity/${processId}`; - const messageUrl = `https://www.ao.link/#/message/${messageId}`; - console.log( - `\nDeployed Process: ${processUrl} \nDeployment Message: ${messageUrl}` - ); + console.log(`Deployed Process: ${processUrl}`); + if (messageId) { + const messageUrl = `https://www.ao.link/#/message/${messageId}`; + console.log(`Deployment Message: ${messageUrl}`); + } } catch (error: any) { console.log( `Deployment failed!: ${error?.message ?? "Failed to deploy contract!"}\n` @@ -294,6 +295,7 @@ The `deployContract` function accepts the following parameters within the Deploy - `processId` (optional): The process id of existing process. - `minify` (optional): Reduce the size of the contract before deployment. - `contractTransformer` (optional): Custom function to transform source code before deployment. +- `onBoot` (optional): Load contract when process is spawned. #### Example: deployContracts @@ -333,10 +335,11 @@ async function main() { if (result.status === "fulfilled") { const { processId, messageId } = result.value; const processUrl = `https://www.ao.link/#/entity/${processId}`; - const messageUrl = `https://www.ao.link/#/message/${messageId}`; - console.log( - `\nDeployed Process: ${processUrl} \nDeployment Message: ${messageUrl}` - ); + console.log(`Deployed Process: ${processUrl}`); + if (messageId) { + const messageUrl = `https://www.ao.link/#/message/${messageId}`; + console.log(`Deployment Message: ${messageUrl}`); + } } else { console.log(`Failed to deploy contract!: ${result.reason}\n`); } From 478d0e99ac7759b0d114b880c95debf7f1e62078 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 16:50:20 +0545 Subject: [PATCH 5/9] feat: add silent to config to silent console logs --- hello.lua | 0 src/lib/deploy.ts | 7 ++++--- src/lib/loader.ts | 4 ++-- src/lib/logger.ts | 13 ++++++++----- src/types/index.ts | 6 ++++++ 5 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 hello.lua diff --git a/hello.lua b/hello.lua new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/deploy.ts b/src/lib/deploy.ts index 56a8690..3672439 100644 --- a/src/lib/deploy.ts +++ b/src/lib/deploy.ts @@ -132,7 +132,8 @@ export class DeploymentsManager { services, minify, contractTransformer, - onBoot + onBoot, + silent = false }: DeployConfig): Promise { name = name || "default"; configName = configName || name; @@ -141,7 +142,7 @@ export class DeploymentsManager { delay: parseToInt(retry?.delay, 3000) }; - const logger = new Logger(configName); + const logger = new Logger(configName, silent); const aosConfig = await this.#getAosConfig(); module = isArweaveAddress(module) ? module! @@ -169,7 +170,7 @@ export class DeploymentsManager { const isNewProcess = !processId; - const loader = new LuaProjectLoader(configName, luaPath); + const loader = new LuaProjectLoader(configName, luaPath, silent); let contractSrc = await loader.loadContract(contractPath); if (contractTransformer && typeof contractTransformer === "function") { diff --git a/src/lib/loader.ts b/src/lib/loader.ts index 2935ba1..fcf1100 100644 --- a/src/lib/loader.ts +++ b/src/lib/loader.ts @@ -26,9 +26,9 @@ export class LuaProjectLoader { #luaPath: string; #logger: Logger; - constructor(name: string, luaPath?: string) { + constructor(name: string, luaPath?: string, silent: boolean = false) { this.#luaPath = luaPath || ""; - this.#logger = Logger.init(name); + this.#logger = Logger.init(name, silent); } async #fileExists(path: string): Promise { diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 33635ae..7874660 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -4,23 +4,26 @@ import { APP_NAME } from "./constants"; export class Logger { static #instances: Map = new Map(); #name: string; + #silent: boolean = false; - constructor(name: string) { + constructor(name: string, silent: boolean = false) { this.#name = name; + this.#silent = silent; } - static #getInstance(name: string): Logger { + static #getInstance(name: string, silent: boolean = false): Logger { if (!Logger.#instances.has(name)) { - Logger.#instances.set(name, new Logger(name)); + Logger.#instances.set(name, new Logger(name, silent)); } return Logger.#instances.get(name)!; } - static init(name: string) { - return this.#getInstance(name); + static init(name: string, silent: boolean = false) { + return this.#getInstance(name, silent); } #logMessage(message: string, prefixNewLine: boolean, suffixNewLine: boolean) { + if (this.#silent) return; const prefix = prefixNewLine ? "\n" : ""; const suffix = suffixNewLine ? "\n" : ""; console.log(`${prefix}${message}${suffix}`); diff --git a/src/types/index.ts b/src/types/index.ts index 27aecbd..54ece48 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -141,6 +141,12 @@ export interface DeployConfig { * @default false */ onBoot?: boolean; + + /** + * Disable logging to console + * @default false + */ + silent?: boolean; } export type Config = Record; From 7af8a8451017c8391c751b0ea6872142196cba38 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 16:52:04 +0545 Subject: [PATCH 6/9] chore: Update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0090192..c8b92d4 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,8 @@ The `deployContract` function accepts the following parameters within the Deploy - `processId` (optional): The process id of existing process. - `minify` (optional): Reduce the size of the contract before deployment. - `contractTransformer` (optional): Custom function to transform source code before deployment. -- `onBoot` (optional): Load contract when process is spawned. +- `onBoot` (optional): Load contract when process is spawned. (default: false) +- `silent` (optional): Disable logging to console. (default: false) #### Example: deployContracts From 54e58338f44b34b9bdc17af9c41339b482941e0d Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 17:17:43 +0545 Subject: [PATCH 7/9] refactor: update logger --- src/lib/logger.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 7874660..743d0a6 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -4,7 +4,7 @@ import { APP_NAME } from "./constants"; export class Logger { static #instances: Map = new Map(); #name: string; - #silent: boolean = false; + #silent: boolean; constructor(name: string, silent: boolean = false) { this.#name = name; @@ -57,27 +57,38 @@ export class Logger { name: string, message: string, prefixNewLine = false, - suffixNewLine = false + suffixNewLine = false, + silent = false ) { - this.#getInstance(name).log(message, prefixNewLine, suffixNewLine); + this.#getInstance(name, silent).log(message, prefixNewLine, suffixNewLine); } static success( name: string, message: string, prefixNewLine = false, - suffixNewLine = false + suffixNewLine = false, + silent = false ) { - this.#getInstance(name).success(message, prefixNewLine, suffixNewLine); + this.#getInstance(name, silent).success( + message, + prefixNewLine, + suffixNewLine + ); } static error( name: string, message: string, prefixNewLine = false, - suffixNewLine = false + suffixNewLine = false, + silent = false ) { - this.#getInstance(name).error(message, prefixNewLine, suffixNewLine); + this.#getInstance(name, silent).error( + message, + prefixNewLine, + suffixNewLine + ); } } From b9daa90df8cd455ed65a476f07607b12fa576caf Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 18:00:04 +0545 Subject: [PATCH 8/9] fix: update logger --- src/lib/loader.ts | 20 +++++++++++--------- src/lib/logger.ts | 10 +++++++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/lib/loader.ts b/src/lib/loader.ts index fcf1100..ffc33fd 100644 --- a/src/lib/loader.ts +++ b/src/lib/loader.ts @@ -193,15 +193,17 @@ export class LuaProjectLoader { false, true ); - console.log( - chalk.dim( - createFileTree([ - ...projectStructure.map((m) => m.path), - `${filePath} ${chalk.reset(chalk.bgGreen(" MAIN "))}` - ]) - ) - ); - console.log(""); + if (!this.#logger.silent) { + console.log( + chalk.dim( + createFileTree([ + ...projectStructure.map((m) => m.path), + `${filePath} ${chalk.reset(chalk.bgGreen(" MAIN "))}` + ]) + ) + ); + console.log(""); + } } return line.trim(); diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 743d0a6..14fcab6 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -11,11 +11,15 @@ export class Logger { this.#silent = silent; } + get silent() { + return this.#silent; + } + static #getInstance(name: string, silent: boolean = false): Logger { - if (!Logger.#instances.has(name)) { - Logger.#instances.set(name, new Logger(name, silent)); + if (!Logger.#instances.has(`${name}-${silent}`)) { + Logger.#instances.set(`${name}-${silent}`, new Logger(name, silent)); } - return Logger.#instances.get(name)!; + return Logger.#instances.get(`${name}-${silent}`)!; } static init(name: string, silent: boolean = false) { From 604dd6ecdec4aa6b9adbf45ed7ca927567f58994 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sun, 12 Jan 2025 18:16:11 +0545 Subject: [PATCH 9/9] refactor: update config --- src/lib/config.ts | 48 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index 406300f..6cfd9a4 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -128,11 +128,17 @@ export class ConfigManager { "wallet", "outDir" ]; + const optionalBooleanProps: (keyof DeployConfig)[] = [ + "sqlite", + "silent", + "minify", + "onBoot" + ]; optionalAddressProps.forEach((prop) => { if (deployConfig[prop] && !isArweaveAddress(deployConfig[prop])) { throw new Error( - `Invalid optional property "${prop}" in configuration for "${keyName}": ${jsonStringify(deployConfig[prop])}` + `Invalid "${prop}" value in configuration for "${keyName}": ${jsonStringify(deployConfig[prop])}` ); } }); @@ -140,7 +146,18 @@ export class ConfigManager { optionalStringProps.forEach((prop) => { if (deployConfig[prop] && !this.#isString(deployConfig[prop])) { throw new Error( - `Invalid optional property "${prop}" in configuration for "${keyName}": ${jsonStringify(deployConfig[prop])}` + `Invalid "${prop}" value in configuration for "${keyName}": ${jsonStringify(deployConfig[prop])}` + ); + } + }); + + optionalBooleanProps.forEach((prop) => { + if ( + deployConfig[prop] !== undefined && + typeof deployConfig[prop] !== "boolean" + ) { + throw new Error( + `Invalid "${prop}" value in configuration for "${keyName}": ${jsonStringify(deployConfig[prop])}` ); } }); @@ -185,33 +202,6 @@ export class ConfigManager { ); } - if ( - deployConfig.sqlite !== undefined && - typeof deployConfig.sqlite !== "boolean" - ) { - throw new Error( - `Invalid sqlite value in configuration for "${name}": ${jsonStringify(deployConfig.sqlite)}` - ); - } - - if ( - deployConfig.minify !== undefined && - typeof deployConfig.minify !== "boolean" - ) { - throw new Error( - `Invalid minify value in configuration for "${name}": ${jsonStringify(deployConfig.minify)}` - ); - } - - if ( - deployConfig.onBoot !== undefined && - typeof deployConfig.onBoot !== "boolean" - ) { - throw new Error( - `Invalid onBoot value in configuration for "${name}": ${jsonStringify(deployConfig.onBoot)}` - ); - } - if ( deployConfig.contractTransformer !== undefined && typeof deployConfig.contractTransformer !== "function"