UNPKG

@iexec/iapp

Version:

A CLI to guide you through the process of building an iExec iApp

209 lines (195 loc) 8.03 kB
#!/usr/bin/env node import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { init } from './cmd/init.js'; import { deploy } from './cmd/deploy.js'; import { run } from './cmd/run.js'; import { test } from './cmd/test.js'; import { mockProtectedData } from './cmd/mock-protected-data.js'; import { debug } from './cmd/debug.js'; import { SUPPORTED_CHAINS } from './config/config.js'; import { checkPackageUpdate } from './cli-helpers/checkPackageUpdate.js'; import { walletImport } from './cmd/wallet-import.js'; import { walletSelect } from './cmd/wallet-select.js'; await checkPackageUpdate(); const coerceRequesterSecret = ( values: string[] ): { key: number; value: string }[] => { // create Array<{key: number, value: string}> from the values Array<string> const secrets = values.reduce( (acc, curr) => { const separatorIndex = curr.indexOf('='); const key = Number(curr.slice(0, separatorIndex)); const value = curr.slice(separatorIndex + 1); if (!Number.isInteger(key) || key < 1) { throw Error( `invalid secret index ${key} in requesterSecret \`${curr}\`` ); } if (value === undefined) { throw Error( `invalid secret value ${value} in requesterSecret \`${curr}\`` ); } return [...acc, { key, value }]; }, [] as { key: number; value: string }[] ); return secrets; }; const yargsInstance = yargs(hideBin(process.argv)); yargsInstance .locale('en') // set local to American English (no i18n) .scriptName('iapp') .usage('$0 <cmd> [args]') // Initialize command .command('init', 'Initialize your app structure', () => {}, init) // Test command .command({ command: 'test', describe: 'Test your app', builder: (y) => { return y .option('args', { describe: `Arguments that will be accessible into the iApp. Spaces separates arguments, use quotes to group arguments (Ex: \`--args '"foo bar" baz'\` will interpret "foo bar" as first arg, "bar" as second arg)`, type: 'string', }) .option('protectedData', { describe: 'Specify the protected data mock name (use "default" protected data mock or create custom mocks with `iapp mock protectedData`) ', type: 'string', }) .option('inputFile', { describe: 'Specify one or multiple input files (publicly-accessible URLs). Input files are accessible to the iApp as local files using path specified in environment variables (Ex: `--inputFile https://foo.com/fileA.txt https://bar.io/fileB.json` will download the file at "https://foo.com/fileA.txt" and make it available for the iApp at `$IEXEC_IN/$IEXEC_INPUT_FILE_NAME_1`, same for "https://bar.io/fileB.json" at `$IEXEC_IN/$IEXEC_INPUT_FILE_NAME_2`)', type: 'string', requiresArg: true, // must be invoked with a value array: true, }) .option('requesterSecret', { describe: 'Specify one or multiple key-value requester secrets. Use syntax `secretIndex=value`, `secretIndex` is a public strictly positive integer, `value` is a secret only available in the iApp (Ex: `--requesterSecret 1=foo 42=bar` will set the following environment variables in iApp `IEXEC_REQUESTER_SECRET_1=foo` and `IEXEC_REQUESTER_SECRET_42=bar`).', type: 'string', requiresArg: true, // must be invoked with a value array: true, coerce: coerceRequesterSecret, }); }, handler: (y) => test(y), }) // Build and publish docker image .command({ command: 'deploy', describe: 'Transform you app into a TEE app and deploy it on iExec', builder: (y) => { return y.option('chain', { describe: 'Specify the blockchain on which the iApp will be deployed (overrides defaultChain configuration)', type: 'string', choices: SUPPORTED_CHAINS, }); }, handler: (y) => deploy(y), }) // Run a published docker image .command({ command: 'run <iAppAddress>', describe: 'Run your deployed iApp', builder: (y) => { return y .positional('iAppAddress', { describe: 'The iApp address to run', type: 'string', demandOption: true, }) .option('args', { describe: `Arguments that will be accessible into the iApp. Spaces separates arguments, use quotes to group arguments (Ex: \`--args '"foo bar" baz'\` will interpret "foo bar" as first arg, "bar" as second arg)`, type: 'string', }) .option('protectedData', { describe: 'Specify the protected data address', type: 'string', }) .option('inputFile', { describe: 'Specify one or multiple input files (publicly-accessible URLs). Input files are accessible to the iApp as local files using path specified in environment variables (Ex: `--inputFile https://foo.com/fileA.txt https://bar.io/fileB.json` will download the file at "https://foo.com/fileA.txt" and make it available for the iApp at `$IEXEC_IN/$IEXEC_INPUT_FILE_NAME_1`, same for "https://bar.io/fileB.json" at `$IEXEC_IN/$IEXEC_INPUT_FILE_NAME_2`)', type: 'string', requiresArg: true, // must be invoked with a value array: true, }) .option('requesterSecret', { describe: 'Specify one or multiple key-value requester secrets. Use syntax `secretIndex=value`, `secretIndex` is a public strictly positive integer, `value` is a secret only available in the iApp (Ex: `--requesterSecret 1=foo 42=bar` will set the following environment variables in iApp `IEXEC_REQUESTER_SECRET_1=foo` and `IEXEC_REQUESTER_SECRET_42=bar`).', type: 'string', requiresArg: true, // must be invoked with a value array: true, coerce: coerceRequesterSecret, }) .option('chain', { describe: 'Specify the blockchain on which the iApp is deployed (overrides defaultChain configuration)', type: 'string', choices: SUPPORTED_CHAINS, }); }, handler: (y) => run(y), }) .command({ command: 'debug <taskId>', describe: 'Retrieve detailed execution logs from worker nodes for a specific task', builder: (y) => { return y .positional('taskId', { describe: 'Unique identifier of the task to debug', type: 'string', demandOption: true, }) .option('chain', { describe: 'Specify the blockchain on which the task is registered (overrides defaultChain configuration)', type: 'string', choices: SUPPORTED_CHAINS, }); }, handler: (y) => debug(y), }) .command({ command: 'mock <inputType>', describe: 'Create a mocked input for test', builder: (y) => y.positional('inputType', { describe: 'Type of input to mock', choices: ['protectedData'], }), handler: (y) => { if (y.inputType === 'protectedData') { return mockProtectedData(); } }, }) .command({ command: 'wallet <action>', describe: 'Manage wallet', builder: (y) => y.positional('action', { describe: 'Import a new wallet or select one from the keystore', choices: ['import', 'select'], }), handler: (y) => { if (y.action === 'import') { return walletImport(); } if (y.action === 'select') { return walletSelect(); } }, }) .help() .completion('completion', false) // create hidden "completion" command .alias('help', 'h') .alias('version', 'v') .strict() // show help if iapp is invoked with an invalid subcommand .demandCommand(1, 'Missing subcommand') // show help if iapp is invoked without subcommand is invoked .wrap(yargsInstance.terminalWidth()) // use full terminal size rather than default 80 .parse();