Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tricky-colts-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@mimicprotocol/cli": patch
"@mimicprotocol/lib-ts": patch
"@mimicprotocol/test-ts": patch
---

Refactor codegen, compile, build, deploy and test commands and their parameters
7 changes: 5 additions & 2 deletions packages/cli/src/commands/authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Command, Flags } from '@oclif/core'

import { CredentialsManager, ProfileCredentials } from '../lib/CredentialsManager'
import log from '../log'
import { FlagsType } from '../types'

export type AuthenticateFlags = FlagsType<typeof Authenticate>

export default class Authenticate extends Command {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -28,15 +31,15 @@ export default class Authenticate extends Command {
}),
}

protected authenticate(flags: { profile?: string; 'api-key'?: string }): ProfileCredentials {
public static authenticate(cmd: Command, flags: AuthenticateFlags): ProfileCredentials {
let apiKey = flags['api-key']
if (!apiKey) {
try {
const credentials = CredentialsManager.getDefault().getCredentials(flags.profile)
apiKey = credentials.apiKey
} catch (error) {
if (error instanceof Error) {
this.error(error.message, {
cmd.error(error.message, {
code: 'AuthenticationRequired',
suggestions: [
`Run ${log.highlightText('mimic login')} to authenticate`,
Expand Down
32 changes: 13 additions & 19 deletions packages/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,31 @@
import { Command, Flags } from '@oclif/core'
import { Command } from '@oclif/core'

import { FlagsType } from '../types'

import Codegen from './codegen'
import Compile from './compile'

export type BuildFlags = FlagsType<typeof Build>

export default class Build extends Command {
static override description = 'Runs code generation and then compiles the function'

static override examples = [
'<%= config.bin %> <%= command.id %> --manifest ./manifest.yaml --function src/function.ts --output ./build --types ./src/types',
'<%= config.bin %> <%= command.id %> --manifest ./manifest.yaml --function src/function.ts --build-directory ./build --types-directory ./src/types',
]

static override flags = {
manifest: Flags.string({ char: 'm', description: 'manifest to use', default: 'manifest.yaml' }),
function: Flags.string({ char: 'f', description: 'function to compile', default: 'src/function.ts' }),
output: Flags.string({ char: 'o', description: 'output directory for build artifacts', default: './build' }),
types: Flags.string({ char: 'y', description: 'output directory for generated types', default: './src/types' }),
clean: Flags.boolean({
char: 'c',
description: 'remove existing generated types before generating new files',
default: false,
}),
...Codegen.flags,
...Compile.flags,
}

public async run(): Promise<void> {
const { flags } = await this.parse(Build)
const { manifest, function: functionFile, output, types, clean } = flags

const codegenArgs: string[] = ['--manifest', manifest, '--output', types]
if (clean) codegenArgs.push('--clean')

await Codegen.run(codegenArgs)
await Build.build(this, flags)
}

const compileArgs: string[] = ['--function', functionFile, '--manifest', manifest, '--output', output]
await Compile.run(compileArgs)
public static async build(cmd: Command, flags: BuildFlags): Promise<void> {
await Codegen.codegen(cmd, flags)
await Compile.compile(cmd, flags)
}
}
44 changes: 27 additions & 17 deletions packages/cli/src/commands/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ import { join } from 'path'

import { AbisInterfaceGenerator, InputsInterfaceGenerator, ManifestHandler } from '../lib'
import log from '../log'
import { Manifest } from '../types'
import { FlagsType, Manifest } from '../types'

export type CodegenFlags = FlagsType<typeof Codegen>

export default class Codegen extends Command {
static override description = 'Generates typed interfaces for declared inputs and ABIs from your manifest.yaml file'

static override examples = ['<%= config.bin %> <%= command.id %> --manifest ./manifest.yaml --output ./types']
static override examples = [
'<%= config.bin %> <%= command.id %> --manifest ./manifest.yaml --types-directory ./types',
]

static override flags = {
manifest: Flags.string({ char: 'm', description: 'Specify a custom manifest file path', default: 'manifest.yaml' }),
output: Flags.string({ char: 'o', description: 'Ouput directory for generated types', default: './src/types' }),
'types-directory': Flags.string({
char: 't',
description: 'Output directory for generated types',
default: './src/types',
}),
clean: Flags.boolean({
char: 'c',
description: 'Remove existing generated types before generating new files',
Expand All @@ -24,21 +32,24 @@ export default class Codegen extends Command {

public async run(): Promise<void> {
const { flags } = await this.parse(Codegen)
const { manifest: manifestDir, output: outputDir, clean } = flags
const manifest = ManifestHandler.load(this, manifestDir)
await Codegen.codegen(this, flags)
}

public static async codegen(cmd: Command, flags: CodegenFlags): Promise<void> {
const { manifest: manifestDir, 'types-directory': typesDir, clean } = flags
const manifest = ManifestHandler.load(cmd, manifestDir)
if (clean) {
const shouldDelete = await confirm({
message: `Are you sure you want to ${log.warnText('delete')} all the contents in ${log.highlightText(outputDir)}. This action is ${log.warnText('irreversible')}`,
message: `Are you sure you want to ${log.warnText('delete')} all the contents in ${log.highlightText(typesDir)}. This action is ${log.warnText('irreversible')}`,
default: false,
})
if (!shouldDelete) {
console.log('You can remove the --clean flag from your command')
console.log('Stopping initialization...')
this.exit(0)
cmd.exit(0)
}
log.startAction(`Deleting contents of ${outputDir}`)
if (fs.existsSync(outputDir)) fs.rmSync(outputDir, { recursive: true, force: true })
log.startAction(`Deleting contents of ${typesDir}`)
if (fs.existsSync(typesDir)) fs.rmSync(typesDir, { recursive: true, force: true })
}

log.startAction('Generating code')
Expand All @@ -47,23 +58,22 @@ export default class Codegen extends Command {
return
}

if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true })

generateAbisCode(manifest, outputDir, manifestDir)
generateInputsCode(manifest, outputDir)
if (!fs.existsSync(typesDir)) fs.mkdirSync(typesDir, { recursive: true })
generateAbisCode(manifest, typesDir, manifestDir)
generateInputsCode(manifest, typesDir)
log.stopAction()
}
}

function generateAbisCode(manifest: Manifest, outputDir: string, manifestDir: string) {
function generateAbisCode(manifest: Manifest, typesDir: string, manifestDir: string) {
for (const [contractName, path] of Object.entries(manifest.abis)) {
const abi = JSON.parse(fs.readFileSync(join(manifestDir, '../', path), 'utf-8'))
const abiInterface = AbisInterfaceGenerator.generate(abi, contractName)
if (abiInterface.length > 0) fs.writeFileSync(`${outputDir}/${contractName}.ts`, abiInterface)
if (abiInterface.length > 0) fs.writeFileSync(`${typesDir}/${contractName}.ts`, abiInterface)
}
}

function generateInputsCode(manifest: Manifest, outputDir: string) {
function generateInputsCode(manifest: Manifest, typesDir: string) {
const inputsInterface = InputsInterfaceGenerator.generate(manifest.inputs)
if (inputsInterface.length > 0) fs.writeFileSync(`${outputDir}/index.ts`, inputsInterface)
if (inputsInterface.length > 0) fs.writeFileSync(`${typesDir}/index.ts`, inputsInterface)
}
36 changes: 23 additions & 13 deletions packages/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,47 @@ import * as path from 'path'
import ManifestHandler from '../lib/ManifestHandler'
import { execBinCommand } from '../lib/packageManager'
import log from '../log'
import { FlagsType } from '../types'

export type CompileFlags = FlagsType<typeof Compile>

export default class Compile extends Command {
static override description = 'Compiles function'

static override examples = ['<%= config.bin %> <%= command.id %> --function src/function.ts --output ./output']
static override examples = [
'<%= config.bin %> <%= command.id %> --function src/function.ts --build-directory ./build',
]

static override flags = {
function: Flags.string({ char: 'f', description: 'function to compile', default: 'src/function.ts' }),
manifest: Flags.string({ char: 'm', description: 'manifest to validate', default: 'manifest.yaml' }),
output: Flags.string({ char: 'o', description: 'output directory', default: './build' }),
function: Flags.string({ char: 'f', description: 'Function to compile', default: 'src/function.ts' }),
manifest: Flags.string({ char: 'm', description: 'Manifest to validate', default: 'manifest.yaml' }),
'build-directory': Flags.string({ char: 'b', description: 'Output directory for compilation', default: './build' }),
}

public async run(): Promise<void> {
const { flags } = await this.parse(Compile)
const { function: functionFile, output: outputDir, manifest: manifestDir } = flags
await Compile.compile(this, flags)
}

const absFunctionFile = path.resolve(functionFile)
const absOutputDir = path.resolve(outputDir)
public static async compile(
cmd: Command,
{ function: functionDir, 'build-directory': buildDir, manifest: manifestDir }: CompileFlags
): Promise<void> {
const absFunctionFile = path.resolve(functionDir)
const absBuildDir = path.resolve(buildDir)

if (!fs.existsSync(absOutputDir)) fs.mkdirSync(absOutputDir, { recursive: true })
if (!fs.existsSync(absBuildDir)) fs.mkdirSync(absBuildDir, { recursive: true })

log.startAction('Verifying Manifest')
const manifest = ManifestHandler.load(this, manifestDir)
const manifest = ManifestHandler.load(cmd, manifestDir)
log.startAction('Compiling')

const ascArgs = [
absFunctionFile,
'--target',
'release',
'--outFile',
path.join(absOutputDir, 'function.wasm'),
path.join(absBuildDir, 'function.wasm'),
'--optimize',
'--exportRuntime',
'--transform',
Expand All @@ -44,16 +54,16 @@ export default class Compile extends Command {

const result = execBinCommand('asc', ascArgs, process.cwd())
if (result.status !== 0) {
this.error('AssemblyScript compilation failed', {
cmd.error('AssemblyScript compilation failed', {
code: 'BuildError',
suggestions: ['Check the AssemblyScript file'],
})
}

log.startAction('Saving files')

fs.writeFileSync(path.join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
fs.writeFileSync(path.join(absBuildDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
log.stopAction()
console.log(`Build complete! Artifacts in ${outputDir}/`)
console.log(`Build complete! Artifacts in ${absBuildDir}/`)
}
}
57 changes: 29 additions & 28 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,68 @@
import { Flags } from '@oclif/core'
import { Command, Flags } from '@oclif/core'
import axios, { AxiosError } from 'axios'
import FormData from 'form-data'
import * as fs from 'fs'
import { join, resolve } from 'path'

import { GENERIC_SUGGESTION } from '../errors'
import { ProfileCredentials } from '../lib/CredentialsManager'
import { execBinCommand } from '../lib/packageManager'
import log from '../log'
import { FlagsType } from '../types'

import Authenticate from './authenticate'
import Build from './build'

const MIMIC_REGISTRY_DEFAULT = 'https://api-protocol.mimic.fi'

export default class Deploy extends Authenticate {
export type DeployFlags = FlagsType<typeof Deploy>

export default class Deploy extends Command {
static override description =
'Uploads your compiled function artifacts to IPFS and registers it into the Mimic Registry'

static override examples = [
'<%= config.bin %> <%= command.id %> --input ./dist --output ./dist',
'<%= config.bin %> <%= command.id %> --build-directory ./build',
'<%= config.bin %> <%= command.id %> --profile staging',
'<%= config.bin %> <%= command.id %> --api-key MY_KEY --input ./dist --output ./dist',
'<%= config.bin %> <%= command.id %> --api-key MY_KEY --build-directory ./build',
]

static override flags = {
...Authenticate.flags,
input: Flags.string({ char: 'i', description: 'Directory containing the compiled artifacts', default: './build' }),
output: Flags.string({ char: 'o', description: 'Output directory for deployment CID', default: './build' }),
...Build.flags,
'build-directory': Flags.string({
char: 'b',
description: 'Output directory for compilation, or input directory for deployment when --skip-build is used',
default: './build',
}),
url: Flags.string({ char: 'u', description: `Mimic Registry base URL`, default: MIMIC_REGISTRY_DEFAULT }),
'skip-compile': Flags.boolean({ description: 'Skip codegen and compile steps before uploading', default: false }),
'skip-build': Flags.boolean({ description: 'Skip codegen and compile steps before uploading', default: false }),
}

public async run(): Promise<void> {
const { flags } = await this.parse(Deploy)
const { input: inputDir, output: outputDir, 'skip-compile': skipCompile, url: registryUrl } = flags
const fullInputDir = resolve(inputDir)
const fullOutputDir = resolve(outputDir)
await this.deploy(this, flags)
}

let credentials = this.authenticate(flags)
public async deploy(cmd: Command, flags: DeployFlags): Promise<void> {
const { 'build-directory': buildDir, 'skip-build': skipBuild, url: registryUrl } = flags
const absBuildDir = resolve(buildDir)

if (!skipCompile) {
const codegen = execBinCommand('mimic', ['codegen'], process.cwd())
if (codegen.status !== 0)
this.error('Code generation failed', { code: 'CodegenError', suggestions: ['Fix manifest and ABI files'] })
let credentials = Authenticate.authenticate(cmd, flags)

const compile = execBinCommand('mimic', ['compile', '--output', fullInputDir], process.cwd())
if (compile.status !== 0)
this.error('Compilation failed', { code: 'BuildError', suggestions: ['Check the function source code'] })
}
if (!skipBuild) await Build.build(cmd, flags)

log.startAction('Validating')

if (!fs.existsSync(fullInputDir))
this.error(`Directory ${log.highlightText(fullInputDir)} does not exist`, {
if (!fs.existsSync(absBuildDir) && skipBuild)
cmd.error(`Directory ${log.highlightText(absBuildDir)} does not exist`, {
code: 'Directory Not Found',
suggestions: ['Use the --input flag to specify the correct path'],
suggestions: ['Use the --build-directory flag to specify the correct path'],
})

const neededFiles = ['manifest.json', 'function.wasm'].map((file) => join(fullInputDir, file))
const neededFiles = ['manifest.json', 'function.wasm'].map((file) => join(absBuildDir, file))
for (const file of neededFiles) {
if (!fs.existsSync(file))
this.error(`Could not find ${file}`, {
cmd.error(`Could not find ${file}`, {
code: 'File Not Found',
suggestions: [`Use ${log.highlightText('mimic compile')} to generate the needed files`],
})
Expand All @@ -71,9 +73,8 @@ export default class Deploy extends Authenticate {
console.log(`IPFS CID: ${log.highlightText(CID)}`)
log.stopAction()

if (!fs.existsSync(fullOutputDir)) fs.mkdirSync(fullOutputDir, { recursive: true })
fs.writeFileSync(join(fullOutputDir, 'CID.json'), JSON.stringify({ CID }, null, 2))
console.log(`CID saved at ${log.highlightText(fullOutputDir)}`)
fs.writeFileSync(join(absBuildDir, 'CID.json'), JSON.stringify({ CID }, null, 2))
console.log(`CID saved at ${log.highlightText(absBuildDir)}`)
console.log(`Function deployed!`)
}

Expand Down
Loading