From 084d897a85b87d9e8d8e38039d6e4ea1144a8d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 18:48:56 +0100 Subject: [PATCH 01/14] feat: inital interactive run mode --- packages/discovery/package.json | 5 + .../discovery/src/cli/InteractiveOverrides.ts | 246 +++++++++++++++ .../src/cli/InteractiveOverridesManager.ts | 182 +++++++++++ packages/discovery/src/cli/discoverCommand.ts | 1 + .../discovery/src/cli/getCliParameters.ts | 8 + packages/discovery/src/config/types.ts | 1 + .../discovery/config/DiscoveryOverrides.ts | 3 +- .../config/MutableDiscoveryOverrides.ts | 89 ++++++ .../discovery/src/discovery/runDiscovery.ts | 65 ++-- yarn.lock | 294 +++++++++++++++++- 10 files changed, 854 insertions(+), 40 deletions(-) create mode 100644 packages/discovery/src/cli/InteractiveOverrides.ts create mode 100644 packages/discovery/src/cli/InteractiveOverridesManager.ts create mode 100644 packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts diff --git a/packages/discovery/package.json b/packages/discovery/package.json index bf6ef5d0..4a6911c8 100644 --- a/packages/discovery/package.json +++ b/packages/discovery/package.json @@ -19,12 +19,16 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@inquirer/checkbox": "^1.5.0", + "@inquirer/select": "^1.3.1", "@l2beat/backend-tools": "^0.5.1", "@l2beat/discovery-types": "^0.8.0", "chalk": "^4.1.2", + "comment-json": "^4.2.3", "deep-diff": "^1.0.2", "dotenv": "^16.0.3", "ethers": "^5.7.2", + "inquirer": "^8.0.0", "jsonc-parser": "^3.2.0", "lodash": "^4.17.21", "mkdirp": "^3.0.0", @@ -36,6 +40,7 @@ }, "devDependencies": { "@types/deep-diff": "^1.0.2", + "@types/inquirer": "^9.0.7", "@types/lodash": "^4.14.198", "@types/node-fetch": "^2.6.4" } diff --git a/packages/discovery/src/cli/InteractiveOverrides.ts b/packages/discovery/src/cli/InteractiveOverrides.ts new file mode 100644 index 00000000..55980037 --- /dev/null +++ b/packages/discovery/src/cli/InteractiveOverrides.ts @@ -0,0 +1,246 @@ +import checkbox from '@inquirer/checkbox' +import select from '@inquirer/select' +import { ContractParameters, DiscoveryOutput } from '@l2beat/discovery-types' +import chalk from 'chalk' + +import { DiscoveryConfig } from '../discovery/config/DiscoveryConfig' +import { InteractiveOverridesManager } from './InteractiveOverridesManager' + +export { runInteraction } + +async function runInteraction( + output: DiscoveryOutput, + config: DiscoveryConfig, +): Promise { + const iom = new InteractiveOverridesManager(output, config) + const interactiveOverrides = new InteractiveOverrides(iom) + + await interactiveOverrides.run() +} + +class InteractiveOverrides { + constructor(private readonly iom: InteractiveOverridesManager) {} + + async run(): Promise { + const header = chalk.blue.bold('### Interactive config builder ###') + + for (;;) { + const message = 'Options: ' + const choices = [ + { + name: 'Configure contract overrides', + value: 'configure', + }, + { name: 'Flush overrides', value: 'flush' }, + ] as const + + console.log(header) + const choice = await select({ + message, + choices, + }) + + if (choice === 'configure') { + await this.configureContract() + } + + if (choice === 'flush') { + await this.iom.flushOverrides() + break + } + } + } + + async configureContract(): Promise { + for (;;) { + const message = 'Configure contract: ' + + const choices = this.iom.getContracts().map((contract) => { + const name = getNameWithColor(contract) + + return { + name, + value: contract, + } + }) + + const contractOrBack = await selectWithBack(message, choices) + + if (contractOrBack === 'back') { + return + } + + await this.configureOverrides(contractOrBack) + } + } + + async configureOverrides(contract: ContractParameters): Promise { + for (;;) { + const message = 'Configure overrides: ' + const choices = [ + { + name: `🔍 Watch Mode (${chalk.gray('ignoreInWatchMode')})`, + value: 'watchmode', + }, + { + name: `⏭️ Ignore methods (${chalk.gray('ignoreMethods')})`, + value: 'methods', + }, + { + name: `🛑 Ignore Discovery completely (${chalk.gray( + 'ignoreDiscovery', + )})`, + value: 'ignore', + }, + ] as const + + const choice = await selectWithBack(message, choices) + + if (choice === 'watchmode') { + await this.configureWatchMode(contract) + } + + if (choice === 'methods') { + await this.configureIgnoredMethods(contract) + } + + if (choice === 'ignore') { + await this.configureIgnoreDiscovery(contract) + } + + if (choice === 'back') { + return + } + } + } + + async configureWatchMode(contract: ContractParameters): Promise { + const { all, ignored } = this.iom.getWatchMode(contract) + + const message = + 'Values hidden in watch-mode (values in ignoreMethods are excluded):' + + const choices = all.map((property) => ({ + name: property, + value: property, + // Sync already present configuration + checked: ignored.includes(property), + })) + + if (choices.length === 0) { + noValuesWarning(true) + return + } + + const ignoreInWatchMode = await checkbox({ + loop: false, + pageSize: choices.length, + message, + choices, + }) + + this.iom.setOverride(contract, { ignoreInWatchMode }) + } + + async configureIgnoredMethods(contract: ContractParameters): Promise { + const ignoredMethods = this.iom.getIgnoredMethods(contract) + + const message = 'Ignored methods: ' + + const choices = ignoredMethods.all.map((property) => ({ + name: property, + value: property, + // Sync already present configuration + checked: ignoredMethods.ignored.includes(property), + })) + + if (choices.length === 0) { + noValuesWarning() + return + } + + const ignoreMethods = await checkbox({ + loop: false, + pageSize: choices.length, + message, + choices, + }) + + this.iom.setOverride(contract, { ignoreMethods }) + } + + async configureIgnoreDiscovery(contract: ContractParameters): Promise { + const isDiscoveryIgnored = this.iom.getIgnoreDiscovery(contract) + const message = 'Ignore discovery: ' + + const choices = [ + { + name: 'yes', + value: 'ignore', + checked: isDiscoveryIgnored, + }, + ] as const + + const choice = await checkbox({ + message, + choices, + }) + + const ignoreDiscovery = Boolean(choice.length > 0) + + this.iom.setOverride(contract, { ignoreDiscovery }) + } +} + +function getNameWithColor(contract: ContractParameters): string { + const hasName = Boolean(contract.name) + + if (hasName) { + return `${chalk.yellowBright( + contract.name, + )} (${contract.address.toString()}) ` + } + + return `${contract.address.toString()}` +} + +interface Choice { + value: T + name: string +} +async function selectWithBack( + message: string, + choices: Choice[] | readonly Choice[], +): Promise { + const choicesWithBack = [ + ...choices, + { + name: 'Back', + value: 'back', + } as const, + ] + + const answer = await select({ + loop: false, + pageSize: choicesWithBack.length + 1, + message, + choices: choicesWithBack, + }) + + return answer +} + +function noValuesWarning(full?: boolean): void { + let msg = ` + ⚠️ OOPS - no values to manage + - Discovery is set to ignore this contract + - Contract has no values discovered` + + if (full) { + msg += '\n - All values are ignored via ignoreMethods' + } + + msg += '\n' + + console.log(chalk.red(msg)) +} diff --git a/packages/discovery/src/cli/InteractiveOverridesManager.ts b/packages/discovery/src/cli/InteractiveOverridesManager.ts new file mode 100644 index 00000000..f04bfa8c --- /dev/null +++ b/packages/discovery/src/cli/InteractiveOverridesManager.ts @@ -0,0 +1,182 @@ +import { assert } from '@l2beat/backend-tools' +import { ContractParameters, DiscoveryOutput } from '@l2beat/discovery-types' +import { parse, stringify } from 'comment-json' +import * as fs from 'fs/promises' + +import { DiscoveryConfig } from '../discovery/config/DiscoveryConfig' +import { ContractOverrides } from '../discovery/config/DiscoveryOverrides' +import { + MutableDiscoveryOverrides, + MutableOverride, +} from '../discovery/config/MutableDiscoveryOverrides' +import { RawDiscoveryConfig } from '../discovery/config/RawDiscoveryConfig' + +export class InteractiveOverridesManager { + private readonly mutableOverrides: MutableDiscoveryOverrides + + constructor( + private readonly output: DiscoveryOutput, + private readonly config: DiscoveryConfig, + ) { + this.mutableOverrides = new MutableDiscoveryOverrides(this.config.raw) + } + + getContracts(): ContractParameters[] { + return [...this.output.contracts] + } + + getWatchMode(contract: ContractParameters): { + all: string[] + ignored: string[] + } { + const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) + const ignoredMethods = this.getIgnoredMethods(contract) + + if (isDiscoveryIgnored) { + return { + all: [], + ignored: [], + } + } + + const overrides = this.getSafeOverride(contract) + + const allProperties = Object.keys(contract.values ?? {}) + + const ignoredInWatchMode = overrides?.ignoreInWatchMode ?? [] + + const possibleMethods = allProperties + .filter((method) => !this.isCustomHandler(contract, method)) + .filter((method) => !ignoredMethods.ignored.includes(method)) + + return { + all: possibleMethods, + ignored: ignoredInWatchMode, + } + } + + getIgnoredMethods(contract: ContractParameters): { + all: string[] + ignored: string[] + } { + const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) + + if (isDiscoveryIgnored) { + return { + all: [], + ignored: [], + } + } + + const overrides = this.getSafeOverride(contract) + + const methods = overrides?.ignoreMethods ?? [] + + const allProperties = Object.keys(contract.values ?? {}) + const possibleMethods = allProperties.filter( + (method) => !this.isCustomHandler(contract, method), + ) + + const ignored = possibleMethods.filter((method) => methods.includes(method)) + + return { + all: possibleMethods, + ignored, + } + } + + getIgnoreDiscovery(contract: ContractParameters): boolean { + const overrides = this.getSafeOverride(contract) + + return overrides?.ignoreDiscovery ?? false + } + + /** + * Enforce consistency + */ + setOverride(contract: ContractParameters, override: MutableOverride): void { + // Optimistically set overrides + this.mutableOverrides.set(contract, override) + + const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) + const ignoredInWatchMode = this.getWatchMode(contract) + const ignoredMethods = this.getIgnoredMethods(contract) + + /** Consistency rules */ + if (isDiscoveryIgnored) { + this.mutableOverrides.set(contract, { + ignoreDiscovery: true, + ignoreInWatchMode: [], + ignoreMethods: [], + }) + return + } + + // Exclude ignoreMethods from watch mode completely + const validWatchMode = ignoredInWatchMode.ignored.filter( + (method) => !ignoredMethods.ignored.includes(method), + ) + + this.mutableOverrides.set(contract, { + ignoreInWatchMode: validWatchMode, + }) + } + + /** + * Do not replace whole file, just read most-recent raw, replace and save overrides + */ + async flushOverrides(): Promise { + const path = `discovery/${this.output.name}/${this.output.chain}/config.jsonc` + + const fileContents = await fs.readFile(path, 'utf8') + + const parsed = parse(fileContents) as RawDiscoveryConfig | null + + assert(parsed, 'Cannot parse file') + + if (this.mutableOverrides.config.overrides) { + parsed.overrides = this.mutableOverrides.config.overrides + } + + if (this.mutableOverrides.config.names) { + parsed.names = this.mutableOverrides.config.names + } + + await fs.writeFile(path, stringify(parsed, null, 2)) + } + + private getOverrideIdentity(contract: ContractParameters): string { + const hasName = Boolean( + this.mutableOverrides.config.names?.[contract.address.toString()], + ) + + if (hasName) { + return contract.name + } + + return contract.address.toString() + } + + private getSafeOverride( + contract: ContractParameters, + ): ContractOverrides | null { + const addressOrName = this.getOverrideIdentity(contract) + + try { + return this.mutableOverrides.get(addressOrName) + } catch { + return null + } + } + + private isCustomHandler( + contract: ContractParameters, + property: string, + ): boolean { + const addressOrName = this.getOverrideIdentity(contract) + + return Object.keys( + this.mutableOverrides.get(addressOrName).fields ?? {}, + ).includes(property) + } +} diff --git a/packages/discovery/src/cli/discoverCommand.ts b/packages/discovery/src/cli/discoverCommand.ts index 09defcd2..55a23fd5 100644 --- a/packages/discovery/src/cli/discoverCommand.ts +++ b/packages/discovery/src/cli/discoverCommand.ts @@ -72,6 +72,7 @@ export function discover( project: config.project, chain: config.chain, dryRun: config.dryRun === true, + interactive: config.interactive === true, dev: config.dev === true, sourcesFolder: config.sourcesFolder, discoveryFilename: config.discoveryFilename, diff --git a/packages/discovery/src/cli/getCliParameters.ts b/packages/discovery/src/cli/getCliParameters.ts index 175920e0..57138396 100644 --- a/packages/discovery/src/cli/getCliParameters.ts +++ b/packages/discovery/src/cli/getCliParameters.ts @@ -19,6 +19,7 @@ export interface DiscoverCliParameters { project: string chain: string dryRun: boolean + interactive: boolean dev: boolean sourcesFolder?: string discoveryFilename?: string @@ -63,6 +64,7 @@ export function getCliParameters(args = process.argv.slice(2)): CliParameters { let dryRun = false let dev = false + let interactive = false let blockNumber: number | undefined let sourcesFolder: string | undefined let discoveryFilename: string | undefined @@ -72,6 +74,11 @@ export function getCliParameters(args = process.argv.slice(2)): CliParameters { remaining.splice(remaining.indexOf('--dry-run'), 1) } + if (remaining.includes('--interactive')) { + interactive = true + remaining.splice(remaining.indexOf('--dry-run'), 1) + } + if (remaining.includes('--dev')) { dev = true remaining.splice(remaining.indexOf('--dev'), 1) @@ -125,6 +132,7 @@ export function getCliParameters(args = process.argv.slice(2)): CliParameters { mode: 'discover', chain, project, + interactive, dryRun, dev, sourcesFolder, diff --git a/packages/discovery/src/config/types.ts b/packages/discovery/src/config/types.ts index 1f4c9e1d..f674f077 100644 --- a/packages/discovery/src/config/types.ts +++ b/packages/discovery/src/config/types.ts @@ -13,6 +13,7 @@ export interface DiscoveryModuleConfig { readonly project: string readonly chain: string readonly dryRun?: boolean + readonly interactive?: boolean readonly dev?: boolean readonly blockNumber?: number readonly getLogsMaxRange?: number diff --git a/packages/discovery/src/discovery/config/DiscoveryOverrides.ts b/packages/discovery/src/discovery/config/DiscoveryOverrides.ts index d6c64dd9..8c71792e 100644 --- a/packages/discovery/src/discovery/config/DiscoveryOverrides.ts +++ b/packages/discovery/src/discovery/config/DiscoveryOverrides.ts @@ -7,7 +7,7 @@ export type ContractOverrides = DiscoveryContract & { } export class DiscoveryOverrides { - private readonly nameToAddress = new Map() + protected readonly nameToAddress = new Map() constructor( public readonly config: Pick, @@ -43,7 +43,6 @@ export class DiscoveryOverrides { *[Symbol.iterator](): IterableIterator { for (const key of Object.keys(this.config.overrides ?? {})) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion yield this.get(key) } } diff --git a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts new file mode 100644 index 00000000..3e1dc343 --- /dev/null +++ b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts @@ -0,0 +1,89 @@ +import { ContractParameters } from '@l2beat/discovery-types' + +import { EthereumAddress } from '../../utils/EthereumAddress' +import { ContractOverrides, DiscoveryOverrides } from './DiscoveryOverrides' + +export type MutableOverride = Pick< + ContractOverrides, + 'ignoreDiscovery' | 'ignoreInWatchMode' | 'ignoreMethods' +> + +export class MutableDiscoveryOverrides extends DiscoveryOverrides { + public set(contract: ContractParameters, override: MutableOverride): void { + const nameOrAddress = this.updateNameToAddress(contract) + + const identifier = this.getIdentifier(nameOrAddress) + + const originalOverride = identifier + ? this.config.overrides?.[identifier] ?? {} + : {} + + if (override.ignoreInWatchMode !== undefined) { + if (override.ignoreInWatchMode.length === 0) { + delete originalOverride.ignoreInWatchMode + } else { + originalOverride.ignoreInWatchMode = override.ignoreInWatchMode + } + } + + if (override.ignoreMethods !== undefined) { + if (override.ignoreMethods.length === 0) { + delete originalOverride.ignoreMethods + } else { + originalOverride.ignoreMethods = override.ignoreMethods + } + } + + if (override.ignoreDiscovery !== undefined) { + if (!override.ignoreDiscovery) { + delete originalOverride.ignoreDiscovery + } else { + originalOverride.ignoreDiscovery = override.ignoreDiscovery + } + } + + if (this.config.overrides === undefined) { + this.config.overrides = {} + } + + this.config.overrides[identifier ?? nameOrAddress] = originalOverride + } + + private getIdentifier( + nameOrAddress: string | EthereumAddress, + ): string | null { + let name: string | undefined + let address: EthereumAddress | undefined + + if (EthereumAddress.check(nameOrAddress.toString())) { + address = EthereumAddress(nameOrAddress.toString()) + name = this.config.names?.[address.toString()] + } else { + address = this.nameToAddress.get(nameOrAddress.toString()) + name = nameOrAddress.toString() + } + + return name ?? address?.toString() ?? null + } + + // Naive update without checks + private updateNameToAddress(contract: ContractParameters): string { + const hasName = Boolean(contract.name) + + if (hasName) { + this.nameToAddress.set(contract.name, contract.address) + + if (this.config.names === undefined) { + this.config.names = { + [contract.address.toString()]: contract.name, + } + } else { + this.config.names[contract.address.toString()] = contract.name + } + } + + const addressOrName = hasName ? contract.name : contract.address.toString() + + return addressOrName + } +} diff --git a/packages/discovery/src/discovery/runDiscovery.ts b/packages/discovery/src/discovery/runDiscovery.ts index f11f5492..207ac343 100644 --- a/packages/discovery/src/discovery/runDiscovery.ts +++ b/packages/discovery/src/discovery/runDiscovery.ts @@ -1,6 +1,7 @@ import { DiscoveryOutput } from '@l2beat/discovery-types' import { providers } from 'ethers' +import { runInteraction } from '../cli/InteractiveOverrides' import { DiscoveryModuleConfig } from '../config/types' import { EtherscanLikeClient } from '../utils/EtherscanLikeClient' import { AddressAnalyzer, Analysis } from './analysis/AddressAnalyzer' @@ -9,7 +10,6 @@ import { DiscoveryConfig } from './config/DiscoveryConfig' import { DiscoveryLogger } from './DiscoveryLogger' import { DiscoveryEngine } from './engine/DiscoveryEngine' import { HandlerExecutor } from './handlers/HandlerExecutor' -import { diffDiscovery } from './output/diffDiscovery' import { saveDiscoveryResult } from './output/saveDiscoveryResult' import { toDiscoveryOutput } from './output/toDiscoveryOutput' import { MulticallClient } from './provider/multicall/MulticallClient' @@ -69,37 +69,42 @@ export async function dryRunDiscovery( config.project, config.chain, ) - - const [discovered, discoveredYesterday] = await Promise.all([ - justDiscover( - provider, - etherscanClient, - multicallConfig, - projectConfig, - blockNumber, - config.getLogsMaxRange, - ), - justDiscover( - provider, - etherscanClient, - multicallConfig, - projectConfig, - blockNumberYesterday, - config.getLogsMaxRange, - ), - ]) - - const diff = diffDiscovery( - discoveredYesterday.contracts, - discovered.contracts, - projectConfig, + const discovered = await configReader.readDiscovery( + config.project, + config.chain, ) - if (diff.length > 0) { - console.log(JSON.stringify(diff, null, 2)) - } else { - console.log('No changes!') - } + // const [discovered, discoveredYesterday] = await Promise.all([ + // justDiscover( + // provider, + // etherscanClient, + // multicallConfig, + // projectConfig, + // blockNumber, + // config.getLogsMaxRange, + // ), + // justDiscover( + // provider, + // etherscanClient, + // multicallConfig, + // projectConfig, + // blockNumberYesterday, + // config.getLogsMaxRange, + // ), + // ]) + + // const diff = diffDiscovery( + // discoveredYesterday.contracts, + // discovered.contracts, + // projectConfig, + // ) + + await runInteraction(discovered, projectConfig) + // if (diff.length > 0) { + // console.log(JSON.stringify(diff, null, 2)) + // } else { + // console.log('No changes!') + // } } export async function justDiscover( diff --git a/yarn.lock b/yarn.lock index fc062f85..a440c80f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -745,6 +745,53 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@inquirer/checkbox@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-1.5.0.tgz#05869b4ee81e2c8d523799ef350d57cabd556bfa" + integrity sha512-3cKJkW1vIZAs4NaS0reFsnpAjP0azffYII4I2R7PTI7ZTMg5Y1at4vzXccOH3762b2c2L4drBhpJpf9uiaGNxA== + dependencies: + "@inquirer/core" "^5.1.1" + "@inquirer/type" "^1.1.5" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + figures "^3.2.0" + +"@inquirer/core@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-5.1.1.tgz#849d4846aea68371c133df6ec9059f5e5bd30d30" + integrity sha512-IuJyZQUg75+L5AmopgnzxYrgcU6PJKL0hoIs332G1Gv55CnmZrhG6BzNOeZ5sOsTi1YCGOopw4rYICv74ejMFg== + dependencies: + "@inquirer/type" "^1.1.5" + "@types/mute-stream" "^0.0.4" + "@types/node" "^20.9.0" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + cli-spinners "^2.9.1" + cli-width "^4.1.0" + figures "^3.2.0" + mute-stream "^1.0.0" + run-async "^3.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + +"@inquirer/select@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-1.3.1.tgz#b10bb8d4ba72f08eb887b3d948eb734d680897c6" + integrity sha512-EgOPHv7XOHEqiBwBJTyiMg9r57ySyW4oyYCumGp+pGyOaXQaLb2kTnccWI6NFd9HSi5kDJhF7YjA+3RfMQJ2JQ== + dependencies: + "@inquirer/core" "^5.1.1" + "@inquirer/type" "^1.1.5" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + figures "^3.2.0" + +"@inquirer/type@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.1.5.tgz#b8c171f755859c8159b10e41e1e3a88f0ca99d7f" + integrity sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -907,6 +954,14 @@ resolved "https://registry.yarnpkg.com/@types/deep-diff/-/deep-diff-1.0.2.tgz#36f1291f0aead8aceb847cde6f07ae613a78ac4f" integrity sha512-WD2O611C7Oz7RSwKbSls8LaznKfWfXh39CHY9Amd8FhQz+NJRe20nUHhYpOopVq9M2oqDZd4L6AzqJIXQycxiA== +"@types/inquirer@^9.0.7": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-9.0.7.tgz#61bb8d0e42f038b9a1738b08fba7fa98ad9b4b24" + integrity sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g== + dependencies: + "@types/through" "*" + rxjs "^7.2.0" + "@types/is-ci@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/is-ci/-/is-ci-3.0.0.tgz#7e8910af6857601315592436f030aaa3ed9783c3" @@ -959,6 +1014,13 @@ resolved "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz" integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + "@types/node-fetch@^2.6.4": version "2.6.4" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.4.tgz#1bc3a26de814f6bf466b25aeb1473fa1afe6a660" @@ -982,6 +1044,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz" integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== +"@types/node@^20.9.0": + version "20.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.6.tgz#6adf4241460e28be53836529c033a41985f85b6e" + integrity sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q== + dependencies: + undici-types "~5.26.4" + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -997,6 +1066,18 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== +"@types/through@*": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.33.tgz#14ebf599320e1c7851e7d598149af183c6b9ea56" + integrity sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ== + dependencies: + "@types/node" "*" + +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" @@ -1160,6 +1241,13 @@ ansi-colors@^4.1.1, ansi-colors@^4.1.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-regex@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" @@ -1259,6 +1347,11 @@ array-includes@^3.1.5, array-includes@^3.1.6: get-intrinsic "^1.1.3" is-string "^1.0.7" +array-timsort@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" @@ -1315,6 +1408,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -1332,6 +1430,15 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bluebird@^3.5.1: version "3.7.2" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" @@ -1386,6 +1493,14 @@ browser-stdout@1.3.1: resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + cacache@^15.2.0: version "15.3.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" @@ -1451,7 +1566,7 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1494,6 +1609,28 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0, cli-spinners@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" @@ -1571,6 +1708,17 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +comment-json@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.2.3.tgz#50b487ebbf43abe44431f575ebda07d30d015365" + integrity sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw== + dependencies: + array-timsort "^1.0.3" + core-util-is "^1.0.3" + esprima "^4.0.1" + has-own-prop "^2.0.0" + repeat-string "^1.6.1" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -1581,6 +1729,11 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== +core-util-is@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" @@ -2108,7 +2261,7 @@ espree@^9.5.2: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -2196,7 +2349,7 @@ extendable-error@^0.1.5: resolved "https://registry.yarnpkg.com/extendable-error/-/extendable-error-0.1.7.tgz#60b9adf206264ac920058a7395685ae4670c2b96" integrity sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg== -external-editor@^3.1.0: +external-editor@^3.0.3, external-editor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== @@ -2238,6 +2391,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +figures@^3.0.0, figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -2555,6 +2715,11 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-own-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af" + integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ== + has-property-descriptors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" @@ -2666,6 +2831,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.0: version "5.2.4" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" @@ -2707,6 +2877,27 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inquirer@^8.0.0: + version "8.2.6" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" + integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^6.0.1" + internal-slot@^1.0.3, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz" @@ -2805,6 +2996,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -3071,7 +3267,7 @@ lodash@^4.17.21, lodash@^4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@4.1.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -3192,6 +3388,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -3358,6 +3559,16 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + nanoid@3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz" @@ -3527,6 +3738,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" @@ -3539,6 +3757,21 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3806,7 +4039,7 @@ read-yaml-file@^1.1.0: pify "^4.0.1" strip-bom "^3.0.0" -readable-stream@^3.6.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -3844,6 +4077,11 @@ regexp.prototype.flags@^1.4.3: define-properties "^1.2.0" functions-have-names "^1.2.3" +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" @@ -3882,6 +4120,14 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -3906,6 +4152,16 @@ rimraf@^5.0.0: dependencies: glob "^10.2.5" +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-async@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" + integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -3913,6 +4169,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^7.2.0, rxjs@^7.5.5: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -4021,7 +4284,7 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -4328,7 +4591,7 @@ throat@^4.0.0, throat@^4.1.0: resolved "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz" integrity sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA== -through@2: +through@2, through@^2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -4391,6 +4654,11 @@ tslib@^1.8.1: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" @@ -4433,6 +4701,11 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -4467,6 +4740,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +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== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -4621,7 +4899,7 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" -wrap-ansi@^6.2.0: +wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== From 730b600348e57fdbe298dee933fa8efe73331e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 18:55:16 +0100 Subject: [PATCH 02/14] chore: reorganize file structure --- .../interactive}/InteractiveOverrides.ts | 17 +---- .../InteractiveOverridesManager.ts | 8 +- .../discovery/src/discovery/runDiscovery.ts | 73 ++++++++++--------- 3 files changed, 44 insertions(+), 54 deletions(-) rename packages/discovery/src/{cli => discovery/interactive}/InteractiveOverrides.ts (91%) rename packages/discovery/src/{cli => discovery/interactive}/InteractiveOverridesManager.ts (94%) diff --git a/packages/discovery/src/cli/InteractiveOverrides.ts b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts similarity index 91% rename from packages/discovery/src/cli/InteractiveOverrides.ts rename to packages/discovery/src/discovery/interactive/InteractiveOverrides.ts index 55980037..edc27301 100644 --- a/packages/discovery/src/cli/InteractiveOverrides.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts @@ -1,24 +1,11 @@ import checkbox from '@inquirer/checkbox' import select from '@inquirer/select' -import { ContractParameters, DiscoveryOutput } from '@l2beat/discovery-types' +import { ContractParameters } from '@l2beat/discovery-types' import chalk from 'chalk' -import { DiscoveryConfig } from '../discovery/config/DiscoveryConfig' import { InteractiveOverridesManager } from './InteractiveOverridesManager' -export { runInteraction } - -async function runInteraction( - output: DiscoveryOutput, - config: DiscoveryConfig, -): Promise { - const iom = new InteractiveOverridesManager(output, config) - const interactiveOverrides = new InteractiveOverrides(iom) - - await interactiveOverrides.run() -} - -class InteractiveOverrides { +export class InteractiveOverrides { constructor(private readonly iom: InteractiveOverridesManager) {} async run(): Promise { diff --git a/packages/discovery/src/cli/InteractiveOverridesManager.ts b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts similarity index 94% rename from packages/discovery/src/cli/InteractiveOverridesManager.ts rename to packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts index f04bfa8c..9285dda1 100644 --- a/packages/discovery/src/cli/InteractiveOverridesManager.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts @@ -3,13 +3,13 @@ import { ContractParameters, DiscoveryOutput } from '@l2beat/discovery-types' import { parse, stringify } from 'comment-json' import * as fs from 'fs/promises' -import { DiscoveryConfig } from '../discovery/config/DiscoveryConfig' -import { ContractOverrides } from '../discovery/config/DiscoveryOverrides' +import { DiscoveryConfig } from '../config/DiscoveryConfig' +import { ContractOverrides } from '../config/DiscoveryOverrides' import { MutableDiscoveryOverrides, MutableOverride, -} from '../discovery/config/MutableDiscoveryOverrides' -import { RawDiscoveryConfig } from '../discovery/config/RawDiscoveryConfig' +} from '../config/MutableDiscoveryOverrides' +import { RawDiscoveryConfig } from '../config/RawDiscoveryConfig' export class InteractiveOverridesManager { private readonly mutableOverrides: MutableDiscoveryOverrides diff --git a/packages/discovery/src/discovery/runDiscovery.ts b/packages/discovery/src/discovery/runDiscovery.ts index 207ac343..a568e509 100644 --- a/packages/discovery/src/discovery/runDiscovery.ts +++ b/packages/discovery/src/discovery/runDiscovery.ts @@ -1,7 +1,6 @@ import { DiscoveryOutput } from '@l2beat/discovery-types' import { providers } from 'ethers' -import { runInteraction } from '../cli/InteractiveOverrides' import { DiscoveryModuleConfig } from '../config/types' import { EtherscanLikeClient } from '../utils/EtherscanLikeClient' import { AddressAnalyzer, Analysis } from './analysis/AddressAnalyzer' @@ -10,6 +9,9 @@ import { DiscoveryConfig } from './config/DiscoveryConfig' import { DiscoveryLogger } from './DiscoveryLogger' import { DiscoveryEngine } from './engine/DiscoveryEngine' import { HandlerExecutor } from './handlers/HandlerExecutor' +import { InteractiveOverrides } from './interactive/InteractiveOverrides' +import { InteractiveOverridesManager } from './interactive/InteractiveOverridesManager' +import { diffDiscovery } from './output/diffDiscovery' import { saveDiscoveryResult } from './output/saveDiscoveryResult' import { toDiscoveryOutput } from './output/toDiscoveryOutput' import { MulticallClient } from './provider/multicall/MulticallClient' @@ -69,42 +71,43 @@ export async function dryRunDiscovery( config.project, config.chain, ) - const discovered = await configReader.readDiscovery( - config.project, - config.chain, + + const [discovered, discoveredYesterday] = await Promise.all([ + justDiscover( + provider, + etherscanClient, + multicallConfig, + projectConfig, + blockNumber, + config.getLogsMaxRange, + ), + justDiscover( + provider, + etherscanClient, + multicallConfig, + projectConfig, + blockNumberYesterday, + config.getLogsMaxRange, + ), + ]) + + const diff = diffDiscovery( + discoveredYesterday.contracts, + discovered.contracts, + projectConfig, ) - // const [discovered, discoveredYesterday] = await Promise.all([ - // justDiscover( - // provider, - // etherscanClient, - // multicallConfig, - // projectConfig, - // blockNumber, - // config.getLogsMaxRange, - // ), - // justDiscover( - // provider, - // etherscanClient, - // multicallConfig, - // projectConfig, - // blockNumberYesterday, - // config.getLogsMaxRange, - // ), - // ]) - - // const diff = diffDiscovery( - // discoveredYesterday.contracts, - // discovered.contracts, - // projectConfig, - // ) - - await runInteraction(discovered, projectConfig) - // if (diff.length > 0) { - // console.log(JSON.stringify(diff, null, 2)) - // } else { - // console.log('No changes!') - // } + if (diff.length > 0) { + console.log(JSON.stringify(diff, null, 2)) + } else { + console.log('No changes!') + } + + if (config.interactive) { + const iom = new InteractiveOverridesManager(discovered, projectConfig) + const interactiveOverrides = new InteractiveOverrides(iom) + await interactiveOverrides.run() + } } export async function justDiscover( From 3b6cc3e0bf8f1b3de6f7101030239e41a9b149b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 18:56:40 +0100 Subject: [PATCH 03/14] test: fix issues with lacking props --- packages/discovery/src/cli/getCliParameters.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/discovery/src/cli/getCliParameters.test.ts b/packages/discovery/src/cli/getCliParameters.test.ts index 21729fee..c64e7d6a 100644 --- a/packages/discovery/src/cli/getCliParameters.test.ts +++ b/packages/discovery/src/cli/getCliParameters.test.ts @@ -35,6 +35,7 @@ describe(getCliParameters.name, () => { chain: 'ethereum', project: 'foo', dryRun: false, + interactive: false, dev: false, sourcesFolder: undefined, discoveryFilename: undefined, @@ -49,6 +50,7 @@ describe(getCliParameters.name, () => { chain: 'ethereum', project: 'foo', dryRun: true, + interactive: false, dev: false, sourcesFolder: undefined, discoveryFilename: undefined, @@ -70,6 +72,7 @@ describe(getCliParameters.name, () => { chain: 'ethereum', project: 'foo', dryRun: false, + interactive: false, dev: true, sourcesFolder: '.code@1234', discoveryFilename: 'discovery@1234', @@ -87,7 +90,7 @@ describe(getCliParameters.name, () => { expect(cli).toEqual({ mode: 'help', error: 'Too many arguments' }) }) - it('discover ethereum --block-number=5678 foo --dry-run', () => { + it('discover ethereum --block-number=5678 foo --dry-run --interactive', () => { const cli = getCliParameters([ 'discover', 'ethereum', @@ -100,6 +103,7 @@ describe(getCliParameters.name, () => { chain: 'ethereum', project: 'foo', dryRun: true, + interactive: true, dev: false, sourcesFolder: undefined, discoveryFilename: undefined, From 0fa65070fc524cb7eb7c8588f91932c188985a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 18:57:38 +0100 Subject: [PATCH 04/14] chore: correct cli params parsing --- packages/discovery/src/cli/getCliParameters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/discovery/src/cli/getCliParameters.ts b/packages/discovery/src/cli/getCliParameters.ts index 57138396..e3bb509e 100644 --- a/packages/discovery/src/cli/getCliParameters.ts +++ b/packages/discovery/src/cli/getCliParameters.ts @@ -76,7 +76,7 @@ export function getCliParameters(args = process.argv.slice(2)): CliParameters { if (remaining.includes('--interactive')) { interactive = true - remaining.splice(remaining.indexOf('--dry-run'), 1) + remaining.splice(remaining.indexOf('--interactive'), 1) } if (remaining.includes('--dev')) { From 30d3cf2fc6bede973cac5d56c24c887b6a292146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 18:59:47 +0100 Subject: [PATCH 05/14] test: insert new param into case --- packages/discovery/src/cli/getCliParameters.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/discovery/src/cli/getCliParameters.test.ts b/packages/discovery/src/cli/getCliParameters.test.ts index c64e7d6a..98300872 100644 --- a/packages/discovery/src/cli/getCliParameters.test.ts +++ b/packages/discovery/src/cli/getCliParameters.test.ts @@ -97,6 +97,7 @@ describe(getCliParameters.name, () => { '--block-number=5678', 'foo', '--dry-run', + '--interactive', ]) expect(cli).toEqual({ mode: 'discover', From 8b18dadb30e2afe80dd993a20417e391088a0d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 19:04:22 +0100 Subject: [PATCH 06/14] docs: alter comments in iom --- .../src/discovery/interactive/InteractiveOverridesManager.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts index 9285dda1..b5328262 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts @@ -91,9 +91,6 @@ export class InteractiveOverridesManager { return overrides?.ignoreDiscovery ?? false } - /** - * Enforce consistency - */ setOverride(contract: ContractParameters, override: MutableOverride): void { // Optimistically set overrides this.mutableOverrides.set(contract, override) @@ -102,7 +99,7 @@ export class InteractiveOverridesManager { const ignoredInWatchMode = this.getWatchMode(contract) const ignoredMethods = this.getIgnoredMethods(contract) - /** Consistency rules */ + // Wipe all overrides if discovery is ignored if (isDiscoveryIgnored) { this.mutableOverrides.set(contract, { ignoreDiscovery: true, From 1bcec31e3e89e897445f707f29ec751e31e99d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 19:10:39 +0100 Subject: [PATCH 07/14] chore: pass interactive flag down --- packages/discovery/src/config/config.discovery.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/discovery/src/config/config.discovery.ts b/packages/discovery/src/config/config.discovery.ts index 4cc9c201..65e3e284 100644 --- a/packages/discovery/src/config/config.discovery.ts +++ b/packages/discovery/src/config/config.discovery.ts @@ -31,6 +31,7 @@ export function getDiscoveryCliConfig(cli: CliParameters): DiscoveryCliConfig { project: cli.project, chain: cli.chain, dryRun: cli.dryRun, + interactive: cli.interactive, dev: cli.dev, blockNumber: cli.blockNumber, getLogsMaxRange: chain.rpcGetLogsMaxRange, From 588da41c2f1b62d611135d414ae2858415e59f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 20:22:28 +0100 Subject: [PATCH 08/14] chore: limit page size --- .../discovery/interactive/InteractiveOverrides.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts index edc27301..55e4bc04 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts @@ -6,10 +6,12 @@ import chalk from 'chalk' import { InteractiveOverridesManager } from './InteractiveOverridesManager' export class InteractiveOverrides { + static MAX_PAGE_SIZE = 10 + constructor(private readonly iom: InteractiveOverridesManager) {} async run(): Promise { - const header = chalk.blue.bold('### Interactive config builder ###') + console.log(chalk.blue.bold('### Interactive mode ###')) for (;;) { const message = 'Options: ' @@ -21,7 +23,6 @@ export class InteractiveOverrides { { name: 'Flush overrides', value: 'flush' }, ] as const - console.log(header) const choice = await select({ message, choices, @@ -121,7 +122,7 @@ export class InteractiveOverrides { const ignoreInWatchMode = await checkbox({ loop: false, - pageSize: choices.length, + pageSize: InteractiveOverrides.MAX_PAGE_SIZE, message, choices, }) @@ -148,7 +149,7 @@ export class InteractiveOverrides { const ignoreMethods = await checkbox({ loop: false, - pageSize: choices.length, + pageSize: InteractiveOverrides.MAX_PAGE_SIZE, message, choices, }) @@ -209,7 +210,7 @@ async function selectWithBack( const answer = await select({ loop: false, - pageSize: choicesWithBack.length + 1, + pageSize: InteractiveOverrides.MAX_PAGE_SIZE, message, choices: choicesWithBack, }) @@ -219,7 +220,7 @@ async function selectWithBack( function noValuesWarning(full?: boolean): void { let msg = ` - ⚠️ OOPS - no values to manage + ⚠️ OOPS - no values to manage - check following cases: - Discovery is set to ignore this contract - Contract has no values discovered` From 6eb89b6de5d91968405fc86e269f1c01b7ce091a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 20:23:08 +0100 Subject: [PATCH 09/14] chore: strip empty overrides --- .../interactive/InteractiveOverridesManager.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts index b5328262..14cf040f 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts @@ -132,7 +132,9 @@ export class InteractiveOverridesManager { assert(parsed, 'Cannot parse file') if (this.mutableOverrides.config.overrides) { - parsed.overrides = this.mutableOverrides.config.overrides + parsed.overrides = this.stripEmptyOverrides( + this.mutableOverrides.config.overrides, + ) } if (this.mutableOverrides.config.names) { @@ -142,6 +144,17 @@ export class InteractiveOverridesManager { await fs.writeFile(path, stringify(parsed, null, 2)) } + private stripEmptyOverrides( + overrides: Record, + ): Record { + return Object.fromEntries( + Object.entries(overrides).filter( + ([_, override]) => Object.keys(override).length > 0, + ), + ) + } + + private getOverrideIdentity(contract: ContractParameters): string { const hasName = Boolean( this.mutableOverrides.config.names?.[contract.address.toString()], From 0a2fef0f0af6216d21dd2296771b05a7036c91c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 29 Jan 2024 20:54:08 +0100 Subject: [PATCH 10/14] chore: port ignoreMethods --- .../InteractiveOverridesManager.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts index 14cf040f..dc9cbb31 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts @@ -9,7 +9,10 @@ import { MutableDiscoveryOverrides, MutableOverride, } from '../config/MutableDiscoveryOverrides' -import { RawDiscoveryConfig } from '../config/RawDiscoveryConfig' +import { + DiscoveryContract, + RawDiscoveryConfig, +} from '../config/RawDiscoveryConfig' export class InteractiveOverridesManager { private readonly mutableOverrides: MutableDiscoveryOverrides @@ -45,7 +48,10 @@ export class InteractiveOverridesManager { const ignoredInWatchMode = overrides?.ignoreInWatchMode ?? [] - const possibleMethods = allProperties + // All discovered keys + look ahead for all ignored methods + const possibleMethods = [ + ...new Set([...allProperties, ...ignoredMethods.all]), + ] .filter((method) => !this.isCustomHandler(contract, method)) .filter((method) => !ignoredMethods.ignored.includes(method)) @@ -70,14 +76,17 @@ export class InteractiveOverridesManager { const overrides = this.getSafeOverride(contract) - const methods = overrides?.ignoreMethods ?? [] + const ignoredMethods = overrides?.ignoreMethods ?? [] const allProperties = Object.keys(contract.values ?? {}) - const possibleMethods = allProperties.filter( - (method) => !this.isCustomHandler(contract, method), - ) - const ignored = possibleMethods.filter((method) => methods.includes(method)) + const possibleMethods = [ + ...new Set([...allProperties, ...ignoredMethods]), + ].filter((method) => !this.isCustomHandler(contract, method)) + + const ignored = possibleMethods.filter((method) => + ignoredMethods.includes(method), + ) return { all: possibleMethods, @@ -154,7 +163,6 @@ export class InteractiveOverridesManager { ) } - private getOverrideIdentity(contract: ContractParameters): string { const hasName = Boolean( this.mutableOverrides.config.names?.[contract.address.toString()], From 626753b6745be08f33c7a2165763b206ad23bb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Tue, 30 Jan 2024 22:25:25 +0100 Subject: [PATCH 11/14] feat: handle comments porting chore: improve menu pages docs: make comment more precise feat: use raw config instead of VO --- .../src/discovery/config/ConfigReader.ts | 34 +++++++ .../config/MutableDiscoveryOverrides.ts | 36 ++++++- .../interactive/InteractiveOverrides.ts | 68 ++++++++++---- .../InteractiveOverridesManager.ts | 94 +++++++++++-------- .../discovery/src/discovery/runDiscovery.ts | 7 +- 5 files changed, 176 insertions(+), 63 deletions(-) diff --git a/packages/discovery/src/discovery/config/ConfigReader.ts b/packages/discovery/src/discovery/config/ConfigReader.ts index 4c6210f3..fae6cde2 100644 --- a/packages/discovery/src/discovery/config/ConfigReader.ts +++ b/packages/discovery/src/discovery/config/ConfigReader.ts @@ -1,5 +1,6 @@ import { assert } from '@l2beat/backend-tools' import { DiscoveryOutput } from '@l2beat/discovery-types' +import { parse as parseWithComments } from 'comment-json' import { readdirSync } from 'fs' import { readFile } from 'fs/promises' import { parse, ParseError } from 'jsonc-parser' @@ -119,4 +120,37 @@ export class ConfigReader { return projects } + + async readRawConfigWithComments( + name: string, + chain: string, + ): Promise { + assert( + fileExistsCaseSensitive(`discovery/${name}`), + 'Project not found, check if case matches', + ) + assert( + fileExistsCaseSensitive(`discovery/${name}/${chain}`), + 'Chain not found in project, check if case matches', + ) + + const contents = await readFile( + `discovery/${name}/${chain}/config.jsonc`, + 'utf-8', + ) + const parsed: unknown = parseWithComments(contents) + + // Parsing via Zod would effectively remove symbols and thus comments + assertDiscoveryConfig(parsed) + + assert(parsed.chain === chain, 'Chain mismatch in config.jsonc') + + return parsed + } +} + +function assertDiscoveryConfig( + config: unknown, +): asserts config is RawDiscoveryConfig { + RawDiscoveryConfig.parse(config) } diff --git a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts index 3e1dc343..4aec48f4 100644 --- a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts +++ b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts @@ -1,13 +1,19 @@ import { ContractParameters } from '@l2beat/discovery-types' +import { assign } from 'comment-json' import { EthereumAddress } from '../../utils/EthereumAddress' import { ContractOverrides, DiscoveryOverrides } from './DiscoveryOverrides' export type MutableOverride = Pick< ContractOverrides, - 'ignoreDiscovery' | 'ignoreInWatchMode' | 'ignoreMethods' + 'ignoreDiscovery' | 'ignoreInWatchMode' | 'ignoreMethods' | 'ignoreRelatives' > +/** + * In-place overrides map with intention to be mutable + * since it is easier to do that this way instead of modification squash + * @notice Re-assignments made via comments-json `assign` which supports both entries with comments (JSONC) and with out them. + */ export class MutableDiscoveryOverrides extends DiscoveryOverrides { public set(contract: ContractParameters, override: MutableOverride): void { const nameOrAddress = this.updateNameToAddress(contract) @@ -22,7 +28,9 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { if (override.ignoreInWatchMode.length === 0) { delete originalOverride.ignoreInWatchMode } else { - originalOverride.ignoreInWatchMode = override.ignoreInWatchMode + assign(originalOverride, { + ignoreInWatchMode: override.ignoreInWatchMode, + }) } } @@ -30,7 +38,15 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { if (override.ignoreMethods.length === 0) { delete originalOverride.ignoreMethods } else { - originalOverride.ignoreMethods = override.ignoreMethods + assign(originalOverride, { ignoreMethods: override.ignoreMethods }) + } + } + + if (override.ignoreRelatives !== undefined) { + if (override.ignoreRelatives.length === 0) { + delete originalOverride.ignoreRelatives + } else { + assign(originalOverride, { ignoreRelatives: override.ignoreRelatives }) } } @@ -38,15 +54,25 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { if (!override.ignoreDiscovery) { delete originalOverride.ignoreDiscovery } else { - originalOverride.ignoreDiscovery = override.ignoreDiscovery + assign(originalOverride, { ignoreRelatives: override.ignoreRelatives }) } } + // Pre-set overrides if they are not set if (this.config.overrides === undefined) { this.config.overrides = {} } - this.config.overrides[identifier ?? nameOrAddress] = originalOverride + // Set override only if it is not empty + if (Object.keys(originalOverride).length > 0) { + assign(this.config.overrides, { + [identifier ?? nameOrAddress]: originalOverride, + }) + // Remove override if it is empty + } else { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.config.overrides[identifier ?? nameOrAddress] + } } private getIdentifier( diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts index 55e4bc04..1a20fbe5 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts @@ -12,29 +12,29 @@ export class InteractiveOverrides { async run(): Promise { console.log(chalk.blue.bold('### Interactive mode ###')) - for (;;) { const message = 'Options: ' const choices = [ { - name: 'Configure contract overrides', + name: '⚙️ Configure contract overrides', value: 'configure', }, - { name: 'Flush overrides', value: 'flush' }, + { name: '💾 Flush overrides & exit', value: 'flush' }, + { name: '🚪 Exit', value: 'exit' }, ] as const - const choice = await select({ message, choices, }) - if (choice === 'configure') { await this.configureContract() } - if (choice === 'flush') { await this.iom.flushOverrides() - break + return + } + if (choice === 'exit') { + return } } } @@ -70,14 +70,16 @@ export class InteractiveOverrides { name: `🔍 Watch Mode (${chalk.gray('ignoreInWatchMode')})`, value: 'watchmode', }, + { + name: `📝 Ignore relatives (${chalk.gray('ignoreRelatives')})`, + value: 'relatives', + }, { name: `⏭️ Ignore methods (${chalk.gray('ignoreMethods')})`, value: 'methods', }, { - name: `🛑 Ignore Discovery completely (${chalk.gray( - 'ignoreDiscovery', - )})`, + name: `🛑 Ignore discovery (${chalk.gray('ignoreDiscovery')})`, value: 'ignore', }, ] as const @@ -88,6 +90,10 @@ export class InteractiveOverrides { await this.configureWatchMode(contract) } + if (choice === 'relatives') { + await this.configureIgnoredRelatives(contract) + } + if (choice === 'methods') { await this.configureIgnoredMethods(contract) } @@ -116,7 +122,7 @@ export class InteractiveOverrides { })) if (choices.length === 0) { - noValuesWarning(true) + noValuesWarning({ withMethods: true }) return } @@ -130,16 +136,41 @@ export class InteractiveOverrides { this.iom.setOverride(contract, { ignoreInWatchMode }) } + async configureIgnoredRelatives(contract: ContractParameters): Promise { + const { possible, ignored } = this.iom.getIgnoredRelatives(contract) + + const message = 'Ignored relatives (values in ignoreMethods are excluded):' + + const choices = possible.map((property) => ({ + name: property, + value: property, + checked: ignored.includes(property), + })) + + if (choices.length === 0) { + noValuesWarning({ withMethods: true }) + return + } + + const ignoredRelatives = await checkbox({ + loop: false, + pageSize: InteractiveOverrides.MAX_PAGE_SIZE, + message, + choices, + }) + + this.iom.setOverride(contract, { ignoreRelatives: ignoredRelatives }) + } + async configureIgnoredMethods(contract: ContractParameters): Promise { - const ignoredMethods = this.iom.getIgnoredMethods(contract) + const { possible, ignored } = this.iom.getIgnoredMethods(contract) const message = 'Ignored methods: ' - const choices = ignoredMethods.all.map((property) => ({ + const choices = possible.map((property) => ({ name: property, value: property, - // Sync already present configuration - checked: ignoredMethods.ignored.includes(property), + checked: ignored.includes(property), })) if (choices.length === 0) { @@ -174,6 +205,7 @@ export class InteractiveOverrides { choices, }) + // Checkbox with only one value, yet array is returned const ignoreDiscovery = Boolean(choice.length > 0) this.iom.setOverride(contract, { ignoreDiscovery }) @@ -203,7 +235,7 @@ async function selectWithBack( const choicesWithBack = [ ...choices, { - name: 'Back', + name: '🏃 Back', value: 'back', } as const, ] @@ -218,13 +250,13 @@ async function selectWithBack( return answer } -function noValuesWarning(full?: boolean): void { +function noValuesWarning(opts?: { withMethods: boolean }): void { let msg = ` ⚠️ OOPS - no values to manage - check following cases: - Discovery is set to ignore this contract - Contract has no values discovered` - if (full) { + if (opts?.withMethods) { msg += '\n - All values are ignored via ignoreMethods' } diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts index dc9cbb31..01619ec3 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts +++ b/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts @@ -1,27 +1,29 @@ -import { assert } from '@l2beat/backend-tools' import { ContractParameters, DiscoveryOutput } from '@l2beat/discovery-types' -import { parse, stringify } from 'comment-json' +import { assign, parse, stringify } from 'comment-json' import * as fs from 'fs/promises' -import { DiscoveryConfig } from '../config/DiscoveryConfig' import { ContractOverrides } from '../config/DiscoveryOverrides' import { MutableDiscoveryOverrides, MutableOverride, } from '../config/MutableDiscoveryOverrides' -import { - DiscoveryContract, - RawDiscoveryConfig, -} from '../config/RawDiscoveryConfig' +import { RawDiscoveryConfig } from '../config/RawDiscoveryConfig' + +interface IgnoreResult { + possible: string[] + ignored: string[] +} export class InteractiveOverridesManager { private readonly mutableOverrides: MutableDiscoveryOverrides constructor( private readonly output: DiscoveryOutput, - private readonly config: DiscoveryConfig, + private readonly rawConfigWithComments: RawDiscoveryConfig, ) { - this.mutableOverrides = new MutableDiscoveryOverrides(this.config.raw) + this.mutableOverrides = new MutableDiscoveryOverrides( + this.rawConfigWithComments, + ) } getContracts(): ContractParameters[] { @@ -50,7 +52,7 @@ export class InteractiveOverridesManager { // All discovered keys + look ahead for all ignored methods const possibleMethods = [ - ...new Set([...allProperties, ...ignoredMethods.all]), + ...new Set([...allProperties, ...ignoredMethods.possible]), ] .filter((method) => !this.isCustomHandler(contract, method)) .filter((method) => !ignoredMethods.ignored.includes(method)) @@ -61,15 +63,42 @@ export class InteractiveOverridesManager { } } - getIgnoredMethods(contract: ContractParameters): { - all: string[] - ignored: string[] - } { + getIgnoredRelatives(contract: ContractParameters): IgnoreResult { const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) + const ignoredMethods = this.getIgnoredMethods(contract) if (isDiscoveryIgnored) { return { - all: [], + possible: [], + ignored: [], + } + } + + const overrides = this.getSafeOverride(contract) + + const allProperties = Object.keys(contract.values ?? {}) + + const ignoredRelatives = overrides?.ignoreRelatives ?? [] + + // All discovered keys + look ahead for all ignored methods + const possibleMethods = [ + ...new Set([...allProperties, ...ignoredMethods.possible]), + ] + .filter((method) => !this.isCustomHandler(contract, method)) + .filter((method) => !ignoredMethods.ignored.includes(method)) + + return { + possible: possibleMethods, + ignored: ignoredRelatives, + } + } + + getIgnoredMethods(contract: ContractParameters): IgnoreResult { + const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) + + if (isDiscoveryIgnored) { + return { + possible: [], ignored: [], } } @@ -84,13 +113,9 @@ export class InteractiveOverridesManager { ...new Set([...allProperties, ...ignoredMethods]), ].filter((method) => !this.isCustomHandler(contract, method)) - const ignored = possibleMethods.filter((method) => - ignoredMethods.includes(method), - ) - return { - all: possibleMethods, - ignored, + possible: possibleMethods, + ignored: ignoredMethods, } } @@ -107,6 +132,7 @@ export class InteractiveOverridesManager { const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) const ignoredInWatchMode = this.getWatchMode(contract) const ignoredMethods = this.getIgnoredMethods(contract) + const ignoredRelatives = this.getIgnoredRelatives(contract) // Wipe all overrides if discovery is ignored if (isDiscoveryIgnored) { @@ -114,17 +140,23 @@ export class InteractiveOverridesManager { ignoreDiscovery: true, ignoreInWatchMode: [], ignoreMethods: [], + ignoreRelatives: [], }) return } - // Exclude ignoreMethods from watch mode completely + // Exclude ignoreMethods from watch mode and relatives completely const validWatchMode = ignoredInWatchMode.ignored.filter( (method) => !ignoredMethods.ignored.includes(method), ) + const validRelatives = ignoredRelatives.ignored.filter( + (method) => !ignoredMethods.ignored.includes(method), + ) + this.mutableOverrides.set(contract, { ignoreInWatchMode: validWatchMode, + ignoreRelatives: validRelatives, }) } @@ -138,31 +170,17 @@ export class InteractiveOverridesManager { const parsed = parse(fileContents) as RawDiscoveryConfig | null - assert(parsed, 'Cannot parse file') - if (this.mutableOverrides.config.overrides) { - parsed.overrides = this.stripEmptyOverrides( - this.mutableOverrides.config.overrides, - ) + assign(parsed, { overrides: this.mutableOverrides.config.overrides }) } if (this.mutableOverrides.config.names) { - parsed.names = this.mutableOverrides.config.names + assign(parsed, { names: this.mutableOverrides.config.names }) } await fs.writeFile(path, stringify(parsed, null, 2)) } - private stripEmptyOverrides( - overrides: Record, - ): Record { - return Object.fromEntries( - Object.entries(overrides).filter( - ([_, override]) => Object.keys(override).length > 0, - ), - ) - } - private getOverrideIdentity(contract: ContractParameters): string { const hasName = Boolean( this.mutableOverrides.config.names?.[contract.address.toString()], diff --git a/packages/discovery/src/discovery/runDiscovery.ts b/packages/discovery/src/discovery/runDiscovery.ts index a568e509..f798a32c 100644 --- a/packages/discovery/src/discovery/runDiscovery.ts +++ b/packages/discovery/src/discovery/runDiscovery.ts @@ -67,7 +67,7 @@ export async function dryRunDiscovery( const BLOCKS_PER_DAY = 86400 / 12 const blockNumberYesterday = blockNumber - BLOCKS_PER_DAY - const projectConfig = await configReader.readConfig( + const rawConfigWitComments = await configReader.readRawConfigWithComments( config.project, config.chain, ) @@ -104,7 +104,10 @@ export async function dryRunDiscovery( } if (config.interactive) { - const iom = new InteractiveOverridesManager(discovered, projectConfig) + const iom = new InteractiveOverridesManager( + discovered, + rawConfigWitComments, + ) const interactiveOverrides = new InteractiveOverrides(iom) await interactiveOverrides.run() } From 8dbfdbe82432952a6a7fb550430a8503f3e20eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Tue, 30 Jan 2024 22:35:36 +0100 Subject: [PATCH 12/14] chore: fix reading parsed config --- packages/discovery/src/discovery/runDiscovery.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/discovery/src/discovery/runDiscovery.ts b/packages/discovery/src/discovery/runDiscovery.ts index f798a32c..b0689665 100644 --- a/packages/discovery/src/discovery/runDiscovery.ts +++ b/packages/discovery/src/discovery/runDiscovery.ts @@ -67,6 +67,11 @@ export async function dryRunDiscovery( const BLOCKS_PER_DAY = 86400 / 12 const blockNumberYesterday = blockNumber - BLOCKS_PER_DAY + const projectConfig = await configReader.readConfig( + config.project, + config.chain, + ) + const rawConfigWitComments = await configReader.readRawConfigWithComments( config.project, config.chain, From d9ebf1546502062f0bef2e2b0d3fda0cedf08008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Mon, 5 Feb 2024 14:17:39 +0100 Subject: [PATCH 13/14] feat: fix overrides keys --- .../config/MutableDiscoveryOverrides.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts index 4aec48f4..db4f8e5b 100644 --- a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts +++ b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts @@ -20,9 +20,7 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { const identifier = this.getIdentifier(nameOrAddress) - const originalOverride = identifier - ? this.config.overrides?.[identifier] ?? {} - : {} + const originalOverride = this.config.overrides?.[identifier] ?? {} if (override.ignoreInWatchMode !== undefined) { if (override.ignoreInWatchMode.length === 0) { @@ -54,7 +52,7 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { if (!override.ignoreDiscovery) { delete originalOverride.ignoreDiscovery } else { - assign(originalOverride, { ignoreRelatives: override.ignoreRelatives }) + assign(originalOverride, { ignoreDiscovery: override.ignoreDiscovery }) } } @@ -66,18 +64,16 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { // Set override only if it is not empty if (Object.keys(originalOverride).length > 0) { assign(this.config.overrides, { - [identifier ?? nameOrAddress]: originalOverride, + [identifier]: originalOverride, }) // Remove override if it is empty } else { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete this.config.overrides[identifier ?? nameOrAddress] + delete this.config.overrides[identifier] } } - private getIdentifier( - nameOrAddress: string | EthereumAddress, - ): string | null { + private getIdentifier(nameOrAddress: string | EthereumAddress): string { let name: string | undefined let address: EthereumAddress | undefined @@ -89,7 +85,8 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { name = nameOrAddress.toString() } - return name ?? address?.toString() ?? null + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return name ?? address!.toString() } // Naive update without checks From ad9dd0930ae4052cecab59f6154699fa4bc440ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Podsiad=C5=82y?= Date: Wed, 7 Feb 2024 10:20:26 +0100 Subject: [PATCH 14/14] feat: simplify code to support diff ignore only --- .../DiscoveryOverridesBuilder.ts} | 97 ++----- .../config/MutableDiscoveryOverrides.ts | 51 +--- .../interactive/InteractiveDiffIgnore.ts | 196 +++++++++++++ .../interactive/InteractiveOverrides.ts | 266 ------------------ .../discovery/src/discovery/runDiscovery.ts | 23 +- 5 files changed, 230 insertions(+), 403 deletions(-) rename packages/discovery/src/discovery/{interactive/InteractiveOverridesManager.ts => config/DiscoveryOverridesBuilder.ts} (59%) create mode 100644 packages/discovery/src/discovery/interactive/InteractiveDiffIgnore.ts delete mode 100644 packages/discovery/src/discovery/interactive/InteractiveOverrides.ts diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts b/packages/discovery/src/discovery/config/DiscoveryOverridesBuilder.ts similarity index 59% rename from packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts rename to packages/discovery/src/discovery/config/DiscoveryOverridesBuilder.ts index 01619ec3..a387c9f6 100644 --- a/packages/discovery/src/discovery/interactive/InteractiveOverridesManager.ts +++ b/packages/discovery/src/discovery/config/DiscoveryOverridesBuilder.ts @@ -2,19 +2,14 @@ import { ContractParameters, DiscoveryOutput } from '@l2beat/discovery-types' import { assign, parse, stringify } from 'comment-json' import * as fs from 'fs/promises' -import { ContractOverrides } from '../config/DiscoveryOverrides' +import { ContractOverrides } from './DiscoveryOverrides' import { MutableDiscoveryOverrides, MutableOverride, -} from '../config/MutableDiscoveryOverrides' -import { RawDiscoveryConfig } from '../config/RawDiscoveryConfig' +} from './MutableDiscoveryOverrides' +import { RawDiscoveryConfig } from './RawDiscoveryConfig' -interface IgnoreResult { - possible: string[] - ignored: string[] -} - -export class InteractiveOverridesManager { +export class DiscoveryOverridesBuilder { private readonly mutableOverrides: MutableDiscoveryOverrides constructor( @@ -30,93 +25,33 @@ export class InteractiveOverridesManager { return [...this.output.contracts] } - getWatchMode(contract: ContractParameters): { - all: string[] - ignored: string[] - } { + getWatchMode(contract: ContractParameters): string[] { const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) - const ignoredMethods = this.getIgnoredMethods(contract) if (isDiscoveryIgnored) { - return { - all: [], - ignored: [], - } + return [] } const overrides = this.getSafeOverride(contract) - const allProperties = Object.keys(contract.values ?? {}) - const ignoredInWatchMode = overrides?.ignoreInWatchMode ?? [] - // All discovered keys + look ahead for all ignored methods - const possibleMethods = [ - ...new Set([...allProperties, ...ignoredMethods.possible]), - ] - .filter((method) => !this.isCustomHandler(contract, method)) - .filter((method) => !ignoredMethods.ignored.includes(method)) - - return { - all: possibleMethods, - ignored: ignoredInWatchMode, - } + return ignoredInWatchMode } - getIgnoredRelatives(contract: ContractParameters): IgnoreResult { - const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) - const ignoredMethods = this.getIgnoredMethods(contract) - - if (isDiscoveryIgnored) { - return { - possible: [], - ignored: [], - } - } - + getIgnoredRelatives(contract: ContractParameters): string[] { const overrides = this.getSafeOverride(contract) - const allProperties = Object.keys(contract.values ?? {}) - const ignoredRelatives = overrides?.ignoreRelatives ?? [] - // All discovered keys + look ahead for all ignored methods - const possibleMethods = [ - ...new Set([...allProperties, ...ignoredMethods.possible]), - ] - .filter((method) => !this.isCustomHandler(contract, method)) - .filter((method) => !ignoredMethods.ignored.includes(method)) - - return { - possible: possibleMethods, - ignored: ignoredRelatives, - } + return ignoredRelatives } - getIgnoredMethods(contract: ContractParameters): IgnoreResult { - const isDiscoveryIgnored = this.getIgnoreDiscovery(contract) - - if (isDiscoveryIgnored) { - return { - possible: [], - ignored: [], - } - } - + getIgnoredMethods(contract: ContractParameters): string[] { const overrides = this.getSafeOverride(contract) - const ignoredMethods = overrides?.ignoreMethods ?? [] - const allProperties = Object.keys(contract.values ?? {}) - - const possibleMethods = [ - ...new Set([...allProperties, ...ignoredMethods]), - ].filter((method) => !this.isCustomHandler(contract, method)) - - return { - possible: possibleMethods, - ignored: ignoredMethods, - } + return ignoredMethods } getIgnoreDiscovery(contract: ContractParameters): boolean { @@ -146,12 +81,12 @@ export class InteractiveOverridesManager { } // Exclude ignoreMethods from watch mode and relatives completely - const validWatchMode = ignoredInWatchMode.ignored.filter( - (method) => !ignoredMethods.ignored.includes(method), + const validWatchMode = ignoredInWatchMode.filter( + (method) => !ignoredMethods.includes(method), ) - const validRelatives = ignoredRelatives.ignored.filter( - (method) => !ignoredMethods.ignored.includes(method), + const validRelatives = ignoredRelatives.filter( + (method) => !ignoredMethods.includes(method), ) this.mutableOverrides.set(contract, { @@ -205,7 +140,7 @@ export class InteractiveOverridesManager { } } - private isCustomHandler( + public isCustomHandler( contract: ContractParameters, property: string, ): boolean { diff --git a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts index db4f8e5b..fe65eb11 100644 --- a/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts +++ b/packages/discovery/src/discovery/config/MutableDiscoveryOverrides.ts @@ -1,7 +1,6 @@ import { ContractParameters } from '@l2beat/discovery-types' import { assign } from 'comment-json' -import { EthereumAddress } from '../../utils/EthereumAddress' import { ContractOverrides, DiscoveryOverrides } from './DiscoveryOverrides' export type MutableOverride = Pick< @@ -10,17 +9,16 @@ export type MutableOverride = Pick< > /** - * In-place overrides map with intention to be mutable - * since it is easier to do that this way instead of modification squash * @notice Re-assignments made via comments-json `assign` which supports both entries with comments (JSONC) and with out them. */ export class MutableDiscoveryOverrides extends DiscoveryOverrides { public set(contract: ContractParameters, override: MutableOverride): void { - const nameOrAddress = this.updateNameToAddress(contract) + const hasName = Boolean(contract.name) - const identifier = this.getIdentifier(nameOrAddress) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const nameOrAddress = hasName ? contract.name : contract.address.toString()! - const originalOverride = this.config.overrides?.[identifier] ?? {} + const originalOverride = this.config.overrides?.[nameOrAddress] ?? {} if (override.ignoreInWatchMode !== undefined) { if (override.ignoreInWatchMode.length === 0) { @@ -64,49 +62,12 @@ export class MutableDiscoveryOverrides extends DiscoveryOverrides { // Set override only if it is not empty if (Object.keys(originalOverride).length > 0) { assign(this.config.overrides, { - [identifier]: originalOverride, + [nameOrAddress]: originalOverride, }) // Remove override if it is empty } else { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete this.config.overrides[identifier] - } - } - - private getIdentifier(nameOrAddress: string | EthereumAddress): string { - let name: string | undefined - let address: EthereumAddress | undefined - - if (EthereumAddress.check(nameOrAddress.toString())) { - address = EthereumAddress(nameOrAddress.toString()) - name = this.config.names?.[address.toString()] - } else { - address = this.nameToAddress.get(nameOrAddress.toString()) - name = nameOrAddress.toString() + delete this.config.overrides[nameOrAddress] } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return name ?? address!.toString() - } - - // Naive update without checks - private updateNameToAddress(contract: ContractParameters): string { - const hasName = Boolean(contract.name) - - if (hasName) { - this.nameToAddress.set(contract.name, contract.address) - - if (this.config.names === undefined) { - this.config.names = { - [contract.address.toString()]: contract.name, - } - } else { - this.config.names[contract.address.toString()] = contract.name - } - } - - const addressOrName = hasName ? contract.name : contract.address.toString() - - return addressOrName } } diff --git a/packages/discovery/src/discovery/interactive/InteractiveDiffIgnore.ts b/packages/discovery/src/discovery/interactive/InteractiveDiffIgnore.ts new file mode 100644 index 00000000..89eeb4c2 --- /dev/null +++ b/packages/discovery/src/discovery/interactive/InteractiveDiffIgnore.ts @@ -0,0 +1,196 @@ +import select from '@inquirer/select' +import { ContractParameters } from '@l2beat/discovery-types' +import chalk from 'chalk' + +import { DiscoveryOverridesBuilder } from '../config/DiscoveryOverridesBuilder' +import { DiscoveryDiff } from '../output/diffDiscovery' + +interface Change { + contract: string + property: string +} + +export class InteractiveDiffIgnore { + constructor(private readonly dob: DiscoveryOverridesBuilder) {} + + async runForDiffs(discoveryDiffs: DiscoveryDiff[]): Promise { + console.log(chalk.green('Starting interactive mode...')) + + // Filter-out diffs without changes + const changesWithDiffs: Change[] = discoveryDiffs.flatMap( + (discoveryDiff) => { + const contract = discoveryDiff.name + ? discoveryDiff.name + : discoveryDiff.address.toString() + + if (!discoveryDiff.diff) { + return [] + } + + return discoveryDiff.diff.map((d) => ({ + contract, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + property: d.key!, + })) + }, + ) + + const rootPropsChanged = getRootPropertiesChanged(changesWithDiffs) + + for (const { property, contract: addressOrName } of rootPropsChanged) { + const contract = this.getContract(addressOrName) + + if (!contract) { + const msg = `Skipping ${chalk.italic.blue( + property, + )} - contract not found` + + console.log(msg) + continue + } + + const prettyContractName = getNameWithColor(contract) + + const isIgnoredInWatchMode = this.dob + .getWatchMode(contract) + .includes(property) + + if (isIgnoredInWatchMode) { + const msg = `Skipping ${chalk.italic.blue( + property, + )} on ${prettyContractName} - ignored in watch mode` + console.log(msg) + continue + } + + const isIgnoredMethod = this.dob + .getIgnoredMethods(contract) + .includes(property) + + if (isIgnoredMethod) { + const msg = `Skipping ${chalk.italic.blue( + property, + )} on ${prettyContractName}- ignored method` + console.log(msg) + continue + } + + const isCustomHandler = this.dob.isCustomHandler(contract, property) + + if (isCustomHandler) { + const msg = `Skipping ${chalk.italic.blue( + property, + )} on ${prettyContractName} - custom handler` + console.log(msg) + continue + } + + await this.configureSingleProperty(contract, property) + } + + console.log(chalk.green('Flushing overrides...')) + await this.dob.flushOverrides() + console.log( + chalk.green( + 'Done! Run prettier over the config file. Re-run the discovery if needed.', + ), + ) + } + + private async configureSingleProperty( + contract: ContractParameters, + property: string, + ): Promise { + const choices = [ + { + name: '👁️ Ignore in watch mode', + value: 'watchmode', + }, + { + name: '🙅 Ignore method', + value: 'method', + }, + { + name: '⏩ Skip', + value: 'skip', + }, + ] as const + + const name = getNameWithColor(contract) + + const message = `Configure ${chalk.italic.blue( + property, + )} visibility on ${name}: ` + + const choose = await select({ + message, + choices, + }) + + if (choose === 'watchmode') { + const currWatchMode = this.dob.getWatchMode(contract) + + this.dob.setOverride(contract, { + ignoreInWatchMode: [...currWatchMode, property], + }) + } + + if (choose === 'method') { + const currIgnoredMethods = this.dob.getIgnoredMethods(contract) + + this.dob.setOverride(contract, { + ignoreMethods: [...currIgnoredMethods, property], + }) + } + + // choose === 'skip' + return + } + + private getContract(nameOrAddress: string): ContractParameters | null { + return this.dob.getContracts().find((c) => c.name === nameOrAddress) ?? null + } +} + +function getNameWithColor(contract: ContractParameters): string { + const hasName = Boolean(contract.name) + + if (hasName) { + return `${chalk.yellowBright( + contract.name, + )} (${contract.address.toString()})` + } + + return `${contract.address.toString()}` +} + +function getRootPropertiesChanged(changes: Change[]): Change[] { + const rootPropertiesChanged = changes + // Double-check if it is a value change + .filter((change) => change.property.startsWith('values.')) + // Strip array index + // values.foo[1] -> values.foo + .map((change) => ({ + ...change, + property: change.property.replace(/\[\d+\]/, ''), + })) + // Strip values. + // values.foo -> foo + .map((change) => ({ + ...change, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + property: change.property.split('.')[1]!, + })) + + const unique = new Map() + + for (const change of rootPropertiesChanged) { + const identifier = `${change.contract}-${change.property}` + if (!unique.has(identifier)) { + // We do not care about nested changes, since we only ignore root properties + unique.set(identifier, change) + } + } + + return Array.from(unique.values()) +} diff --git a/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts b/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts deleted file mode 100644 index 1a20fbe5..00000000 --- a/packages/discovery/src/discovery/interactive/InteractiveOverrides.ts +++ /dev/null @@ -1,266 +0,0 @@ -import checkbox from '@inquirer/checkbox' -import select from '@inquirer/select' -import { ContractParameters } from '@l2beat/discovery-types' -import chalk from 'chalk' - -import { InteractiveOverridesManager } from './InteractiveOverridesManager' - -export class InteractiveOverrides { - static MAX_PAGE_SIZE = 10 - - constructor(private readonly iom: InteractiveOverridesManager) {} - - async run(): Promise { - console.log(chalk.blue.bold('### Interactive mode ###')) - for (;;) { - const message = 'Options: ' - const choices = [ - { - name: '⚙️ Configure contract overrides', - value: 'configure', - }, - { name: '💾 Flush overrides & exit', value: 'flush' }, - { name: '🚪 Exit', value: 'exit' }, - ] as const - const choice = await select({ - message, - choices, - }) - if (choice === 'configure') { - await this.configureContract() - } - if (choice === 'flush') { - await this.iom.flushOverrides() - return - } - if (choice === 'exit') { - return - } - } - } - - async configureContract(): Promise { - for (;;) { - const message = 'Configure contract: ' - - const choices = this.iom.getContracts().map((contract) => { - const name = getNameWithColor(contract) - - return { - name, - value: contract, - } - }) - - const contractOrBack = await selectWithBack(message, choices) - - if (contractOrBack === 'back') { - return - } - - await this.configureOverrides(contractOrBack) - } - } - - async configureOverrides(contract: ContractParameters): Promise { - for (;;) { - const message = 'Configure overrides: ' - const choices = [ - { - name: `🔍 Watch Mode (${chalk.gray('ignoreInWatchMode')})`, - value: 'watchmode', - }, - { - name: `📝 Ignore relatives (${chalk.gray('ignoreRelatives')})`, - value: 'relatives', - }, - { - name: `⏭️ Ignore methods (${chalk.gray('ignoreMethods')})`, - value: 'methods', - }, - { - name: `🛑 Ignore discovery (${chalk.gray('ignoreDiscovery')})`, - value: 'ignore', - }, - ] as const - - const choice = await selectWithBack(message, choices) - - if (choice === 'watchmode') { - await this.configureWatchMode(contract) - } - - if (choice === 'relatives') { - await this.configureIgnoredRelatives(contract) - } - - if (choice === 'methods') { - await this.configureIgnoredMethods(contract) - } - - if (choice === 'ignore') { - await this.configureIgnoreDiscovery(contract) - } - - if (choice === 'back') { - return - } - } - } - - async configureWatchMode(contract: ContractParameters): Promise { - const { all, ignored } = this.iom.getWatchMode(contract) - - const message = - 'Values hidden in watch-mode (values in ignoreMethods are excluded):' - - const choices = all.map((property) => ({ - name: property, - value: property, - // Sync already present configuration - checked: ignored.includes(property), - })) - - if (choices.length === 0) { - noValuesWarning({ withMethods: true }) - return - } - - const ignoreInWatchMode = await checkbox({ - loop: false, - pageSize: InteractiveOverrides.MAX_PAGE_SIZE, - message, - choices, - }) - - this.iom.setOverride(contract, { ignoreInWatchMode }) - } - - async configureIgnoredRelatives(contract: ContractParameters): Promise { - const { possible, ignored } = this.iom.getIgnoredRelatives(contract) - - const message = 'Ignored relatives (values in ignoreMethods are excluded):' - - const choices = possible.map((property) => ({ - name: property, - value: property, - checked: ignored.includes(property), - })) - - if (choices.length === 0) { - noValuesWarning({ withMethods: true }) - return - } - - const ignoredRelatives = await checkbox({ - loop: false, - pageSize: InteractiveOverrides.MAX_PAGE_SIZE, - message, - choices, - }) - - this.iom.setOverride(contract, { ignoreRelatives: ignoredRelatives }) - } - - async configureIgnoredMethods(contract: ContractParameters): Promise { - const { possible, ignored } = this.iom.getIgnoredMethods(contract) - - const message = 'Ignored methods: ' - - const choices = possible.map((property) => ({ - name: property, - value: property, - checked: ignored.includes(property), - })) - - if (choices.length === 0) { - noValuesWarning() - return - } - - const ignoreMethods = await checkbox({ - loop: false, - pageSize: InteractiveOverrides.MAX_PAGE_SIZE, - message, - choices, - }) - - this.iom.setOverride(contract, { ignoreMethods }) - } - - async configureIgnoreDiscovery(contract: ContractParameters): Promise { - const isDiscoveryIgnored = this.iom.getIgnoreDiscovery(contract) - const message = 'Ignore discovery: ' - - const choices = [ - { - name: 'yes', - value: 'ignore', - checked: isDiscoveryIgnored, - }, - ] as const - - const choice = await checkbox({ - message, - choices, - }) - - // Checkbox with only one value, yet array is returned - const ignoreDiscovery = Boolean(choice.length > 0) - - this.iom.setOverride(contract, { ignoreDiscovery }) - } -} - -function getNameWithColor(contract: ContractParameters): string { - const hasName = Boolean(contract.name) - - if (hasName) { - return `${chalk.yellowBright( - contract.name, - )} (${contract.address.toString()}) ` - } - - return `${contract.address.toString()}` -} - -interface Choice { - value: T - name: string -} -async function selectWithBack( - message: string, - choices: Choice[] | readonly Choice[], -): Promise { - const choicesWithBack = [ - ...choices, - { - name: '🏃 Back', - value: 'back', - } as const, - ] - - const answer = await select({ - loop: false, - pageSize: InteractiveOverrides.MAX_PAGE_SIZE, - message, - choices: choicesWithBack, - }) - - return answer -} - -function noValuesWarning(opts?: { withMethods: boolean }): void { - let msg = ` - ⚠️ OOPS - no values to manage - check following cases: - - Discovery is set to ignore this contract - - Contract has no values discovered` - - if (opts?.withMethods) { - msg += '\n - All values are ignored via ignoreMethods' - } - - msg += '\n' - - console.log(chalk.red(msg)) -} diff --git a/packages/discovery/src/discovery/runDiscovery.ts b/packages/discovery/src/discovery/runDiscovery.ts index b0689665..75f354f1 100644 --- a/packages/discovery/src/discovery/runDiscovery.ts +++ b/packages/discovery/src/discovery/runDiscovery.ts @@ -6,11 +6,11 @@ import { EtherscanLikeClient } from '../utils/EtherscanLikeClient' import { AddressAnalyzer, Analysis } from './analysis/AddressAnalyzer' import { ConfigReader } from './config/ConfigReader' import { DiscoveryConfig } from './config/DiscoveryConfig' +import { DiscoveryOverridesBuilder } from './config/DiscoveryOverridesBuilder' import { DiscoveryLogger } from './DiscoveryLogger' import { DiscoveryEngine } from './engine/DiscoveryEngine' import { HandlerExecutor } from './handlers/HandlerExecutor' -import { InteractiveOverrides } from './interactive/InteractiveOverrides' -import { InteractiveOverridesManager } from './interactive/InteractiveOverridesManager' +import { InteractiveDiffIgnore } from './interactive/InteractiveDiffIgnore' import { diffDiscovery } from './output/diffDiscovery' import { saveDiscoveryResult } from './output/saveDiscoveryResult' import { toDiscoveryOutput } from './output/toDiscoveryOutput' @@ -104,18 +104,19 @@ export async function dryRunDiscovery( if (diff.length > 0) { console.log(JSON.stringify(diff, null, 2)) + + if (config.interactive) { + const dob = new DiscoveryOverridesBuilder( + discovered, + rawConfigWitComments, + ) + const interactiveDiffIgnore = new InteractiveDiffIgnore(dob) + + await interactiveDiffIgnore.runForDiffs(diff) + } } else { console.log('No changes!') } - - if (config.interactive) { - const iom = new InteractiveOverridesManager( - discovered, - rawConfigWitComments, - ) - const interactiveOverrides = new InteractiveOverrides(iom) - await interactiveOverrides.run() - } } export async function justDiscover(