Rollback CLI tools (#79)
* Rollback CLI tools * Refactor * Address PR feedback * Sort deployment history * Rollback specific deployment * Split calldata
This commit is contained in:
parent
1e16d59c23
commit
27d679e1f1
@ -8,3 +8,4 @@
|
||||
# Blacklist tests in lib
|
||||
/lib/test/*
|
||||
# Package specific ignore
|
||||
/lib/scripts/*
|
||||
|
@ -37,7 +37,8 @@
|
||||
"compile:truffle": "truffle compile",
|
||||
"docs:md": "ts-doc-gen --sourceDir='$PROJECT_FILES' --output=$MD_FILE_DIR --fileExtension=mdx --tsconfig=./typedoc-tsconfig.json",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES",
|
||||
"publish:private": "yarn build && gitpkg publish"
|
||||
"publish:private": "yarn build && gitpkg publish",
|
||||
"rollback": "node ./lib/scripts/rollback.js"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector",
|
||||
@ -55,6 +56,7 @@
|
||||
"homepage": "https://github.com/0xProject/protocol/tree/main/contracts/zero-ex",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^5.4.13",
|
||||
"@0x/contract-addresses": "^5.6.0",
|
||||
"@0x/contracts-erc20": "^3.2.12",
|
||||
"@0x/contracts-gen": "^2.0.24",
|
||||
"@0x/contracts-test-utils": "^5.3.15",
|
||||
@ -62,11 +64,15 @@
|
||||
"@0x/sol-compiler": "^4.4.1",
|
||||
"@0x/ts-doc-gen": "^0.0.28",
|
||||
"@0x/tslint-config": "^4.1.3",
|
||||
"@types/isomorphic-fetch": "^0.0.35",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/prompts": "^2.0.9",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"mocha": "^6.2.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"prompts": "^2.4.0",
|
||||
"shx": "^0.2.2",
|
||||
"solhint": "^1.4.1",
|
||||
"truffle": "^5.0.32",
|
||||
|
420
contracts/zero-ex/scripts/rollback.ts
Normal file
420
contracts/zero-ex/scripts/rollback.ts
Normal file
@ -0,0 +1,420 @@
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { constants } from '@0x/contracts-test-utils';
|
||||
import { RPCSubprovider, SupportedProvider, Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { AbiEncoder, BigNumber, logUtils, providerUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { MethodAbi } from 'ethereum-types';
|
||||
import * as fetch from 'isomorphic-fetch';
|
||||
import * as _ from 'lodash';
|
||||
import * as prompts from 'prompts';
|
||||
|
||||
import * as wrappers from '../src/wrappers';
|
||||
|
||||
const SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/mzhu25/zeroex-migrations';
|
||||
|
||||
const ownableFeature = new wrappers.OwnableFeatureContract(constants.NULL_ADDRESS, new Web3ProviderEngine());
|
||||
const simpleFunctionRegistryFeature = new wrappers.SimpleFunctionRegistryFeatureContract(
|
||||
constants.NULL_ADDRESS,
|
||||
new Web3ProviderEngine(),
|
||||
);
|
||||
const DO_NOT_ROLLBACK = [
|
||||
ownableFeature.getSelector('migrate'),
|
||||
ownableFeature.getSelector('transferOwnership'),
|
||||
simpleFunctionRegistryFeature.getSelector('rollback'),
|
||||
simpleFunctionRegistryFeature.getSelector('extend'),
|
||||
];
|
||||
|
||||
const governorEncoder = AbiEncoder.create('(bytes[], address[], uint256[])');
|
||||
|
||||
const selectorToSignature: { [selector: string]: string } = {};
|
||||
for (const wrapper of Object.values(wrappers)) {
|
||||
if (typeof wrapper === 'function') {
|
||||
const contract = new wrapper(constants.NULL_ADDRESS, new Web3ProviderEngine());
|
||||
contract.abi
|
||||
.filter(abiDef => abiDef.type === 'function')
|
||||
.map(method => {
|
||||
const methodName = (method as MethodAbi).name;
|
||||
const selector = contract.getSelector(methodName);
|
||||
const signature = contract.getFunctionSignature(methodName);
|
||||
selectorToSignature[selector] = signature;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface ProxyFunctionEntity {
|
||||
id: string;
|
||||
currentImpl: string;
|
||||
fullHistory: Array<{ impl: string; timestamp: string }>;
|
||||
}
|
||||
|
||||
interface Deployment {
|
||||
time: string;
|
||||
updates: Array<{ selector: string; signature?: string; previousImpl: string; newImpl: string }>;
|
||||
}
|
||||
|
||||
async function querySubgraphAsync(): Promise<ProxyFunctionEntity[]> {
|
||||
const query = `
|
||||
{
|
||||
proxyFunctions {
|
||||
id
|
||||
currentImpl
|
||||
fullHistory {
|
||||
impl
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await fetch(SUBGRAPH_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
}),
|
||||
});
|
||||
const {
|
||||
data: { proxyFunctions },
|
||||
} = await response.json();
|
||||
// Sort the history in chronological order
|
||||
proxyFunctions.map((fn: ProxyFunctionEntity) =>
|
||||
fn.fullHistory.sort((a, b) => Number.parseInt(a.timestamp, 10) - Number.parseInt(b.timestamp, 10)),
|
||||
);
|
||||
return proxyFunctions;
|
||||
}
|
||||
|
||||
function reconstructDeployments(proxyFunctions: ProxyFunctionEntity[]): Deployment[] {
|
||||
const deploymentsByTimestamp: { [timestamp: string]: Deployment } = {};
|
||||
proxyFunctions.map(fn => {
|
||||
fn.fullHistory.map((update, i) => {
|
||||
const { updates } = (deploymentsByTimestamp[update.timestamp] = deploymentsByTimestamp[
|
||||
update.timestamp
|
||||
] || { time: timestampToUTC(update.timestamp), updates: [] });
|
||||
updates.push({
|
||||
selector: fn.id,
|
||||
signature: selectorToSignature[fn.id],
|
||||
previousImpl: i > 0 ? fn.fullHistory[i - 1].impl : constants.NULL_ADDRESS,
|
||||
newImpl: update.impl,
|
||||
});
|
||||
});
|
||||
});
|
||||
return Object.keys(deploymentsByTimestamp)
|
||||
.sort()
|
||||
.map(timestamp => deploymentsByTimestamp[timestamp]);
|
||||
}
|
||||
|
||||
function timestampToUTC(timestamp: string): string {
|
||||
return new Date(Number.parseInt(timestamp, 10) * 1000).toUTCString();
|
||||
}
|
||||
|
||||
enum CommandLineActions {
|
||||
History = 'History',
|
||||
Function = 'Function',
|
||||
Current = 'Current',
|
||||
Rollback = 'Rollback',
|
||||
Emergency = 'Emergency',
|
||||
Exit = 'Exit',
|
||||
}
|
||||
|
||||
async function confirmRollbackAsync(
|
||||
rollbackTargets: { [selector: string]: string },
|
||||
proxyFunctions: ProxyFunctionEntity[],
|
||||
): Promise<boolean> {
|
||||
const { confirmed } = await prompts({
|
||||
type: 'confirm',
|
||||
name: 'confirmed',
|
||||
message: `Are these the correct rollbacks?\n${Object.entries(rollbackTargets)
|
||||
.map(
|
||||
([selector, target]) =>
|
||||
`[${selector}] ${selectorToSignature[selector] || '(function signature not found)'} \n ${
|
||||
proxyFunctions.find(fn => fn.id === selector)!.currentImpl
|
||||
} => ${target}`,
|
||||
)
|
||||
.join('\n')}`,
|
||||
});
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
async function printRollbackCalldataAsync(
|
||||
rollbackTargets: { [selector: string]: string },
|
||||
zeroEx: wrappers.IZeroExContract,
|
||||
): Promise<void> {
|
||||
const numRollbacks = Object.keys(rollbackTargets).length;
|
||||
const { numTxns } = await prompts({
|
||||
type: 'number',
|
||||
name: 'numTxns',
|
||||
message:
|
||||
'To avoid limitations on calldata size, the full rollback can be split into multiple transactions. How many transactions would you like to split it into?',
|
||||
initial: 1,
|
||||
style: 'default',
|
||||
min: 1,
|
||||
max: numRollbacks,
|
||||
});
|
||||
for (let i = 0; i < numTxns; i++) {
|
||||
const startIndex = i * Math.trunc(numRollbacks / numTxns);
|
||||
const endIndex = startIndex + Math.trunc(numRollbacks / numTxns) + (i < numRollbacks % numTxns ? 1 : 0);
|
||||
const rollbacks = Object.entries(rollbackTargets).slice(startIndex, endIndex);
|
||||
const rollbackCallData = governorEncoder.encode([
|
||||
rollbacks.map(([selector, target]) => zeroEx.rollback(selector, target).getABIEncodedTransactionData()),
|
||||
new Array(rollbacks.length).fill(zeroEx.address),
|
||||
new Array(rollbacks.length).fill(constants.ZERO_AMOUNT),
|
||||
]);
|
||||
if (numTxns > 1) {
|
||||
logUtils.log(`======================== Governor Calldata #${i + 1} ========================`);
|
||||
}
|
||||
logUtils.log(rollbackCallData);
|
||||
}
|
||||
}
|
||||
|
||||
async function deploymentHistoryAsync(deployments: Deployment[], proxyFunctions: ProxyFunctionEntity[]): Promise<void> {
|
||||
const { index } = await prompts({
|
||||
type: 'select',
|
||||
name: 'index',
|
||||
message: 'Choose a deployment:',
|
||||
choices: deployments.map((deployment, i) => ({
|
||||
title: deployment.time,
|
||||
value: i,
|
||||
})),
|
||||
});
|
||||
|
||||
const { action } = await prompts({
|
||||
type: 'select',
|
||||
name: 'action',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{ title: 'Deployment info', value: 'info' },
|
||||
{ title: 'Rollback this deployment', value: 'rollback' },
|
||||
],
|
||||
});
|
||||
|
||||
if (action === 'info') {
|
||||
logUtils.log(
|
||||
deployments[index].updates.map(update => ({
|
||||
selector: update.selector,
|
||||
signature: update.signature || '(function signature not found)',
|
||||
update: `${update.previousImpl} => ${update.newImpl}`,
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
const zeroEx = await getMainnetContractAsync();
|
||||
const rollbackTargets: { [selector: string]: string } = {};
|
||||
for (const update of deployments[index].updates) {
|
||||
rollbackTargets[update.selector] = update.previousImpl;
|
||||
const rollbackLength = (await zeroEx.getRollbackLength(update.selector).callAsync()).toNumber();
|
||||
for (let i = rollbackLength - 1; i >= 0; i--) {
|
||||
const entry = await zeroEx.getRollbackEntryAtIndex(update.selector, new BigNumber(i)).callAsync();
|
||||
if (entry === update.previousImpl) {
|
||||
break;
|
||||
} else if (i === 0) {
|
||||
logUtils.log(
|
||||
'Cannot rollback this deployment. The following update from this deployment cannot be rolled back:',
|
||||
);
|
||||
logUtils.log(`\t[${update.selector}] ${update.signature || '(function signature not found)'}`);
|
||||
logUtils.log(`\t${update.previousImpl} => ${update.newImpl}`);
|
||||
logUtils.log(
|
||||
`Cannot find ${
|
||||
update.previousImpl
|
||||
} in the selector's rollback history. It itself may have been previously rolled back.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const isConfirmed = await confirmRollbackAsync(rollbackTargets, proxyFunctions);
|
||||
if (isConfirmed) {
|
||||
await printRollbackCalldataAsync(rollbackTargets, zeroEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function functionHistoryAsync(proxyFunctions: ProxyFunctionEntity[]): Promise<void> {
|
||||
const { fnSelector } = await prompts({
|
||||
type: 'autocomplete',
|
||||
name: 'fnSelector',
|
||||
message: 'Enter the selector or name of the function:',
|
||||
choices: [
|
||||
..._.flatMap(Object.entries(selectorToSignature), ([selector, signature]) => [
|
||||
{ title: selector, value: selector, description: signature },
|
||||
{ title: signature, value: selector, description: selector },
|
||||
]),
|
||||
...proxyFunctions
|
||||
.filter(fn => !Object.keys(selectorToSignature).includes(fn.id))
|
||||
.map(fn => ({ title: fn.id, value: fn.id, description: '(function signature not found)' })),
|
||||
],
|
||||
});
|
||||
const functionEntity = proxyFunctions.find(fn => fn.id === fnSelector);
|
||||
if (functionEntity === undefined) {
|
||||
logUtils.log(`Couldn't find deployment history for selector ${fnSelector}`);
|
||||
} else {
|
||||
logUtils.log(
|
||||
functionEntity.fullHistory.map(update => ({
|
||||
date: timestampToUTC(update.timestamp),
|
||||
impl: update.impl,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function currentFunctionsAsync(proxyFunctions: ProxyFunctionEntity[]): Promise<void> {
|
||||
const currentFunctions: {
|
||||
[selector: string]: { signature: string; impl: string; lastUpdated: string };
|
||||
} = {};
|
||||
proxyFunctions
|
||||
.filter(fn => fn.currentImpl !== constants.NULL_ADDRESS)
|
||||
.map(fn => {
|
||||
currentFunctions[fn.id] = {
|
||||
signature: selectorToSignature[fn.id] || '(function signature not found)',
|
||||
impl: fn.currentImpl,
|
||||
lastUpdated: timestampToUTC(fn.fullHistory.slice(-1)[0].timestamp),
|
||||
};
|
||||
});
|
||||
logUtils.log(currentFunctions);
|
||||
}
|
||||
|
||||
async function generateRollbackAsync(proxyFunctions: ProxyFunctionEntity[]): Promise<void> {
|
||||
const zeroEx = await getMainnetContractAsync();
|
||||
const { selected } = await prompts({
|
||||
type: 'autocompleteMultiselect',
|
||||
name: 'selected',
|
||||
message: 'Select the functions to rollback:',
|
||||
choices: _.flatMap(proxyFunctions.filter(fn => fn.currentImpl !== constants.NULL_ADDRESS), fn => [
|
||||
{
|
||||
title: [
|
||||
`[${fn.id}]`,
|
||||
`Implemented @ ${fn.currentImpl}`,
|
||||
selectorToSignature[fn.id] || '(function signature not found)',
|
||||
].join('\n\t\t\t\t'),
|
||||
value: fn.id,
|
||||
},
|
||||
]),
|
||||
});
|
||||
const rollbackTargets: { [selector: string]: string } = {};
|
||||
for (const selector of selected) {
|
||||
const rollbackLength = (await zeroEx.getRollbackLength(selector).callAsync()).toNumber();
|
||||
const rollbackHistory = await Promise.all(
|
||||
_.range(rollbackLength).map(async i =>
|
||||
zeroEx.getRollbackEntryAtIndex(selector, new BigNumber(i)).callAsync(),
|
||||
),
|
||||
);
|
||||
const fullHistory = proxyFunctions.find(fn => fn.id === selector)!.fullHistory;
|
||||
const previousImpl = rollbackHistory[rollbackLength - 1];
|
||||
const { target } = await prompts({
|
||||
type: 'select',
|
||||
name: 'target',
|
||||
message: 'Select the implementation to rollback to',
|
||||
hint: `[${selector}] ${selectorToSignature[selector] || '(function signature not found)'}`,
|
||||
choices: [
|
||||
{
|
||||
title: 'DISABLE',
|
||||
value: constants.NULL_ADDRESS,
|
||||
description: 'Rolls back to address(0)',
|
||||
},
|
||||
{
|
||||
title: 'PREVIOUS',
|
||||
value: previousImpl,
|
||||
description: `${previousImpl} (${timestampToUTC(
|
||||
_.findLast(fullHistory, update => update.impl === previousImpl)!.timestamp,
|
||||
)})`,
|
||||
},
|
||||
...[...new Set(rollbackHistory)]
|
||||
.filter(impl => impl !== constants.NULL_ADDRESS)
|
||||
.map(impl => ({
|
||||
title: impl,
|
||||
value: impl,
|
||||
description: timestampToUTC(_.findLast(fullHistory, update => update.impl === impl)!.timestamp),
|
||||
})),
|
||||
],
|
||||
});
|
||||
rollbackTargets[selector] = target;
|
||||
}
|
||||
|
||||
const isConfirmed = await confirmRollbackAsync(rollbackTargets, proxyFunctions);
|
||||
if (isConfirmed) {
|
||||
await printRollbackCalldataAsync(rollbackTargets, zeroEx);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateEmergencyRollbackAsync(proxyFunctions: ProxyFunctionEntity[]): Promise<void> {
|
||||
const zeroEx = new wrappers.IZeroExContract(
|
||||
getContractAddressesForChainOrThrow(1).exchangeProxy,
|
||||
new Web3ProviderEngine(),
|
||||
);
|
||||
const allSelectors = proxyFunctions
|
||||
.filter(fn => fn.currentImpl !== constants.NULL_ADDRESS && !DO_NOT_ROLLBACK.includes(fn.id))
|
||||
.map(fn => fn.id);
|
||||
await printRollbackCalldataAsync(
|
||||
_.zipObject(allSelectors, new Array(allSelectors.length).fill(constants.NULL_ADDRESS)),
|
||||
zeroEx,
|
||||
);
|
||||
}
|
||||
|
||||
let provider: SupportedProvider | undefined = process.env.RPC_URL ? createWeb3Provider(process.env.RPC_URL) : undefined;
|
||||
|
||||
function createWeb3Provider(rpcUrl: string): SupportedProvider {
|
||||
const providerEngine = new Web3ProviderEngine();
|
||||
providerEngine.addProvider(new RPCSubprovider(rpcUrl));
|
||||
providerUtils.startProviderEngine(providerEngine);
|
||||
return providerEngine;
|
||||
}
|
||||
|
||||
async function getMainnetContractAsync(): Promise<wrappers.IZeroExContract> {
|
||||
if (provider === undefined) {
|
||||
const { rpcUrl } = await prompts({
|
||||
type: 'text',
|
||||
name: 'rpcUrl',
|
||||
message: 'Enter an RPC endpoint:',
|
||||
});
|
||||
provider = createWeb3Provider(rpcUrl);
|
||||
}
|
||||
const chainId = await new Web3Wrapper(provider).getChainIdAsync();
|
||||
const { exchangeProxy } = getContractAddressesForChainOrThrow(chainId);
|
||||
return new wrappers.IZeroExContract(exchangeProxy, provider);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const proxyFunctions = await querySubgraphAsync();
|
||||
const deployments = reconstructDeployments(proxyFunctions);
|
||||
|
||||
while (true) {
|
||||
const { action } = await prompts({
|
||||
type: 'select',
|
||||
name: 'action',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{ title: '🚢 Deployment history', value: CommandLineActions.History },
|
||||
{ title: '📜 Function history', value: CommandLineActions.Function },
|
||||
{ title: '🗺️ Currently registered functions', value: CommandLineActions.Current },
|
||||
{ title: '🔙 Generate rollback calldata', value: CommandLineActions.Rollback },
|
||||
{ title: '🚨 Emergency shutdown calldata', value: CommandLineActions.Emergency },
|
||||
{ title: '👋 Exit', value: CommandLineActions.Exit },
|
||||
],
|
||||
});
|
||||
|
||||
switch (action) {
|
||||
case CommandLineActions.History:
|
||||
await deploymentHistoryAsync(deployments, proxyFunctions);
|
||||
break;
|
||||
case CommandLineActions.Function:
|
||||
await functionHistoryAsync(proxyFunctions);
|
||||
break;
|
||||
case CommandLineActions.Current:
|
||||
await currentFunctionsAsync(proxyFunctions);
|
||||
break;
|
||||
case CommandLineActions.Rollback:
|
||||
await generateRollbackAsync(proxyFunctions);
|
||||
break;
|
||||
case CommandLineActions.Emergency:
|
||||
await generateEmergencyRollbackAsync(proxyFunctions);
|
||||
break;
|
||||
case CommandLineActions.Exit:
|
||||
default:
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
})().catch(err => {
|
||||
logUtils.log(err);
|
||||
process.exit(1);
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*", "./scripts/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/AffiliateFeeTransformer.json",
|
||||
"generated-artifacts/BridgeAdapter.json",
|
||||
|
43
yarn.lock
43
yarn.lock
@ -2569,6 +2569,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/isomorphic-fetch@^0.0.35":
|
||||
version "0.0.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.35.tgz#c1c0d402daac324582b6186b91f8905340ea3361"
|
||||
integrity sha512-DaZNUvLDCAnCTjgwxgiL1eQdxIKEpNLOlTNtAgnZc50bG2copGhRrFN9/PxPBuJe+tZVLCbQ7ls0xveXVRPkvw==
|
||||
|
||||
"@types/js-combinatorics@^0.5.29":
|
||||
version "0.5.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-combinatorics/-/js-combinatorics-0.5.32.tgz#befa3c2b6ea10c45fd8d672f7aa477a79a2601ed"
|
||||
@ -2625,6 +2630,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/prompts@^2.0.9":
|
||||
version "2.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.0.9.tgz#19f419310eaa224a520476b19d4183f6a2b3bd8f"
|
||||
integrity sha512-TORZP+FSjTYMWwKadftmqEn6bziN5RnfygehByGsjxoK5ydnClddtv6GikGWPvCm24oI+YBwck5WDxIIyNxUrA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
@ -7846,6 +7858,14 @@ isomorphic-fetch@2.2.1, isomorphic-fetch@^2.2.1:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isomorphic-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4"
|
||||
integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==
|
||||
dependencies:
|
||||
node-fetch "^2.6.1"
|
||||
whatwg-fetch "^3.4.1"
|
||||
|
||||
isstream@0.1.x, isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
@ -8138,6 +8158,11 @@ klaw@^1.0.0:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.9"
|
||||
|
||||
kleur@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
lazy@~1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690"
|
||||
@ -10300,6 +10325,14 @@ prompt@^1.0.0:
|
||||
utile "0.3.x"
|
||||
winston "2.1.x"
|
||||
|
||||
prompts@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7"
|
||||
integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==
|
||||
dependencies:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
promzard@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
|
||||
@ -11333,6 +11366,11 @@ sinon@^4.0.0:
|
||||
supports-color "^5.1.0"
|
||||
type-detect "^4.0.5"
|
||||
|
||||
sisteransi@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
|
||||
|
||||
slash@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||
@ -13439,6 +13477,11 @@ whatwg-fetch@>=0.10.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3"
|
||||
|
||||
whatwg-fetch@^3.4.1:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
|
||||
integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==
|
||||
|
||||
whatwg-url@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
||||
|
Loading…
x
Reference in New Issue
Block a user