From dcf6818279d26c0ccf6a29cae9856da451ed49c6 Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Wed, 13 Dec 2023 00:24:26 +0000 Subject: [PATCH] feat(dev-cli)!: add proper tags and sensible defaults on ao Module #197 --- dev-cli/container/src/node/package-lock.json | 4 +- dev-cli/container/src/node/package.json | 4 +- .../src/bin/{ao-source.js => ao-module.js} | 14 +-- .../src/bin/{ao-process.js => ao-spawn.js} | 6 +- dev-cli/container/src/node/src/defaults.js | 16 ++- dev-cli/container/src/node/src/main.js | 46 ++++++-- dev-cli/container/src/node/test/main.test.js | 111 +++++++++++++++--- dev-cli/src/commands/publish.js | 4 +- dev-cli/src/commands/spawn.js | 4 +- 9 files changed, 165 insertions(+), 44 deletions(-) rename dev-cli/container/src/node/src/bin/{ao-source.js => ao-module.js} (81%) rename dev-cli/container/src/node/src/bin/{ao-process.js => ao-spawn.js} (92%) diff --git a/dev-cli/container/src/node/package-lock.json b/dev-cli/container/src/node/package-lock.json index 145a5a8f2..9c84ceba6 100644 --- a/dev-cli/container/src/node/package-lock.json +++ b/dev-cli/container/src/node/package-lock.json @@ -14,8 +14,8 @@ "bin": { "ao-bundler-balance": "src/bin/bundler-balance.js", "ao-bundler-fund": "src/bin/bundler-fund.js", - "ao-process": "src/bin/ao-process.js", - "ao-source": "src/bin/ao-source.js" + "ao-module": "src/bin/ao-module.js", + "ao-spawn": "src/bin/ao-spawn.js" }, "engines": { "node": ">=18" diff --git a/dev-cli/container/src/node/package.json b/dev-cli/container/src/node/package.json index 714732310..615e34c0a 100644 --- a/dev-cli/container/src/node/package.json +++ b/dev-cli/container/src/node/package.json @@ -8,8 +8,8 @@ "bin": { "ao-bundler-balance": "src/bin/bundler-balance.js", "ao-bundler-fund": "src/bin/bundler-fund.js", - "ao-process": "src/bin/ao-process.js", - "ao-source": "src/bin/ao-source.js" + "ao-module": "src/bin/ao-module.js", + "ao-spawn": "src/bin/ao-spawn.js" }, "scripts": { "test": "node --test" diff --git a/dev-cli/container/src/node/src/bin/ao-source.js b/dev-cli/container/src/node/src/bin/ao-module.js similarity index 81% rename from dev-cli/container/src/node/src/bin/ao-source.js rename to dev-cli/container/src/node/src/bin/ao-module.js index a48f49963..8c7f49069 100755 --- a/dev-cli/container/src/node/src/bin/ao-source.js +++ b/dev-cli/container/src/node/src/bin/ao-module.js @@ -2,11 +2,11 @@ import fs from 'node:fs' -import { ArtifactNotFoundError, BundlerHostNotSupportedError, WalletNotFoundError, parseTags, uploadWith } from '../main.js' -import { AoContractSourceTags } from '../defaults.js' +import { ArtifactNotFoundError, BundlerHostNotSupportedError, WalletNotFoundError, parseTags, uploadModuleWith } from '../main.js' +import { AoModuleTags } from '../defaults.js' import { UPLOADERS } from '../clients.js' -const uploadHyperbeamContractSource = uploadWith({ +const uploadModule = uploadModuleWith({ walletExists: async (path) => fs.existsSync(path), /** * implement to check if single file exists @@ -35,14 +35,14 @@ const uploadHyperbeamContractSource = uploadWith({ */ Promise.resolve() .then(() => - uploadHyperbeamContractSource({ + uploadModule({ walletPath: process.env.WALLET_PATH, - artifactPath: process.env.CONTRACT_WASM_PATH, + artifactPath: process.env.MODULE_WASM_PATH, to: process.env.BUNDLER_HOST, tags: [ ...parseTags(process.env.TAGS || ''), // Add the proper tags for ao contract source - ...AoContractSourceTags + ...AoModuleTags ] }) ) @@ -55,7 +55,7 @@ Promise.resolve() return process.exit(1) } case ArtifactNotFoundError.code: { - console.error('Contract Wasm source not found at the specified path. Make sure to provide the path to your built Wasm') + console.error('Module source not found at the specified path. Make sure to provide the path to your built Wasm') return process.exit(1) } case BundlerHostNotSupportedError.code: { diff --git a/dev-cli/container/src/node/src/bin/ao-process.js b/dev-cli/container/src/node/src/bin/ao-spawn.js similarity index 92% rename from dev-cli/container/src/node/src/bin/ao-process.js rename to dev-cli/container/src/node/src/bin/ao-spawn.js index 94a8bb3c9..bc59b39d9 100755 --- a/dev-cli/container/src/node/src/bin/ao-process.js +++ b/dev-cli/container/src/node/src/bin/ao-spawn.js @@ -14,9 +14,9 @@ const uploadAoProcess = spawnProcessWith({ /** * implement to create a contract using the ao SDK */ - create: async ({ src, tags, wallet }) => { + create: async ({ module, tags, wallet }) => { return spawnProcess({ - srcId: src, + moduleId: module, tags, signer: createDataItemSigner(wallet) }).then((contractId) => ({ contractId })) @@ -34,7 +34,7 @@ Promise.resolve() .then(() => uploadAoProcess({ walletPath: process.env.WALLET_PATH, - src: process.env.CONTRACT_SOURCE_TX, + module: process.env.MODULE_TX, tags: parseTags(process.env.TAGS || '') }) ) diff --git a/dev-cli/container/src/node/src/defaults.js b/dev-cli/container/src/node/src/defaults.js index e3044667e..c1f0eb98d 100644 --- a/dev-cli/container/src/node/src/defaults.js +++ b/dev-cli/container/src/node/src/defaults.js @@ -1,6 +1,14 @@ -export const AoContractSourceTags = [ - { name: 'Content-Type', value: 'application/wasm' }, - { name: 'Contract-Type', value: 'ao' } -] +export const DEFAULT_MODULE_FORMAT_TAG = { name: 'Module-Format', value: 'emscripten' } +export const DEFAULT_INPUT_ENCODING_TAG = { name: 'Input-Encoding', value: 'JSON-1' } +export const DEFAULT_OUTPUT_ENCODING_TAG = { name: 'Output-Encoding', value: 'JSON-1' } export const DEFAULT_BUNDLER_HOST = 'https://node2.irys.xyz' + +export const AoModuleTags = [ + { name: 'Data-Protocol', value: 'ao' }, + { name: 'Type', value: 'Module' }, + DEFAULT_MODULE_FORMAT_TAG, + DEFAULT_INPUT_ENCODING_TAG, + DEFAULT_OUTPUT_ENCODING_TAG, + { name: 'Content-Type', value: 'application/wasm' } +] diff --git a/dev-cli/container/src/node/src/main.js b/dev-cli/container/src/node/src/main.js index 5af43a4d8..1895aeedf 100644 --- a/dev-cli/container/src/node/src/main.js +++ b/dev-cli/container/src/node/src/main.js @@ -1,4 +1,4 @@ -import { DEFAULT_BUNDLER_HOST } from './defaults.js' +import { DEFAULT_BUNDLER_HOST, DEFAULT_INPUT_ENCODING_TAG, DEFAULT_MODULE_FORMAT_TAG, DEFAULT_OUTPUT_ENCODING_TAG } from './defaults.js' export class WalletNotFoundError extends Error { static code = 'WalletNotFound' @@ -59,6 +59,31 @@ export const parseTags = (tagsStr) => .map(([name, value]) => ({ name, value })) : [] // TODO: filter out dups? +export const useFirstTag = (name) => (tags = []) => { + let found = false + return tags.reduce( + (tags, tag) => { + if (tag.name === name) { + /** + * skip it + */ + if (found) return tags + /** + * set the flag + */ + found = true + } + + /** + * append and continue + */ + tags.push(tag) + return tags + }, + [] + ) +} + /** * A reusuable upload client to upload any artifact * to any destination that implements the api @@ -89,13 +114,18 @@ export const parseTags = (tagsStr) => * * @returns {Uploader} */ -export const uploadWith = +export const uploadModuleWith = ({ walletExists, artifactExists, readWallet, uploaders }) => - async ({ walletPath, artifactPath, to, ...rest }) => { + async ({ walletPath, artifactPath, to, tags, ...rest }) => { if (!(await walletExists(walletPath))) throw new WalletNotFoundError() if (!(await artifactExists(artifactPath))) throw new ArtifactNotFoundError() to = to || DEFAULT_BUNDLER_HOST + tags = [ + useFirstTag(DEFAULT_MODULE_FORMAT_TAG.name), + useFirstTag(DEFAULT_INPUT_ENCODING_TAG.name), + useFirstTag(DEFAULT_OUTPUT_ENCODING_TAG.name) + ].reduce((tags, fn) => fn(tags), tags) const bundlerHost = determineBundlerHost(to) if (!bundlerHost) throw new BundlerHostNotSupportedError() @@ -103,7 +133,7 @@ export const uploadWith = const upload = uploaders[bundlerHost] const wallet = await readWallet(walletPath) - const res = await upload({ path: artifactPath, to, wallet, ...rest }) + const res = await upload({ path: artifactPath, to, wallet, tags, ...rest }) return res.id } @@ -111,7 +141,7 @@ export const uploadWith = * Create a contract * * @callback Create - * @param {{ src: string, tags: Tag[], wallet: unknown }} args + * @param {{ module: string, tags: Tag[], wallet: unknown }} args * * @typedef CreateEnvironment * @property {WalletExists} walletExists @@ -122,7 +152,7 @@ export const uploadWith = * * @typedef SpawnProcessArgs * @property {string} walletPath - * @property {string} src + * @property {string} module * @property {Tag[]} tags * * @callback SpawnProcess @@ -133,12 +163,12 @@ export const uploadWith = */ export const spawnProcessWith = ({ walletExists, readWallet, create }) => - async ({ walletPath, src, tags }) => { + async ({ walletPath, module, tags }) => { if (!(await walletExists(walletPath))) throw new WalletNotFoundError() const wallet = await readWallet(walletPath) const res = await create({ - src, + module, tags, wallet }) diff --git a/dev-cli/container/src/node/test/main.test.js b/dev-cli/container/src/node/test/main.test.js index 02c469811..780624ba0 100644 --- a/dev-cli/container/src/node/test/main.test.js +++ b/dev-cli/container/src/node/test/main.test.js @@ -10,11 +10,11 @@ import { checkBalanceWith, spawnProcessWith, fundWith, - uploadWith + uploadModuleWith } from '../src/main.js' -import { DEFAULT_BUNDLER_HOST } from '../src/defaults.js' +import { AoModuleTags, DEFAULT_BUNDLER_HOST } from '../src/defaults.js' -describe('uploadWith', () => { +describe('uploadModuleWith', () => { const happy = { walletExists: async () => true, artifactExists: async () => ({ foo: 'bar' }), @@ -24,8 +24,91 @@ describe('uploadWith', () => { } } - it('should publish the artifact to arweave', async () => { - const upload = uploadWith(happy) + describe('should use the first tag', () => { + it('Module-Format', async () => { + const upload = uploadModuleWith({ + ...happy, + uploaders: { + [SUPPORTED_BUNDLERS.IRYS]: async (params) => { + /** + * custom tag, plus default tags, then only use + * the first Module-Format tag + */ + assert.equal(params.tags.length, 7) + return { params, id: '123' } + } + } + }) + + await upload({ + walletPath: '/path/to/wallet.json', + artifactPath: '/path/to/artifact.wasm', + to: DEFAULT_BUNDLER_HOST, + tags: [ + { name: 'foo', value: 'bar' }, + { name: 'Module-Format', value: 'wasi32' }, + ...AoModuleTags + ] + }) + }) + + it('Input-Encoding', async () => { + const upload = uploadModuleWith({ + ...happy, + uploaders: { + [SUPPORTED_BUNDLERS.IRYS]: async (params) => { + /** + * custom tag, plus default tags, then only use + * the first Input-Encoding tag + */ + assert.equal(params.tags.length, 7) + return { params, id: '123' } + } + } + }) + + await upload({ + walletPath: '/path/to/wallet.json', + artifactPath: '/path/to/artifact.wasm', + to: DEFAULT_BUNDLER_HOST, + tags: [ + { name: 'foo', value: 'bar' }, + { name: 'Input-Encoding', value: 'ANS-104' }, + ...AoModuleTags + ] + }) + }) + + it('Output-Encoding', async () => { + const upload = uploadModuleWith({ + ...happy, + uploaders: { + [SUPPORTED_BUNDLERS.IRYS]: async (params) => { + /** + * custom tag, plus default tags, then only use + * the first Input-Encoding tag + */ + assert.equal(params.tags.length, 7) + return { params, id: '123' } + } + } + }) + + await upload({ + walletPath: '/path/to/wallet.json', + artifactPath: '/path/to/artifact.wasm', + to: DEFAULT_BUNDLER_HOST, + tags: [ + { name: 'foo', value: 'bar' }, + { name: 'Output-Encoding', value: 'ANS-104' }, + ...AoModuleTags + ] + }) + }) + }) + + it('should publish the artifact', async () => { + const upload = uploadModuleWith(happy) const res = await upload({ walletPath: '/path/to/wallet.json', @@ -40,7 +123,7 @@ describe('uploadWith', () => { }) it('should default the "to" if not provided', async () => { - const upload = uploadWith({ + const upload = uploadModuleWith({ ...happy, uploaders: { [SUPPORTED_BUNDLERS.IRYS]: (params) => { @@ -68,7 +151,7 @@ describe('uploadWith', () => { }) it('should pass the correct args to upload', async () => { - const upload = uploadWith({ + const upload = uploadModuleWith({ ...happy, uploaders: { [SUPPORTED_BUNDLERS.IRYS]: (params) => { @@ -97,7 +180,7 @@ describe('uploadWith', () => { }) it('should throw if the wallet does not exist', async () => { - const upload = uploadWith({ ...happy, walletExists: async () => false }) + const upload = uploadModuleWith({ ...happy, walletExists: async () => false }) await upload({ walletPath: '/path/to/wallet.json', @@ -111,7 +194,7 @@ describe('uploadWith', () => { }) it('should throw if the artifact does not exist', async () => { - const upload = uploadWith({ ...happy, artifactExists: async () => false }) + const upload = uploadModuleWith({ ...happy, artifactExists: async () => false }) await upload({ walletPath: '/path/to/wallet.json', @@ -125,7 +208,7 @@ describe('uploadWith', () => { }) it('should throw if the bundler is not supported', async () => { - const upload = uploadWith(happy) + const upload = uploadModuleWith(happy) await upload({ walletPath: '/path/to/wallet.json', @@ -151,7 +234,7 @@ describe('createContractWith', () => { const res = await spawnProcess({ walletPath: '/path/to/wallet.json', - src: 'src-tx-123', + module: 'module-tx-123', tags: [ { name: 'foo', value: 'bar' } ] @@ -165,7 +248,7 @@ describe('createContractWith', () => { ...happy, create: (params) => { assert.deepStrictEqual(params, { - src: 'src-tx-123', + module: 'module-tx-123', tags: [ { name: 'foo', value: 'bar' } ], @@ -178,7 +261,7 @@ describe('createContractWith', () => { await spawnProcess({ walletPath: '/path/to/wallet.json', - src: 'src-tx-123', + module: 'module-tx-123', tags: [ { name: 'foo', value: 'bar' } ] @@ -193,7 +276,7 @@ describe('createContractWith', () => { await contract({ walletPath: '/path/to/wallet.json', - src: 'src-tx-123', + module: 'module-tx-123', tags: [ { name: 'foo', value: 'bar' } ] diff --git a/dev-cli/src/commands/publish.js b/dev-cli/src/commands/publish.js index 9360b748f..15ff834b2 100644 --- a/dev-cli/src/commands/publish.js +++ b/dev-cli/src/commands/publish.js @@ -38,7 +38,7 @@ function contractSourceArgs (contractWasmPath) { '-v', `${contractWasmSrc}:${contractWasmDest}`, '-e', - `CONTRACT_WASM_PATH=${contractWasmDest}` + `MODULE_WASM_PATH=${contractWasmDest}` ] } @@ -70,7 +70,7 @@ export async function publish ({ wallet, host, tag }, contractWasmPath) { ...cmdArgs, '-it', `p3rmaw3b/ao:${VERSION.IMAGE}`, - 'ao-source' + 'ao-module' ] }) await p.status() diff --git a/dev-cli/src/commands/spawn.js b/dev-cli/src/commands/spawn.js index e2614bd45..1154f5984 100644 --- a/dev-cli/src/commands/spawn.js +++ b/dev-cli/src/commands/spawn.js @@ -7,7 +7,7 @@ import { VERSION } from '../versions.js' function sourceArgs (src) { return [ '-e', - `CONTRACT_SOURCE_TX=${src}` + `MODULE_TX=${src}` ] } @@ -32,7 +32,7 @@ export async function spawn ({ wallet, tag, source }) { ...cmdArgs, '-it', `p3rmaw3b/ao:${VERSION.IMAGE}`, - 'ao-contract' + 'ao-spawn' ] }) await p.status()