From 8fe734f5d329da6599ae3bb01b361ecb33cba11f Mon Sep 17 00:00:00 2001 From: sakulstra Date: Wed, 1 Nov 2023 20:10:05 +0100 Subject: [PATCH 1/6] fix: better number formatting --- .../__snapshots__/assetListing.spec.ts.snap | 5 +++-- generator/prompts.spec.ts | 19 +++++++++++++++++++ generator/prompts.ts | 18 +++++++++++------- package.json | 1 + yarn.lock | 12 ++++++++++++ 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 generator/prompts.spec.ts diff --git a/generator/features/__snapshots__/assetListing.spec.ts.snap b/generator/features/__snapshots__/assetListing.spec.ts.snap index 336e7ee2e..477214388 100644 --- a/generator/features/__snapshots__/assetListing.spec.ts.snap +++ b/generator/features/__snapshots__/assetListing.spec.ts.snap @@ -166,7 +166,7 @@ contract AaveV3Ethereum_Test_20231023_Test is ProtocolV3TestBase { AaveV3Ethereum_Test_20231023 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 18461057); + vm.createSelectFork(vm.rpcUrl('mainnet'), 18479250); proposal = new AaveV3Ethereum_Test_20231023(); } @@ -219,7 +219,7 @@ contract DeployEthereum is EthereumScript { * command: make deploy-ledger contract=src/20231023_AaveV3Ethereum_Test/Test_20231023.s.sol:CreateProposal chain=mainnet */ contract CreateProposal is EthereumScript { - function run() external broadcast { + function run() external { // create payloads PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1); @@ -230,6 +230,7 @@ contract CreateProposal is EthereumScript { payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum); // create proposal + vm.startBroadcast(); GovV3Helpers.createProposal2_5( payloads, GovV3Helpers.ipfsHashFile(vm, 'src/20231023_AaveV3Ethereum_Test/Test.md') diff --git a/generator/prompts.spec.ts b/generator/prompts.spec.ts new file mode 100644 index 000000000..1e4660dcf --- /dev/null +++ b/generator/prompts.spec.ts @@ -0,0 +1,19 @@ +// sum.test.js +import {expect, describe, it} from 'vitest'; +import {transformNumberToHumanReadable, transformNumberToPercent} from './prompts'; + +describe('prompts', () => { + describe('transforms', () => { + it('transformNumberToHumanReadable: should return a human readable full number', () => { + expect(transformNumberToHumanReadable('1000')).toBe('1,000'); + expect(transformNumberToHumanReadable('1000000')).toBe('1,000,000'); + }); + + it('transformNumberToPercent: should return a human readable % number', () => { + expect(transformNumberToPercent('100')).toBe('100.00 %'); + expect(transformNumberToPercent('3333.33')).toBe('3,333.33 %'); + expect(transformNumberToPercent('0.33')).toBe('0.33 %'); + expect(transformNumberToPercent('0.3')).toBe('0.30 %'); + }); + }); +}); diff --git a/generator/prompts.ts b/generator/prompts.ts index c4c5de71a..768c924d9 100644 --- a/generator/prompts.ts +++ b/generator/prompts.ts @@ -19,17 +19,21 @@ function isAddressOrKeepCurrent(value: string) { } // TRANSFORMS -function transformNumberToPercent(value: string) { +export function transformNumberToPercent(value: string) { if (value && isNumber(value)) { - if (Number(value) <= 9) value = value.padStart(2, '0'); - return value.replace(/(?=(\d{2}$)+(?!\d))/g, '.') + ' %'; + return ( + new Intl.NumberFormat('en-us', { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + }).format(value as unknown as number) + ' %' + ); } return value; } -function transformNumberToHumanReadable(value: string) { +export function transformNumberToHumanReadable(value: string) { if (value && isNumber(value)) { - return value.replace(/(?=(\d{3}$)+(?!\d))/g, '.'); + return new Intl.NumberFormat('en-us').format(BigInt(value)); } return value; } @@ -37,8 +41,8 @@ function transformNumberToHumanReadable(value: string) { // TRANSLATIONS function translateJsPercentToSol(value: string, bpsToRay?: boolean) { if (value === ENGINE_FLAGS.KEEP_CURRENT) return `EngineFlags.KEEP_CURRENT`; - if (bpsToRay) return `_bpsToRay(${value.replace(/(?=(\d{2}$))/g, '_')})`; - return value.replace(/(?=(\d{2}$)+(?!\d))/g, '_'); + if (bpsToRay) return `_bpsToRay(${value.replace(/\./g, '_')})`; + return value.replace(/\./g, '_'); } function translateJsNumberToSol(value: string) { diff --git a/package.json b/package.json index 0bedd9b5c..6c4be8f9b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ }, "homepage": "https://github.com/bgd-labs/aave-proposals-v3#readme", "devDependencies": { + "@types/node": "^20.8.10", "prettier": "2.8.7", "prettier-plugin-solidity": "1.1.3", "vitest": "^0.34.6" diff --git a/yarn.lock b/yarn.lock index e4535fd5f..4e2f99055 100644 --- a/yarn.lock +++ b/yarn.lock @@ -451,6 +451,13 @@ dependencies: undici-types "~5.25.1" +"@types/node@^20.8.10": + version "20.8.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.10.tgz#a5448b895c753ae929c26ce85cab557c6d4a365e" + integrity sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w== + dependencies: + undici-types "~5.26.4" + "@types/normalize-package-data@^2.4.0": version "2.4.2" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz#9b0e3e8533fe5024ad32d6637eb9589988b6fdca" @@ -1789,6 +1796,11 @@ undici-types@~5.25.1: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From d4b3df24ef71ad32ee775fc3311ee34be4b0c44e Mon Sep 17 00:00:00 2001 From: sakulstra Date: Wed, 1 Nov 2023 20:54:54 +0100 Subject: [PATCH 2/6] fix: improve generator --- .../__snapshots__/assetListing.spec.ts.snap | 2 +- generator/features/assetListing.spec.ts | 2 +- generator/features/mocks/configs.ts | 2 +- generator/prompts.spec.ts | 34 +++++++++++++++++-- generator/prompts.ts | 20 +++++++---- .../ACIPhaseII_20231029.s.sol | 2 +- 6 files changed, 48 insertions(+), 14 deletions(-) diff --git a/generator/features/__snapshots__/assetListing.spec.ts.snap b/generator/features/__snapshots__/assetListing.spec.ts.snap index 477214388..0a7cd1e8f 100644 --- a/generator/features/__snapshots__/assetListing.spec.ts.snap +++ b/generator/features/__snapshots__/assetListing.spec.ts.snap @@ -166,7 +166,7 @@ contract AaveV3Ethereum_Test_20231023_Test is ProtocolV3TestBase { AaveV3Ethereum_Test_20231023 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 18479250); + vm.createSelectFork(vm.rpcUrl('mainnet'), 18479264); proposal = new AaveV3Ethereum_Test_20231023(); } diff --git a/generator/features/assetListing.spec.ts b/generator/features/assetListing.spec.ts index 28018c495..cd34175b1 100644 --- a/generator/features/assetListing.spec.ts +++ b/generator/features/assetListing.spec.ts @@ -5,7 +5,7 @@ import {MOCK_OPTIONS, assetListingConfig} from './mocks/configs'; import {generateFiles} from '../generator'; import {FEATURE, PoolConfigs} from '../types'; -describe('feature: assetListing', () => { +describe.skip('feature: assetListing', () => { it('should return reasonable code', () => { const output = assetListing.build(MOCK_OPTIONS, 'AaveV3Ethereum', assetListingConfig); expect(output).toMatchSnapshot(); diff --git a/generator/features/mocks/configs.ts b/generator/features/mocks/configs.ts index 7af85354d..eb0f1ccce 100644 --- a/generator/features/mocks/configs.ts +++ b/generator/features/mocks/configs.ts @@ -1,5 +1,5 @@ import {Options} from '../../types'; -import {Listing} from '../types'; +import {CapsUpdate, Listing} from '../types'; export const MOCK_OPTIONS: Options = { pools: ['AaveV3Ethereum'], diff --git a/generator/prompts.spec.ts b/generator/prompts.spec.ts index 1e4660dcf..f357afc32 100644 --- a/generator/prompts.spec.ts +++ b/generator/prompts.spec.ts @@ -1,8 +1,17 @@ // sum.test.js import {expect, describe, it} from 'vitest'; -import {transformNumberToHumanReadable, transformNumberToPercent} from './prompts'; +import { + transformNumberToHumanReadable, + transformNumberToPercent, + translateJsNumberToSol, + translateJsPercentToSol, +} from './prompts'; describe('prompts', () => { + /** + * Transformers are here to format the input based on a users input + * They do not change the users input value though, the effect is purely visual + */ describe('transforms', () => { it('transformNumberToHumanReadable: should return a human readable full number', () => { expect(transformNumberToHumanReadable('1000')).toBe('1,000'); @@ -10,10 +19,29 @@ describe('prompts', () => { }); it('transformNumberToPercent: should return a human readable % number', () => { - expect(transformNumberToPercent('100')).toBe('100.00 %'); + expect(transformNumberToPercent('100')).toBe('100 %'); expect(transformNumberToPercent('3333.33')).toBe('3,333.33 %'); expect(transformNumberToPercent('0.33')).toBe('0.33 %'); - expect(transformNumberToPercent('0.3')).toBe('0.30 %'); + expect(transformNumberToPercent('0.3')).toBe('0.3 %'); + }); + }); + + /** + * Translates, translate the js input value to solidity + */ + describe('translate', () => { + it('translateJsNumberToSol: should properly translate values', () => { + expect(translateJsNumberToSol('0')).toBe('0'); + expect(translateJsNumberToSol('1000')).toBe('1_000'); + expect(translateJsNumberToSol('1000000')).toBe('1_000_000'); + }); + + it('translateJsPercentToSol: should properly translate % values', () => { + expect(translateJsPercentToSol('0')).toBe('0'); + expect(translateJsPercentToSol('100')).toBe('100_00'); + expect(translateJsPercentToSol('3333.33')).toBe('3_333_33'); + expect(translateJsPercentToSol('0.33')).toBe('33'); + expect(translateJsPercentToSol('0.3')).toBe('30'); }); }); }); diff --git a/generator/prompts.ts b/generator/prompts.ts index 768c924d9..6b9326f4f 100644 --- a/generator/prompts.ts +++ b/generator/prompts.ts @@ -24,7 +24,6 @@ export function transformNumberToPercent(value: string) { return ( new Intl.NumberFormat('en-us', { maximumFractionDigits: 2, - minimumFractionDigits: 2, }).format(value as unknown as number) + ' %' ); } @@ -39,13 +38,20 @@ export function transformNumberToHumanReadable(value: string) { } // TRANSLATIONS -function translateJsPercentToSol(value: string, bpsToRay?: boolean) { +export function translateJsPercentToSol(value: string, bpsToRay?: boolean) { if (value === ENGINE_FLAGS.KEEP_CURRENT) return `EngineFlags.KEEP_CURRENT`; - if (bpsToRay) return `_bpsToRay(${value.replace(/\./g, '_')})`; - return value.replace(/\./g, '_'); -} - -function translateJsNumberToSol(value: string) { + const formattedValue = new Intl.NumberFormat('en-us', { + maximumFractionDigits: 2, + minimumFractionDigits: 2, + }).format(value as unknown as number); + const _value = ( + Number(value) >= 1 ? formattedValue : formattedValue.replace(/^0\.0*(?=[0-9])/, '') + ).replace(/[\.,]/g, '_'); + if (bpsToRay) return `_bpsToRay(${_value})`; + return _value; +} + +export function translateJsNumberToSol(value: string) { if (value === ENGINE_FLAGS.KEEP_CURRENT) return `EngineFlags.KEEP_CURRENT`; return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, '_'); } diff --git a/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol b/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol index 3248d6160..054f765d5 100644 --- a/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol +++ b/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol @@ -18,7 +18,7 @@ contract DeployEthereum is EthereumScript { IPayloadsControllerCore.ExecutionAction[] memory actions = new IPayloadsControllerCore.ExecutionAction[](1); actions[0] = GovV3Helpers.buildAction(address(payload0)); - + uint256 a = 10; // register action at payloadsController GovV3Helpers.createPayload(actions); } From 0386a17f5f6f29f1c2eb3a45096539347e165948 Mon Sep 17 00:00:00 2001 From: sakulstra Date: Wed, 1 Nov 2023 20:58:12 +0100 Subject: [PATCH 3/6] fix: uncomment test --- generator/features/__snapshots__/assetListing.spec.ts.snap | 2 +- generator/features/assetListing.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/generator/features/__snapshots__/assetListing.spec.ts.snap b/generator/features/__snapshots__/assetListing.spec.ts.snap index 0a7cd1e8f..461957a78 100644 --- a/generator/features/__snapshots__/assetListing.spec.ts.snap +++ b/generator/features/__snapshots__/assetListing.spec.ts.snap @@ -166,7 +166,7 @@ contract AaveV3Ethereum_Test_20231023_Test is ProtocolV3TestBase { AaveV3Ethereum_Test_20231023 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 18479264); + vm.createSelectFork(vm.rpcUrl('mainnet'), 18479488); proposal = new AaveV3Ethereum_Test_20231023(); } diff --git a/generator/features/assetListing.spec.ts b/generator/features/assetListing.spec.ts index cd34175b1..28018c495 100644 --- a/generator/features/assetListing.spec.ts +++ b/generator/features/assetListing.spec.ts @@ -5,7 +5,7 @@ import {MOCK_OPTIONS, assetListingConfig} from './mocks/configs'; import {generateFiles} from '../generator'; import {FEATURE, PoolConfigs} from '../types'; -describe.skip('feature: assetListing', () => { +describe('feature: assetListing', () => { it('should return reasonable code', () => { const output = assetListing.build(MOCK_OPTIONS, 'AaveV3Ethereum', assetListingConfig); expect(output).toMatchSnapshot(); From 3ca0be1f45b3c54c72713ff80926bd50f9708765 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 2 Nov 2023 10:08:12 +0100 Subject: [PATCH 4/6] Update src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol --- src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol b/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol index 054f765d5..55e7016af 100644 --- a/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol +++ b/src/20231029_AaveV3Ethereum_ACIPhaseII/ACIPhaseII_20231029.s.sol @@ -18,7 +18,6 @@ contract DeployEthereum is EthereumScript { IPayloadsControllerCore.ExecutionAction[] memory actions = new IPayloadsControllerCore.ExecutionAction[](1); actions[0] = GovV3Helpers.buildAction(address(payload0)); - uint256 a = 10; // register action at payloadsController GovV3Helpers.createPayload(actions); } From d9e5b42606e371004a549eba63c687ce49999604 Mon Sep 17 00:00:00 2001 From: sakulstra Date: Thu, 2 Nov 2023 13:10:00 +0100 Subject: [PATCH 5/6] fix: add advanced input --- .../__snapshots__/assetListing.spec.ts.snap | 2 +- generator/prompts.spec.ts | 35 ++++++- generator/prompts.ts | 48 ++++++---- generator/prompts/advancedInput.ts | 96 +++++++++++++++++++ package.json | 1 + yarn.lock | 12 +++ 6 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 generator/prompts/advancedInput.ts diff --git a/generator/features/__snapshots__/assetListing.spec.ts.snap b/generator/features/__snapshots__/assetListing.spec.ts.snap index ff226fc4f..fa297e7e4 100644 --- a/generator/features/__snapshots__/assetListing.spec.ts.snap +++ b/generator/features/__snapshots__/assetListing.spec.ts.snap @@ -167,7 +167,7 @@ contract AaveV3Ethereum_Test_20231023_Test is ProtocolV3TestBase { AaveV3Ethereum_Test_20231023 internal proposal; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 18484053); + vm.createSelectFork(vm.rpcUrl('mainnet'), 18484119); proposal = new AaveV3Ethereum_Test_20231023(); } diff --git a/generator/prompts.spec.ts b/generator/prompts.spec.ts index f357afc32..c6e97059c 100644 --- a/generator/prompts.spec.ts +++ b/generator/prompts.spec.ts @@ -1,6 +1,8 @@ -// sum.test.js import {expect, describe, it} from 'vitest'; +import {render} from '@inquirer/testing'; import { + numberInput, + percentInput, transformNumberToHumanReadable, transformNumberToPercent, translateJsNumberToSol, @@ -8,6 +10,37 @@ import { } from './prompts'; describe('prompts', () => { + describe('numberInput', () => { + it('handles "yes"', async () => { + const {answer, events, getScreen} = await render(numberInput, { + message: 'Enter number?', + }); + + expect(getScreen()).toMatchInlineSnapshot('"? Enter number? (KEEP_CURRENT)"'); + + events.type('yes112.3'); + expect(getScreen()).toMatchInlineSnapshot('"? Enter number? 1,123"'); + + events.keypress('enter'); + await expect(answer).resolves.toEqual('1_123'); + }); + }); + + describe('percentInput', () => { + it('handles "yes"', async () => { + const {answer, events, getScreen} = await render(percentInput, { + message: 'Enter number?', + }); + + expect(getScreen()).toMatchInlineSnapshot('"? Enter number? (KEEP_CURRENT)"'); + + events.type('yes12.3'); + expect(getScreen()).toMatchInlineSnapshot('"? Enter number? 12.3 %"'); + + events.keypress('enter'); + await expect(answer).resolves.toEqual('12_30'); + }); + }); /** * Transformers are here to format the input based on a users input * They do not change the users input value though, the effect is purely visual diff --git a/generator/prompts.ts b/generator/prompts.ts index 6b9326f4f..231b89019 100644 --- a/generator/prompts.ts +++ b/generator/prompts.ts @@ -2,6 +2,7 @@ import {checkbox, input, select} from '@inquirer/prompts'; import {ENGINE_FLAGS, PoolIdentifier} from './types'; import {getAssets, getEModes} from './common'; import {Hex, getAddress, isAddress} from 'viem'; +import {advancedInput} from './prompts/advancedInput'; // VALIDATION function isNumber(value: string) { @@ -128,31 +129,38 @@ interface PercentInputPrompt extends GenericPrompt { export type PercentInputValues = typeof ENGINE_FLAGS.KEEP_CURRENT | string; -export async function percentInput({ - message, - disableKeepCurrent, - toRay, -}: PercentInputPrompt): Promise< - T extends true ? PercentInputValues : Exclude -> { - const value = await input({ - message, - transformer: transformNumberToPercent, - validate: disableKeepCurrent ? isNumber : isNumberOrKeepCurrent, - ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT}), - }); +export async function percentInput( + {message, disableKeepCurrent, toRay}: PercentInputPrompt, + opts +): Promise> { + const value = await advancedInput( + { + message, + transformer: transformNumberToPercent, + validate: disableKeepCurrent ? isNumber : isNumberOrKeepCurrent, + ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT}), + pattern: /^[0-9]*\.?[0-9]*$/, + patternError: 'Only full numbers are allowed', + }, + opts + ); return translateJsPercentToSol(value, toRay); } export type NumberInputValues = typeof ENGINE_FLAGS.KEEP_CURRENT | string; -export async function numberInput({message, disableKeepCurrent}: GenericPrompt) { - const value = await input({ - message, - transformer: transformNumberToHumanReadable, - validate: disableKeepCurrent ? isNumber : isNumberOrKeepCurrent, - ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT}), - }); +export async function numberInput({message, disableKeepCurrent}: GenericPrompt, opts) { + const value = await advancedInput( + { + message, + transformer: transformNumberToHumanReadable, + validate: disableKeepCurrent ? isNumber : isNumberOrKeepCurrent, + ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT}), + pattern: /^[0-9]*$/, + patternError: 'Only full numbers are allowed', + }, + opts + ); return translateJsNumberToSol(value); } diff --git a/generator/prompts/advancedInput.ts b/generator/prompts/advancedInput.ts new file mode 100644 index 000000000..70b242508 --- /dev/null +++ b/generator/prompts/advancedInput.ts @@ -0,0 +1,96 @@ +import { + createPrompt, + useState, + useKeypress, + usePrefix, + isEnterKey, + isBackspaceKey, + type PromptConfig, +} from '@inquirer/core'; +import type {} from '@inquirer/type'; +import chalk from 'chalk'; + +type InputConfig = PromptConfig<{ + default?: string; + transformer?: (value: string, {isFinal}: {isFinal: boolean}) => string; + validate?: (value: string) => boolean | string | Promise; + pattern?: RegExp; + patternError?: string; +}>; + +/** + * It's a modified input prompt allowing to specify a pattern + * The input will simply discard any non conform input and show an error + */ +export const advancedInput = createPrompt((config, done) => { + const {validate = () => true, pattern, patternError} = config; + const [status, setStatus] = useState('pending'); + const [defaultValue = '', setDefaultValue] = useState(config.default); + const [errorMsg, setError] = useState(undefined); + const [value, setValue] = useState(''); + + const isLoading = status === 'loading'; + const prefix = usePrefix(isLoading); + + useKeypress(async (key, rl) => { + // Ignore keypress while our prompt is doing other processing. + if (status !== 'pending') { + return; + } + + if (!pattern || pattern?.test(rl.line)) { + if (isEnterKey(key)) { + const answer = value || defaultValue; + setStatus('loading'); + const isValid = await validate(answer); + if (isValid === true) { + setValue(answer); + setStatus('done'); + done(answer); + } else { + // Reset the readline line value to the previous value. On line event, the value + // get cleared, forcing the user to re-enter the value instead of fixing it. + rl.write(value); + setError(isValid || 'You must provide a valid value'); + setStatus('pending'); + } + } else if (isBackspaceKey(key) && !value) { + setDefaultValue(undefined); + } else if (key.name === 'tab' && !value) { + setDefaultValue(undefined); + rl.clearLine(0); // Remove the tab character. + rl.write(defaultValue); + setValue(defaultValue); + } else { + setValue(rl.line); + setError(undefined); + } + } else { + const line = rl.line; + rl.clearLine(0); + rl.write(line.slice(0, -1)); + setError(patternError); + } + }); + + const message = chalk.bold(config.message); + let formattedValue = value; + if (typeof config.transformer === 'function') { + formattedValue = config.transformer(value, {isFinal: status === 'done'}); + } + if (status === 'done') { + formattedValue = chalk.cyan(formattedValue); + } + + let defaultStr = ''; + if (defaultValue && status !== 'done' && !value) { + defaultStr = chalk.dim(` (${defaultValue})`); + } + + let error = ''; + if (errorMsg) { + error = chalk.red(`> ${errorMsg}`); + } + + return [`${prefix} ${message}${defaultStr} ${formattedValue}`, error]; +}); diff --git a/package.json b/package.json index 6c4be8f9b..2ad7b1232 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@bgd-labs/aave-address-book": "^2.8.0", "@bgd-labs/aave-cli": "0.0.27-0a01f2a07efe0ec4c875cf479004d20350235f64.0", "@inquirer/prompts": "^3.2.0", + "@inquirer/testing": "^2.1.8", "commander": "^11.0.0", "tsx": "^3.13.0", "viem": "^1.16.6" diff --git a/yarn.lock b/yarn.lock index 4e2f99055..b087ae6c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -294,6 +294,18 @@ chalk "^4.1.2" figures "^3.2.0" +"@inquirer/testing@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/testing/-/testing-2.1.8.tgz#809dffe6a4891100c54eee02ae99e3cd26b8425e" + integrity sha512-PPCiS5wN/MDjeJRQuMZa3cUDwsVmmB9wwbWNSNoC3ciPUwhX8fHdC0Um/gKMlF7EnaZaPa+AOrA8zLo/zJLjPw== + dependencies: + "@inquirer/type" "^1.1.5" + "@types/mute-stream" "^0.0.2" + "@types/node" "^20.8.2" + ansi-escapes "^4.3.2" + mute-stream "^1.0.0" + strip-ansi "^6.0.1" + "@inquirer/type@^1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.1.5.tgz#b8c171f755859c8159b10e41e1e3a88f0ca99d7f" From d042b1a276572a519259312236268dcc0db6232d Mon Sep 17 00:00:00 2001 From: sakulstra Date: Thu, 2 Nov 2023 13:12:30 +0100 Subject: [PATCH 6/6] fix: add correct error --- generator/prompts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/prompts.ts b/generator/prompts.ts index 231b89019..129bed84c 100644 --- a/generator/prompts.ts +++ b/generator/prompts.ts @@ -140,7 +140,7 @@ export async function percentInput( validate: disableKeepCurrent ? isNumber : isNumberOrKeepCurrent, ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT}), pattern: /^[0-9]*\.?[0-9]*$/, - patternError: 'Only full numbers are allowed', + patternError: 'Only decimal numbers are allowed (e.g. 1.1)', }, opts );