From dc818726e1107d056940df7c4d3d897591b92738 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:00:13 +0100 Subject: [PATCH 1/6] feat: add "iapp chain select" command --- cli/src/cmd/chain-select.ts | 19 +++++++++++++++++++ cli/src/index.ts | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 cli/src/cmd/chain-select.ts 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') From 5dcc738bb6fbf1608db25e97897f677091e41fa2 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:10:28 +0100 Subject: [PATCH 2/6] test: add chain select test --- cli/test/iapp.test.ts | 49 ++++++++++++++++++++++++++++++++++++++++++ cli/test/test-utils.ts | 8 ++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index 5539b1f9..6c0df643 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -9,6 +9,8 @@ import { createTestDir, removeTestDir, retry, + readIAppConfig, + sleep, } from './test-utils.ts'; import { fileURLToPath } from 'node:url'; import { readFile, rm, writeFile } from 'node:fs/promises'; @@ -83,6 +85,53 @@ test('iapp init command works', async () => { clear(); }); +describe('iapp chain select', () => { + const projectName = 'test-iapp'; + beforeEach(async () => { + await initIappProject({ + testDir, + projectName, + template: 'JavaScript', + projectType: 'Hello World', + }); + }); + + test('select bellecour works', async () => { + const { debug } = await render(IAPP_COMMAND, ['chain select bellecour'], { + cwd: join(testDir, projectName), + }); + await sleep(1000); + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'bellecour'); + }); + + test('select bellecour arbitrum-sepolia-testnet', async () => { + const { debug } = await render( + IAPP_COMMAND, + ['chain select arbitrum-sepolia-testnet'], + { + cwd: join(testDir, projectName), + } + ); + await sleep(1000); + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'arbitrum-sepolia-testnet'); + }); + + test('select bellecour arbitrum-mainnet', async () => { + const { debug } = await render( + IAPP_COMMAND, + ['chain select arbitrum-mainnet'], + { + cwd: join(testDir, projectName), + } + ); + await sleep(1000); + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'arbitrum-mainnet'); + }); +}); + describe('JavaScript iApp', () => { describe('Hello World', () => { describe('iapp test', () => { 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, From 4ffe93d4739fca04c32c772e1f745747e6825b99 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:17:39 +0100 Subject: [PATCH 3/6] test: add tests on init defaults --- cli/test/iapp.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index 6c0df643..b31b1e2e 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -83,6 +83,30 @@ test('iapp init command works', async () => { 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', () => { From e8198bcb4642cc7fa4ae71128f6e88616b6f91e9 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:08:00 +0100 Subject: [PATCH 4/6] test: fix typos in test names --- cli/test/iapp.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index b31b1e2e..2e7fa8ea 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -129,7 +129,7 @@ describe('iapp chain select', () => { assert.strictEqual(config.defaultChain, 'bellecour'); }); - test('select bellecour arbitrum-sepolia-testnet', async () => { + test('select arbitrum-sepolia-testnet works', async () => { const { debug } = await render( IAPP_COMMAND, ['chain select arbitrum-sepolia-testnet'], @@ -142,7 +142,7 @@ describe('iapp chain select', () => { assert.strictEqual(config.defaultChain, 'arbitrum-sepolia-testnet'); }); - test('select bellecour arbitrum-mainnet', async () => { + test('select arbitrum-mainnet works', async () => { const { debug } = await render( IAPP_COMMAND, ['chain select arbitrum-mainnet'], From b7f9343bbb954b59dcebbfa57d0370a4d3879e60 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:17:00 +0100 Subject: [PATCH 5/6] test: refactor retry instead of sleep --- cli/test/iapp.test.ts | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index 2e7fa8ea..dd031b45 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -10,7 +10,6 @@ import { removeTestDir, retry, readIAppConfig, - sleep, } from './test-utils.ts'; import { fileURLToPath } from 'node:url'; import { readFile, rm, writeFile } from 'node:fs/promises'; @@ -124,9 +123,16 @@ describe('iapp chain select', () => { const { debug } = await render(IAPP_COMMAND, ['chain select bellecour'], { cwd: join(testDir, projectName), }); - await sleep(1000); - const config = await readIAppConfig(join(testDir, projectName)); - assert.strictEqual(config.defaultChain, 'bellecour'); + 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 () => { @@ -137,9 +143,16 @@ describe('iapp chain select', () => { cwd: join(testDir, projectName), } ); - await sleep(1000); - const config = await readIAppConfig(join(testDir, projectName)); - assert.strictEqual(config.defaultChain, 'arbitrum-sepolia-testnet'); + 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 () => { @@ -150,9 +163,16 @@ describe('iapp chain select', () => { cwd: join(testDir, projectName), } ); - await sleep(1000); - const config = await readIAppConfig(join(testDir, projectName)); - assert.strictEqual(config.defaultChain, 'arbitrum-mainnet'); + await retry( + async () => { + const config = await readIAppConfig(join(testDir, projectName)); + assert.strictEqual(config.defaultChain, 'arbitrum-mainnet'); + }, + { + retries: 10, + delay: 100, + } + ); }); }); From 1dc447db8d1aad547eed0f3f4e602fd00a16cbd4 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:20:48 +0100 Subject: [PATCH 6/6] test: refactor remove debug --- cli/test/iapp.test.ts | 113 ++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 66 deletions(-) diff --git a/cli/test/iapp.test.ts b/cli/test/iapp.test.ts index dd031b45..5ba1a108 100644 --- a/cli/test/iapp.test.ts +++ b/cli/test/iapp.test.ts @@ -31,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(); }); @@ -51,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'], { @@ -68,19 +66,15 @@ 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')); @@ -120,7 +114,7 @@ describe('iapp chain select', () => { }); test('select bellecour works', async () => { - const { debug } = await render(IAPP_COMMAND, ['chain select bellecour'], { + await render(IAPP_COMMAND, ['chain select bellecour'], { cwd: join(testDir, projectName), }); await retry( @@ -136,13 +130,9 @@ describe('iapp chain select', () => { }); test('select arbitrum-sepolia-testnet works', async () => { - const { debug } = await render( - IAPP_COMMAND, - ['chain select arbitrum-sepolia-testnet'], - { - cwd: join(testDir, projectName), - } - ); + await render(IAPP_COMMAND, ['chain select arbitrum-sepolia-testnet'], { + cwd: join(testDir, projectName), + }); await retry( async () => { const config = await readIAppConfig(join(testDir, projectName)); @@ -156,13 +146,9 @@ describe('iapp chain select', () => { }); test('select arbitrum-mainnet works', async () => { - const { debug } = await render( - IAPP_COMMAND, - ['chain select arbitrum-mainnet'], - { - cwd: join(testDir, projectName), - } - ); + await render(IAPP_COMMAND, ['chain select arbitrum-mainnet'], { + cwd: join(testDir, projectName), + }); await retry( async () => { const config = await readIAppConfig(join(testDir, projectName)); @@ -192,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, @@ -208,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 @@ -249,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?'), { @@ -277,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 @@ -320,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, @@ -336,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 @@ -372,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?'), { @@ -400,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 @@ -454,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, @@ -470,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