@iexec/iapp
Version:
A CLI to guide you through the process of building an iExec iApp
206 lines (187 loc) • 6.65 kB
text/typescript
import chalk from 'chalk';
import figlet from 'figlet';
import { mkdir } from 'node:fs/promises';
import { fromError } from 'zod-validation-error';
import { folderExists } from '../utils/fs.utils.js';
import { initIAppWorkspace } from '../utils/initIAppWorkspace.js';
import { getSpinner } from '../cli-helpers/spinner.js';
import { handleCliError } from '../cli-helpers/handleCliError.js';
import { generateWallet } from '../utils/generateWallet.js';
import * as color from '../cli-helpers/color.js';
import { hintBox } from '../cli-helpers/box.js';
import { projectNameSchema } from '../utils/iAppConfigFile.js';
import { TEMPLATES, type TemplateName } from '../config/config.js';
const targetDir = 'hello-world';
export async function init() {
const spinner = getSpinner();
try {
spinner.start('Configuring project...');
spinner.log(
chalk.magenta(
figlet.textSync('IAPP', {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default',
})
)
);
const { projectName } = await spinner.prompt({
type: 'text',
name: 'projectName',
message: `What's your project name? ${color.promptHelper('(A folder with this name will be created)')}`,
initial: targetDir,
validate: (value) => {
try {
projectNameSchema.parse(value);
return true;
} catch (e) {
return fromError(e)
.details.map((issue) => issue.message)
.join('; ');
}
},
});
if (await folderExists(projectName)) {
throw Error(
`Target directory "${projectName}" already exists. Remove it or choose a different name.`
);
}
const INIT_BASIC = 'basic';
const INIT_ADVANCED = 'advanced';
const { template, initType } = await spinner.prompt([
{
type: 'select',
name: 'template',
message: 'Which language do you want to use?',
choices: Object.entries(TEMPLATES).map(([key, val]) => ({
title: val.title,
value: key,
selected: val?.default,
})),
},
{
type: 'select',
name: 'initType',
message: 'What kind of project do you want to init?',
choices: [
{
title: 'Hello World',
value: INIT_BASIC,
selected: true,
description: 'iapp quick start',
},
{
title: 'advanced',
value: INIT_ADVANCED,
description: 'more configuration options for advanced users',
},
],
},
]);
const templateTitle = TEMPLATES[template as TemplateName].title;
const {
useArgs = true,
useProtectedData = true,
useInputFile = false,
useRequesterSecret = false,
useAppSecret = false,
} = initType === INIT_ADVANCED
? await spinner.prompt([
{
type: 'confirm',
name: 'useArgs',
message: `Would you like to use args inside your iApp? ${color.promptHelper(
'(args are public positional arguments, args are provided by users that will run your iApp)'
)}`,
initial: true,
},
{
type: 'confirm',
name: 'useInputFile',
message: `Would you like to use input files inside your iApp? ${color.promptHelper(
'(input files are public files downloaded from the internet, files urls are provided by user that will run your iApp)'
)}`,
initial: false,
},
{
type: 'confirm',
name: 'useRequesterSecret',
message: `Would you like to use requester secrets inside your iApp? ${color.promptHelper(
'(requester secrets are secrets strings, secrets are provided by users that will run your iApp)'
)}`,
initial: false,
},
{
type: 'confirm',
name: 'useProtectedData',
message: `Would you like to use a protected data inside your iApp? ${color.promptHelper(
'(protected data a secret file, the protected data is provided by a third party for users that will run your iApp)'
)}`,
initial: false,
},
{
type: 'confirm',
name: 'useAppSecret',
message: `Would you like to use an app secret inside your iApp? ${color.promptHelper('(app secret is an immutable secret string provisioned once by the iApp owner)')}`,
initial: false,
},
])
: {}; // default
await mkdir(projectName);
process.chdir(projectName);
// Copying simple project files from templates/
spinner.start(`Creating ${templateTitle} app...`);
await initIAppWorkspace({
projectName,
template,
useArgs,
useProtectedData,
useInputFile,
useRequesterSecret,
useAppSecret,
});
spinner.succeed(`${templateTitle} app setup complete.`);
spinner.start('Generating wallet...');
const walletAddress = await generateWallet();
spinner.succeed(`Generated ethereum wallet (${walletAddress})`);
const output = `
${chalk.bold.underline('Steps to Get Started:')}
Navigate to your project folder:
${color.command(`$ cd ${projectName.split(' ').length > 1 ? `"${projectName}"` : `${projectName}`}`)}
${color.emphasis('Make your changes in the')} ${color.file(TEMPLATES[template as TemplateName]?.mainFile)} ${color.emphasis('file')}.
-1- Test your iApp locally:
${color.command('$ iapp test')}${
useArgs
? `
${color.comment('# with args')}
${color.command('$ iapp test --args your-name')}`
: ''
}${
useInputFile
? `
${color.comment('# with input files')}
${color.command('$ iapp test --inputFile https://ipfs.iex.ec/ipfs/Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u')}`
: ''
}${
useRequesterSecret
? `
${color.comment('# with requester secrets')}
${color.command('$ iapp test --requesterSecret 1=foo 42=bar')}`
: ''
}${
useProtectedData
? `
${color.comment('# with a protected data')}
${color.command('$ iapp test --protectedData default')}`
: ''
}
-2- Deploy your iApp on the iExec protocol:
${color.command('$ iapp deploy')}
-3- Ask an iExec worker to run your confidential iApp:
${color.command('$ iapp run <iapp-address>')}
`;
spinner.log(hintBox(output));
} catch (error) {
handleCliError({ spinner, error });
}
}