Skip to content
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@types/lodash": "^4.17.15",
"@types/mocha": "^10.0.1",
"@types/node": "^22.10.5",
"@types/sinon": "^21.0.0",
"axios-mock-adapter": "^2.1.0",
"chai": "^4.3.7",
"eslint-config-mimic": "^0.0.3",
Expand Down
56 changes: 49 additions & 7 deletions packages/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Command, Flags } from '@oclif/core'

import { DEFAULT_BUILD_OUTPUT, DEFAULT_MANIFEST_FILE, DEFAULT_TASK_ENTRY, DEFAULT_TYPES_OUTPUT } from '../constants'
import { filterTasks, taskFilterFlags } from '../helpers'
import MimicConfigHandler from '../lib/MimicConfigHandler'
import log from '../log'
import { RequiredTaskConfig } from '../types'

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

Expand All @@ -11,27 +17,63 @@ export default class Build extends Command {
]

static override flags = {
manifest: Flags.string({ char: 'm', description: 'manifest to use', default: 'manifest.yaml' }),
task: Flags.string({ char: 't', description: 'task to compile', default: 'src/task.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' }),
manifest: Flags.string({ char: 'm', description: 'manifest to use', default: DEFAULT_MANIFEST_FILE }),
task: Flags.string({ char: 't', description: 'task to compile', default: DEFAULT_TASK_ENTRY }),
output: Flags.string({
char: 'o',
description: 'output directory for build artifacts',
default: DEFAULT_BUILD_OUTPUT,
}),
types: Flags.string({
char: 'y',
description: 'output directory for generated types',
default: DEFAULT_TYPES_OUTPUT,
}),
clean: Flags.boolean({
char: 'c',
description: 'remove existing generated types before generating new files',
default: false,
}),
'skip-config': Flags.boolean({
hidden: true,
description: 'Skip mimic.yaml config (used internally)',
default: false,
}),
...taskFilterFlags,
}

public async run(): Promise<void> {
const { flags } = await this.parse(Build)
const { manifest, task, output, types, clean } = flags
const { manifest, task, output, types, clean, include, exclude, 'skip-config': skipConfig } = flags

if (!skipConfig && MimicConfigHandler.exists()) {
const mimicConfig = MimicConfigHandler.load(this)
const allTasks = MimicConfigHandler.getTasks(mimicConfig)
const tasks = filterTasks(this, allTasks, include, exclude)
for (const taskConfig of tasks) {
console.log(`\n${log.highlightText(`[${taskConfig.name}]`)}`)
await this.runForTask(taskConfig, clean)
}
} else {
await this.runForTask({ manifest, entry: task, output, types }, clean)
}
}

const codegenArgs: string[] = ['--manifest', manifest, '--output', types]
private async runForTask(task: Omit<RequiredTaskConfig, 'name'>, clean: boolean): Promise<void> {
const codegenArgs: string[] = ['--manifest', task.manifest, '--output', task.types, '--skip-config']
if (clean) codegenArgs.push('--clean')

await Codegen.run(codegenArgs)

const compileArgs: string[] = ['--task', task, '--manifest', manifest, '--output', output]
const compileArgs: string[] = [
'--task',
task.entry,
'--manifest',
task.manifest,
'--output',
task.output,
'--skip-config',
]
await Compile.run(compileArgs)
}
}
43 changes: 38 additions & 5 deletions packages/cli/src/commands/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,61 @@ import { Command, Flags } from '@oclif/core'
import * as fs from 'fs'
import { join } from 'path'

import { AbisInterfaceGenerator, InputsInterfaceGenerator, ManifestHandler } from '../lib'
import { DEFAULT_MANIFEST_FILE, DEFAULT_TYPES_OUTPUT } from '../constants'
import { filterTasks, taskFilterFlags } from '../helpers'
import { AbisInterfaceGenerator, InputsInterfaceGenerator, ManifestHandler, MimicConfigHandler } from '../lib'
import log from '../log'
import { Manifest } from '../types'
import { Manifest, RequiredTaskConfig } from '../types'

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 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' }),
manifest: Flags.string({
char: 'm',
description: 'Specify a custom manifest file path',
default: DEFAULT_MANIFEST_FILE,
}),
output: Flags.string({
char: 'o',
description: 'Ouput directory for generated types',
default: DEFAULT_TYPES_OUTPUT,
}),
clean: Flags.boolean({
char: 'c',
description: 'Remove existing generated types before generating new files',
default: false,
}),
'skip-config': Flags.boolean({
hidden: true,
description: 'Skip mimic.yaml config (used internally by build command)',
default: false,
}),
...taskFilterFlags,
}

public async run(): Promise<void> {
const { flags } = await this.parse(Codegen)
const { manifest: manifestDir, output: outputDir, clean } = flags
const { manifest: manifestDir, output: outputDir, clean, include, exclude, 'skip-config': skipConfig } = flags

if (!skipConfig && MimicConfigHandler.exists()) {
const mimicConfig = MimicConfigHandler.load(this)
const allTasks = MimicConfigHandler.getTasks(mimicConfig)
const tasks = filterTasks(this, allTasks, include, exclude)
for (const task of tasks) {
console.log(`\n${log.highlightText(`[${task.name}]`)}`)
await this.runForTask(task, clean)
}
} else {
await this.runForTask({ manifest: manifestDir, types: outputDir }, clean)
}
}

private async runForTask(task: Omit<RequiredTaskConfig, 'name' | 'entry' | 'output'>, clean: boolean): Promise<void> {
const manifestDir = task.manifest
const outputDir = task.types
const manifest = ManifestHandler.load(this, manifestDir)

if (clean) {
Expand Down
49 changes: 40 additions & 9 deletions packages/cli/src/commands/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,63 @@ import { Command, Flags } from '@oclif/core'
import * as fs from 'fs'
import * as path from 'path'

import { DEFAULT_BUILD_OUTPUT, DEFAULT_MANIFEST_FILE, DEFAULT_TASK_ENTRY } from '../constants'
import { filterTasks, taskFilterFlags } from '../helpers'
import ManifestHandler from '../lib/ManifestHandler'
import MimicConfigHandler from '../lib/MimicConfigHandler'
import { execBinCommand } from '../lib/packageManager'
import log from '../log'
import { RequiredTaskConfig } from '../types'

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

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

static override flags = {
task: Flags.string({ char: 't', description: 'task to compile', default: 'src/task.ts' }),
manifest: Flags.string({ char: 'm', description: 'manifest to validate', default: 'manifest.yaml' }),
output: Flags.string({ char: 'o', description: 'output directory', default: './build' }),
task: Flags.string({ char: 't', description: 'task to compile', default: DEFAULT_TASK_ENTRY }),
manifest: Flags.string({ char: 'm', description: 'manifest to validate', default: DEFAULT_MANIFEST_FILE }),
output: Flags.string({ char: 'o', description: 'output directory', default: DEFAULT_BUILD_OUTPUT }),
'skip-config': Flags.boolean({
hidden: true,
description: 'Skip mimic.yaml config (used internally by build command)',
default: false,
}),
...taskFilterFlags,
}

public async run(): Promise<void> {
const { flags } = await this.parse(Compile)
const { task: taskFile, output: outputDir, manifest: manifestDir } = flags
const {
task: taskFile,
output: outputDir,
manifest: manifestDir,
include,
exclude,
'skip-config': skipConfig,
} = flags

const absTaskFile = path.resolve(taskFile)
const absOutputDir = path.resolve(outputDir)
if (!skipConfig && MimicConfigHandler.exists()) {
const mimicConfig = MimicConfigHandler.load(this)
const allTasks = MimicConfigHandler.getTasks(mimicConfig)
const tasks = filterTasks(this, allTasks, include, exclude)
for (const task of tasks) {
console.log(`\n${log.highlightText(`[${task.name}]`)}`)
await this.runForTask(task)
}
} else {
await this.runForTask({ manifest: manifestDir, entry: taskFile, output: outputDir })
}
}

private async runForTask(task: Omit<RequiredTaskConfig, 'name' | 'types'>): Promise<void> {
const absTaskFile = path.resolve(task.entry)
const absOutputDir = path.resolve(task.output)

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

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

const ascArgs = [
Expand All @@ -52,8 +83,8 @@ export default class Compile extends Command {

log.startAction('Saving files')

fs.writeFileSync(path.join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
fs.writeFileSync(path.join(absOutputDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
log.stopAction()
console.log(`Build complete! Artifacts in ${outputDir}/`)
console.log(`Build complete! Artifacts in ${task.output}/`)
}
}
93 changes: 79 additions & 14 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import FormData from 'form-data'
import * as fs from 'fs'
import { join, resolve } from 'path'

import { DEFAULT_BUILD_OUTPUT, DEFAULT_MANIFEST_FILE, DEFAULT_TASK_ENTRY, DEFAULT_TYPES_OUTPUT } from '../constants'
import { GENERIC_SUGGESTION } from '../errors'
import { filterTasks, taskFilterFlags } from '../helpers'
import { ProfileCredentials } from '../lib/CredentialsManager'
import MimicConfigHandler from '../lib/MimicConfigHandler'
import { execBinCommand } from '../lib/packageManager'
import log from '../log'
import { RequiredTaskConfig } from '../types'

import Authenticate from './authenticate'

Expand All @@ -24,45 +28,106 @@ export default class Deploy extends Authenticate {

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' }),
input: Flags.string({
char: 'i',
description: 'Directory containing the compiled artifacts',
default: DEFAULT_BUILD_OUTPUT,
}),
output: Flags.string({
char: 'o',
description: 'Output directory for deployment CID',
default: DEFAULT_BUILD_OUTPUT,
}),
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 }),
...taskFilterFlags,
}

public async run(): Promise<void> {
const { flags } = await this.parse(Deploy)
const { input: inputDir, output: outputDir, 'skip-compile': skipCompile, url: registryUrl } = flags
const {
profile,
'api-key': apiKey,
input: inputDir,
output: outputDir,
'skip-compile': skipCompile,
url: registryUrl,
include,
exclude,
} = flags

if (MimicConfigHandler.exists()) {
const mimicConfig = MimicConfigHandler.load(this)
const allTasks = MimicConfigHandler.getTasks(mimicConfig)
const tasks = filterTasks(this, allTasks, include, exclude)
for (const task of tasks) {
console.log(`\n${log.highlightText(`[${task.name}]`)}`)
await this.runForTask(task, registryUrl, skipCompile, task.output, profile, apiKey)
}
} else {
await this.runForTask(
{ manifest: DEFAULT_MANIFEST_FILE, entry: DEFAULT_TASK_ENTRY, types: DEFAULT_TYPES_OUTPUT, output: outputDir },
registryUrl,
skipCompile,
inputDir,
profile,
apiKey
)
}
}

private async runForTask(
task: Omit<RequiredTaskConfig, 'name'>,
registryUrl: string,
skipCompile: boolean,
inputDir: string,
profile?: string,
apiKey?: string
): Promise<void> {
const fullInputDir = resolve(inputDir)
const fullOutputDir = resolve(outputDir)
const fullOutputDir = resolve(task.output)

let credentials = this.authenticate(flags)
const credentials = this.authenticate({ profile, 'api-key': apiKey })

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'] })

const compile = execBinCommand('mimic', ['compile', '--output', fullInputDir], process.cwd())
if (compile.status !== 0)
this.error('Compilation failed', { code: 'BuildError', suggestions: ['Check the task source code'] })
const build = execBinCommand(
'mimic',
[
'build',
'--manifest',
task.manifest,
'--task',
task.entry,
'--output',
fullInputDir,
'--types',
task.types,
'--skip-config',
],
process.cwd()
)
if (build.status !== 0) {
this.error('Build failed', { code: 'BuildError', suggestions: ['Check the task source code and manifest'] })
}
}

log.startAction('Validating')

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

const neededFiles = ['manifest.json', 'task.wasm'].map((file) => join(fullInputDir, file))
for (const file of neededFiles) {
if (!fs.existsSync(file))
if (!fs.existsSync(file)) {
this.error(`Could not find ${file}`, {
code: 'File Not Found',
suggestions: [`Use ${log.highlightText('mimic compile')} to generate the needed files`],
})
}
}

log.startAction('Uploading to Mimic Registry')
Expand Down
Loading