zkapp-cli
Version:
CLI to create zkApps (zero-knowledge apps) for Mina Protocol
403 lines (388 loc) • 12.9 kB
JavaScript
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'node:path';
import url from 'node:url';
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs/yargs';
import config from '../lib/config.js';
import Constants from '../lib/constants.js';
import deploy from '../lib/deploy.js';
import example from '../lib/example.js';
import file from '../lib/file.js';
import {
lightnetExplorer,
lightnetFollowLogs,
lightnetSaveLogs,
lightnetStart,
lightnetStatus,
lightnetStop,
} from '../lib/lightnet.js';
import project from '../lib/project.js';
import system from '../lib/system.js';
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
yargs(hideBin(process.argv))
.scriptName(chalk.green('zk'))
.usage('Usage: $0 <command> [options]')
.strictCommands()
.strictOptions()
.updateStrings(getCustomizedStrings())
.demandCommand(1, chalk.red('Please provide a command.'))
.command(projectCli())
.command(fileCli())
.command(configCli())
.command(deployCli())
.command(exampleCli())
.command(systemCli())
.command(lightnetCli())
.version(
fs.readJsonSync(path.join(__dirname, '..', '..', 'package.json')).version
)
.alias('h', 'help')
.alias('v', 'version')
.epilog(
// chalk.reset is a hack to force the terminal to retain a line break below
chalk.reset(
`
__ _
/_ | | |
___ | | __ _| |__ ___
/ _ \\| | / _\` | '_ \\/ __|
| (_) | |_| (_| | |_) \\__ \\
\\___/|____\\__,_|_.__/|___/
https://o1labs.org
`
)
).argv;
function projectCli() {
return {
command: ['project [name]', 'proj [name]', 'p [name]'],
describe: 'Create a new project',
builder: {
name: { demand: true, string: true, hidden: true },
ui: {
demand: false,
string: true,
hidden: false,
choices: Constants.uiTypes,
description: 'Creates an accompanying UI',
},
},
handler: async (argv) => await project(argv),
};
}
function fileCli() {
return {
command: ['file [name]', 'f [name]'],
describe: 'Create a file and generate the corresponding test file',
builder: { name: { demand: true, string: true, hidden: true } },
handler: async (argv) => await file(argv.name),
};
}
function configCli() {
return {
command: ['config [list] [lightnet]'],
describe: 'List or add a new deploy alias',
builder: {
list: {
alias: 'l',
demand: false,
boolean: true,
hidden: false,
default: false,
description:
'Whether to list the available deploy aliases and their configurations.',
},
lightnet: {
alias: 'ln',
demand: false,
boolean: true,
hidden: false,
default: false,
description:
'Whether to automatically configure the deploy alias compatible with the lightweight Mina blockchain network.',
},
},
handler: async (argv) => await config(argv),
};
}
function deployCli() {
return {
command: ['deploy [alias]'],
describe: 'Deploy or redeploy a zkApp',
builder: {
alias: { demand: false, string: true, hidden: true },
y: {
alias: 'yes',
boolean: true,
demand: false,
hidden: false,
description:
'Respond `yes` to all confirmation prompts.\nAllows running non-interactively within a script.',
},
},
handler: async (argv) => await deploy(argv),
};
}
function exampleCli() {
return {
command: ['example [name]', 'e [name]'],
describe: 'Create an example project',
builder: {
name: {
demand: false,
string: true,
hidden: false,
choices: Constants.exampleTypes,
},
},
handler: async (argv) => await example(argv.name),
};
}
function systemCli() {
return {
command: ['system', 'sys', 's'],
describe: 'Show system info',
builder: {},
handler: async () => await system(),
};
}
function lightnetCli() {
return {
command: ['lightnet <sub-command> [options]'],
describe:
'Manage the lightweight Mina blockchain network for zkApps development and testing purposes.\nMore information can be found at:\nhttps://docs.minaprotocol.com/zkapps/testing-zkapps-lightnet',
builder: (yargs) => {
yargs
.command(
[
'start [mode] [type] [proof-level] [mina-branch] [archive] [sync] [pull] [mina-log-level]',
],
'Start the lightweight Mina blockchain network Docker container.',
{
mode: {
alias: 'm',
demand: false,
string: true,
hidden: false,
choices: Constants.lightnetModes,
default: 'single-node',
description:
'Whether to form the network with one node or with multiple network participants.\n"single-node" value will make the network faster.',
},
type: {
alias: 't',
demand: false,
string: true,
hidden: false,
choices: Constants.lightnetTypes,
default: 'fast',
description:
'Whether to configure the network to be fast or slower with closer-to-real-world properties.',
},
'proof-level': {
alias: 'p',
demand: false,
string: true,
hidden: false,
choices: Constants.lightnetProofLevels,
default: 'none',
description:
'"none" value will make the network faster by disabling the blockchain SNARK proofs.',
},
'mina-branch': {
alias: 'b',
demand: false,
string: true,
hidden: false,
choices: Constants.lightnetMinaBranches,
default: 'compatible',
description:
'One of the major Mina repository branches the Docker image artifacts were compiled against.',
},
archive: {
alias: 'a',
demand: false,
boolean: true,
hidden: false,
default: true,
description:
'Whether to start the Mina Archive process and Archive Node API application within the Docker container.',
},
sync: {
alias: 's',
demand: false,
boolean: true,
hidden: false,
default: true,
description:
'Whether to wait for the network to reach the synchronized state.',
},
pull: {
alias: 'u',
demand: false,
boolean: true,
hidden: false,
default: true,
description:
'Whether to pull the latest version of the Docker image from the Docker Hub.',
},
'mina-log-level': {
alias: 'l',
demand: false,
string: true,
hidden: false,
choices: Constants.lightnetMinaProcessesLogLevels,
default: 'Trace',
description: 'Mina processes logging level to use.',
},
'slot-time': {
alias: 'o',
demand: false,
number: true,
hidden: false,
default: 20_000,
description:
'The slot time in milliseconds for block production.',
},
},
async (argv) => await lightnetStart(argv)
)
.command(
['stop [save-logs] [clean-up]'],
'Stop the lightweight Mina blockchain network Docker container and perform the cleanup.',
{
'save-logs': {
alias: 'l',
demand: false,
boolean: true,
hidden: false,
default: true,
description:
'Whether to save the Docker container processes logs to the host file system.',
},
'clean-up': {
alias: 'c',
demand: false,
boolean: true,
hidden: false,
default: true,
description:
'Whether to remove the Docker container, dangling Docker images, consumed Docker volume, and the blockchain network configuration.',
},
},
async (argv) => await lightnetStop(argv)
)
.command(
['status'],
'Get the lightweight Mina blockchain network status.',
async () => await lightnetStatus()
)
.command(
['logs <sub-command> [options]'],
'Handle the lightweight Mina blockchain network Docker container processes logs.',
(yargs) => {
yargs
.command(
['save'],
'Save the lightweight Mina blockchain network Docker container processes logs to the host file system.',
async () => await lightnetSaveLogs()
)
.command(
['follow [process]'],
'Follow one of the lightweight Mina blockchain network Docker container processes logs.',
{
process: {
alias: 'p',
demand: false,
string: true,
hidden: false,
choices: [
...Constants.lightnetProcessToLogFileMapping.keys(),
],
description:
'The name of the Docker container process to follow the logs of.',
},
},
async (argv) => await lightnetFollowLogs(argv)
);
}
)
.command(
['explorer [use] [list]'],
'Launch the lightweight Mina explorer.',
{
use: {
alias: 'u',
demand: false,
string: true,
hidden: false,
default: 'latest',
description:
'The version of the lightweight Mina explorer to use.\nThe "latest" value will use the latest available version.',
},
list: {
alias: 'l',
demand: false,
boolean: true,
hidden: false,
default: false,
description:
'Whether to list the available versions of the lightweight Mina explorer.',
},
},
async (argv) => await lightnetExplorer(argv)
);
},
};
}
function getCustomizedStrings() {
// Overridden messages source can be found here:
// https://github.com/yargs/yargs/blob/master/locales/en.json
return {
'Not enough non-option arguments: got %s, need at least %s': {
one: chalk.red(
'Not enough non-option arguments: got %s, need at least %s'
),
other: chalk.red(
'Not enough non-option arguments: got %s, need at least %s'
),
},
'Too many non-option arguments: got %s, maximum of %s': {
one: chalk.red('Too many non-option arguments: got %s, maximum of %s'),
other: chalk.red('Too many non-option arguments: got %s, maximum of %s'),
},
'Missing argument value: %s': {
one: chalk.red('Missing argument value: %s'),
other: chalk.red('Missing argument values: %s'),
},
'Missing required argument: %s': {
one: chalk.red('Missing required argument: %s'),
other: chalk.red('Missing required arguments: %s'),
},
'Unknown argument: %s': {
one: chalk.red('Unknown argument: %s'),
other: chalk.red('Unknown arguments: %s'),
},
'Unknown command: %s': {
one: chalk.red('Unknown command: %s'),
other: chalk.red('Unknown commands: %s'),
},
'Invalid values:': chalk.red('Invalid values:'),
'Argument: %s, Given: %s, Choices: %s': chalk.red(
'Argument: %s, Given: %s, Choices: %s'
),
'Argument check failed: %s': chalk.red('Argument check failed: %s'),
'Implications failed:': chalk.red('Missing dependent arguments:'),
'Not enough arguments following: %s': chalk.red(
'Not enough arguments following: %s'
),
'Invalid JSON config file: %s': chalk.red('Invalid JSON config file: %s'),
'Arguments %s and %s are mutually exclusive': chalk.yellow(
'Arguments %s and %s are mutually exclusive'
),
deprecated: chalk.yellow('deprecated'),
'deprecated: %s': chalk.yellow('deprecated: %s'),
};
}