Skip to content

Commit

Permalink
feat(dev-cli)!: add proper tags and sensible defaults on ao Module #197
Browse files Browse the repository at this point in the history
  • Loading branch information
TillaTheHun0 committed Dec 13, 2023
1 parent 8f2110d commit dcf6818
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 44 deletions.
4 changes: 2 additions & 2 deletions dev-cli/container/src/node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dev-cli/container/src/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
]
})
)
Expand All @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }))
Expand All @@ -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 || '')
})
)
Expand Down
16 changes: 12 additions & 4 deletions dev-cli/container/src/node/src/defaults.js
Original file line number Diff line number Diff line change
@@ -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' }
]
46 changes: 38 additions & 8 deletions dev-cli/container/src/node/src/main.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -89,29 +114,34 @@ 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()

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
}

/**
* 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
Expand All @@ -122,7 +152,7 @@ export const uploadWith =
*
* @typedef SpawnProcessArgs
* @property {string} walletPath
* @property {string} src
* @property {string} module
* @property {Tag[]} tags
*
* @callback SpawnProcess
Expand All @@ -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
})
Expand Down
111 changes: 97 additions & 14 deletions dev-cli/container/src/node/test/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
Expand All @@ -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',
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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' }
]
Expand All @@ -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' }
],
Expand All @@ -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' }
]
Expand All @@ -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' }
]
Expand Down
Loading

0 comments on commit dcf6818

Please sign in to comment.