diff --git a/cli/src/cmd/chain-select.ts b/cli/src/cmd/chain-select.ts new file mode 100644 index 00000000..cf8136c8 --- /dev/null +++ b/cli/src/cmd/chain-select.ts @@ -0,0 +1,19 @@ +import { handleCliError } from '../cli-helpers/handleCliError.js'; +import { getSpinner } from '../cli-helpers/spinner.js'; +import * as color from '../cli-helpers/color.js'; +import { readIAppConfig, writeIAppConfig } from '../utils/iAppConfigFile.js'; +import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js'; + +export async function chainSelect({ chainName }: { chainName: string }) { + const spinner = getSpinner(); + try { + await goToProjectRoot({ spinner }); + spinner.text = 'Selecting chain'; + const config = await readIAppConfig(); + config.defaultChain = chainName; + await writeIAppConfig(config); + spinner.succeed(`Default chain set to ${color.file(chainName)}`); + } catch (error) { + handleCliError({ spinner, error }); + } +} diff --git a/cli/src/index.ts b/cli/src/index.ts index 891c050e..310fe157 100755 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -12,6 +12,7 @@ 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'; +import { chainSelect } from './cmd/chain-select.js'; await checkPackageUpdate(); @@ -200,6 +201,20 @@ yargsInstance }, }) + .command({ + command: 'chain select ', + describe: 'Select the default blockchain environment to use', + builder: (y) => + y.positional('chainName', { + describe: 'Name of the blockchain to use', + choices: SUPPORTED_CHAINS, + demandOption: true, + }), + handler: (y) => { + return chainSelect(y); + }, + }) + .help() .completion('completion', false) // create hidden "completion" command .alias('help', 'h') diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index 5539b1f9..5ba1a108 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -9,6 +9,7 @@ import { createTestDir, removeTestDir, retry, + readIAppConfig, } from './test-utils.ts'; import { fileURLToPath } from 'node:url'; import { readFile, rm, writeFile } from 'node:fs/promises'; @@ -30,16 +31,15 @@ beforeEach(async (t) => { afterEach(async () => { // remove test directory after each test - // comment the line below to keep the test directories for debugging + // comment the line below to keep the test directories for ging await removeTestDir(testDir); }); test('iapp help command works', async () => { - const { findByText, debug, clear } = await render(IAPP_COMMAND, ['help'], { + const { findByText, clear } = await render(IAPP_COMMAND, ['help'], { cwd: testDir, }); await findByText('iapp [args]'); - // debug(); clear(); }); @@ -50,16 +50,15 @@ test('iapp -v command works', async () => { ); const { version } = packageJson; - const { findByText, debug, clear } = await render(IAPP_COMMAND, ['-v'], { + const { findByText, clear } = await render(IAPP_COMMAND, ['-v'], { cwd: testDir, }); await findByText(version); - // debug(); clear(); }); test('iapp init command works', async () => { - const { findByText, clear, debug, userEvent } = await render( + const { findByText, clear, userEvent } = await render( IAPP_COMMAND, ['init'], { @@ -67,20 +66,100 @@ test('iapp init command works', async () => { } ); await findByText("What's your project name?"); - // debug(); clear(); userEvent.keyboard('[Enter]'); await findByText('Which language do you want to use?'); - // debug(); clear(); userEvent.keyboard('[Enter]'); await findByText('What kind of project do you want to init?'); - // debug(); clear(); userEvent.keyboard('[Enter]'); await findByText('Steps to Get Started:'); - // debug(); clear(); + + const config = await readIAppConfig(join(testDir, 'hello-world')); + // default chain is bellecour + assert.strictEqual( + config.defaultChain, + 'bellecour', + 'defaultChain should be bellecour' + ); + // default project name is hello-world + assert.strictEqual( + config.projectName, + 'hello-world', + 'projectName should be hello-world' + ); + // default template is JavaScript + assert.strictEqual( + config.template, + 'JavaScript', + 'template should be JavaScript' + ); + // default app secret is disabled + assert.strictEqual(config.appSecret, null, 'appSecret should be null'); + // default wallet private key is set + assert(config.walletPrivateKey, 'walletPrivateKey should be set'); +}); + +describe('iapp chain select', () => { + const projectName = 'test-iapp'; + beforeEach(async () => { + await initIappProject({ + testDir, + projectName, + template: 'JavaScript', + projectType: 'Hello World', + }); + }); + + test('select bellecour works', async () => { + await render(IAPP_COMMAND, ['chain select bellecour'], { + cwd: join(testDir, projectName), + }); + await retry( + async () => { + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'bellecour'); + }, + { + retries: 10, + delay: 100, + } + ); + }); + + test('select arbitrum-sepolia-testnet works', async () => { + await render(IAPP_COMMAND, ['chain select arbitrum-sepolia-testnet'], { + cwd: join(testDir, projectName), + }); + await retry( + async () => { + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'arbitrum-sepolia-testnet'); + }, + { + retries: 10, + delay: 100, + } + ); + }); + + test('select arbitrum-mainnet works', async () => { + await render(IAPP_COMMAND, ['chain select arbitrum-mainnet'], { + cwd: join(testDir, projectName), + }); + await retry( + async () => { + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'arbitrum-mainnet'); + }, + { + retries: 10, + delay: 100, + } + ); + }); }); describe('JavaScript iApp', () => { @@ -99,10 +178,13 @@ describe('JavaScript iApp', () => { }); test('iapp test command works', async () => { - const { findByText, debug, clear, userEvent, getStdallStr } = - await render(IAPP_COMMAND, ['test'], { + const { findByText, clear, userEvent, getStdallStr } = await render( + IAPP_COMMAND, + ['test'], + { cwd: join(testDir, projectName), - }); + } + ); // wait for docker build and test run await retry(() => findByText('Would you like to see the app logs?'), { retries: 8, @@ -115,16 +197,12 @@ describe('JavaScript iApp', () => { ); assert.ok(dockerImageIdMatch, 'Docker image ID not found in output'); const dockerImageId = dockerImageIdMatch![0].split('(')[1].slice(0, -1); - - // debug(); clear(); userEvent.keyboard('n'); await findByText('Would you like to see the result?'); - // debug(); clear(); userEvent.keyboard('n'); await findByText('When ready run iapp deploy'); - // debug(); clear(); // check built docker image content @@ -156,21 +234,24 @@ describe('JavaScript iApp', () => { }); test('iapp test command works', async () => { - const { findByText, debug, clear, userEvent, getStdallStr } = - await render(IAPP_COMMAND, ['test'], { + const { findByText, clear, userEvent, getStdallStr } = await render( + IAPP_COMMAND, + ['test'], + { cwd: join(testDir, projectName), - }); + } + ); await findByText('Do you want to attach an app secret to your iApp?'); userEvent.keyboard('y'); - // debug() + // ) clear(); await findByText('What is the app secret?'); userEvent.keyboard('mySuperSecretAppSecret[Enter]'); - // debug() + // ) clear(); await findByText('Do you want to save this app secret to your config?'); userEvent.keyboard('y'); - // debug() + // ) clear(); // wait for docker build and test run await retry(() => findByText('Would you like to see the app logs?'), { @@ -184,16 +265,12 @@ describe('JavaScript iApp', () => { ); assert.ok(dockerImageIdMatch, 'Docker image ID not found in output'); const dockerImageId = dockerImageIdMatch![0].split('(')[1].slice(0, -1); - - // debug(); clear(); userEvent.keyboard('n'); await findByText('Would you like to see the result?'); - // debug(); clear(); userEvent.keyboard('n'); await findByText('When ready run iapp deploy'); - // debug(); clear(); // check built docker image content @@ -227,10 +304,13 @@ describe('Python iApp', () => { }); test('iapp test command works', async () => { - const { findByText, debug, clear, userEvent, getStdallStr } = - await render(IAPP_COMMAND, ['test'], { + const { findByText, clear, userEvent, getStdallStr } = await render( + IAPP_COMMAND, + ['test'], + { cwd: join(testDir, projectName), - }); + } + ); // wait for docker build and test run await retry(() => findByText('Would you like to see the app logs?'), { retries: 8, @@ -243,16 +323,12 @@ describe('Python iApp', () => { ); assert.ok(dockerImageIdMatch, 'Docker image ID not found in output'); const dockerImageId = dockerImageIdMatch![0].split('(')[1].slice(0, -1); - - // debug(); clear(); userEvent.keyboard('n'); await findByText('Would you like to see the result?'); - // debug(); clear(); userEvent.keyboard('n'); await findByText('When ready run iapp deploy'); - // debug(); clear(); // check built docker image content @@ -279,21 +355,24 @@ describe('Python iApp', () => { }); test('iapp test command works', async () => { - const { findByText, debug, clear, userEvent, getStdallStr } = - await render(IAPP_COMMAND, ['test'], { + const { findByText, clear, userEvent, getStdallStr } = await render( + IAPP_COMMAND, + ['test'], + { cwd: join(testDir, projectName), - }); + } + ); await findByText('Do you want to attach an app secret to your iApp?'); userEvent.keyboard('y'); - // debug() + // ) clear(); await findByText('What is the app secret?'); userEvent.keyboard('mySuperSecretAppSecret[Enter]'); - // debug() + // ) clear(); await findByText('Do you want to save this app secret to your config?'); userEvent.keyboard('y'); - // debug() + // ) clear(); // wait for docker build and test run await retry(() => findByText('Would you like to see the app logs?'), { @@ -307,16 +386,12 @@ describe('Python iApp', () => { ); assert.ok(dockerImageIdMatch, 'Docker image ID not found in output'); const dockerImageId = dockerImageIdMatch![0].split('(')[1].slice(0, -1); - - // debug(); clear(); userEvent.keyboard('n'); await findByText('Would you like to see the result?'); - // debug(); clear(); userEvent.keyboard('n'); await findByText('When ready run iapp deploy'); - // debug(); clear(); // check built docker image content @@ -361,10 +436,13 @@ describe('Custom app', () => { 'utf-8' ); - const { findByText, debug, clear, userEvent, getStdallStr } = - await render(IAPP_COMMAND, ['test'], { + const { findByText, clear, userEvent, getStdallStr } = await render( + IAPP_COMMAND, + ['test'], + { cwd: join(testDir, projectName), - }); + } + ); // wait for docker build and test run await retry(() => findByText('Would you like to see the app logs?'), { retries: 8, @@ -377,16 +455,12 @@ describe('Custom app', () => { ); assert.ok(dockerImageIdMatch, 'Docker image ID not found in output'); const dockerImageId = dockerImageIdMatch![0].split('(')[1].slice(0, -1); - - // debug(); clear(); userEvent.keyboard('n'); await findByText('Would you like to see the result?'); - // debug(); clear(); userEvent.keyboard('n'); await findByText('When ready run iapp deploy'); - // debug(); clear(); // check built docker image content diff --git a/cli/test/test-utils.ts b/cli/test/test-utils.ts index 27bd41bc..2b13ec84 100644 --- a/cli/test/test-utils.ts +++ b/cli/test/test-utils.ts @@ -1,6 +1,6 @@ import { render } from 'cli-testing-library'; import assert from 'node:assert'; -import { mkdir, rm } from 'node:fs/promises'; +import { mkdir, readFile, rm } from 'node:fs/promises'; import { join } from 'node:path'; import type { SuiteContext, TestContext } from 'node:test'; @@ -87,6 +87,12 @@ export const initIappProject = async ({ await findByText('Steps to Get Started:'); }; +export const readIAppConfig = async (projectDir: string) => { + const configPath = join(projectDir, 'iapp.config.json'); + const configContent = await readFile(configPath, 'utf-8'); + return JSON.parse(configContent); +}; + export const checkDockerImageContent = async ({ dockerImageId, expectedFiles,