simplify refunds a bit, show in transaction history, add integration tests
This commit is contained in:
parent
5f8714091a
commit
66d76a3591
@ -4,40 +4,19 @@
|
||||
"description": "Integration tests and fault injection for GNU Taler components",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"compile": "tsc",
|
||||
"test": "tsc && ava"
|
||||
"compile": "tsc -b"
|
||||
},
|
||||
"author": "Florian Dold <dold@taler.net>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "^1.1.1",
|
||||
"ava": "^3.11.1",
|
||||
"esm": "^3.2.25",
|
||||
"source-map-support": "^0.5.19",
|
||||
"ts-node": "^8.10.2"
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.2",
|
||||
"taler-wallet-core": "workspace:*",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"ava": {
|
||||
"require": [
|
||||
"esm"
|
||||
],
|
||||
"files": [
|
||||
"src/**/test-*"
|
||||
],
|
||||
"typescript": {
|
||||
"extensions": [
|
||||
"js",
|
||||
"ts",
|
||||
"tsx"
|
||||
],
|
||||
"rewritePaths": {
|
||||
"src/": "lib/"
|
||||
}
|
||||
}
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
cd $DIR
|
||||
|
||||
./node_modules/.bin/tsc
|
||||
./node_modules/.bin/tsc -b
|
||||
|
||||
export ESM_OPTIONS='{"sourceMap": true}'
|
||||
|
||||
|
@ -50,7 +50,7 @@ import { EddsaKeyPair } from "taler-wallet-core/lib/crypto/talerCrypto";
|
||||
|
||||
const exec = util.promisify(require("child_process").exec);
|
||||
|
||||
async function delay(ms: number): Promise<void> {
|
||||
export async function delayMs(ms: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(), ms);
|
||||
});
|
||||
@ -410,7 +410,7 @@ async function pingProc(
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log(`service ${serviceName} not ready:`, e.toString());
|
||||
await delay(1000);
|
||||
await delayMs(1000);
|
||||
}
|
||||
if (!proc || proc.proc.exitCode !== null) {
|
||||
throw Error(`service process ${serviceName} stopped unexpectedly`);
|
||||
@ -951,16 +951,41 @@ export class MerchantService {
|
||||
}
|
||||
|
||||
async queryPrivateOrderStatus(instanceName: string, orderId: string) {
|
||||
let url;
|
||||
if (instanceName === "default") {
|
||||
url = `http://localhost:${this.merchantConfig.httpPort}/private/orders/${orderId}`;
|
||||
} else {
|
||||
url = `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceName}/private/orders/${orderId}`;
|
||||
}
|
||||
const resp = await axios.get(url);
|
||||
const reqUrl = new URL(
|
||||
`private/orders/${orderId}`,
|
||||
this.makeInstanceBaseUrl(instanceName),
|
||||
);
|
||||
const resp = await axios.get(reqUrl.href);
|
||||
return codecForMerchantOrderPrivateStatusResponse().decode(resp.data);
|
||||
}
|
||||
|
||||
makeInstanceBaseUrl(instanceName: string): string {
|
||||
if (instanceName === "default") {
|
||||
return `http://localhost:${this.merchantConfig.httpPort}/`;
|
||||
} else {
|
||||
return `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceName}/`;
|
||||
}
|
||||
}
|
||||
|
||||
async giveRefund(r: {
|
||||
instance: string;
|
||||
orderId: string;
|
||||
amount: string;
|
||||
justification: string;
|
||||
}): Promise<{ talerRefundUri: string }> {
|
||||
const reqUrl = new URL(
|
||||
`private/orders/${r.orderId}/refund`,
|
||||
this.makeInstanceBaseUrl(r.instance),
|
||||
);
|
||||
const resp = await axios.post(reqUrl.href, {
|
||||
refund: r.amount,
|
||||
reason: r.justification,
|
||||
});
|
||||
return {
|
||||
talerRefundUri: resp.data.taler_refund_uri,
|
||||
}
|
||||
}
|
||||
|
||||
async createOrder(
|
||||
instanceName: string,
|
||||
req: PostOrderRequest,
|
||||
|
126
packages/taler-integrationtests/src/test-refund-incremental.ts
Normal file
126
packages/taler-integrationtests/src/test-refund-incremental.ts
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2020 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { runTest, GlobalTestState, delayMs } from "./harness";
|
||||
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
|
||||
|
||||
/**
|
||||
* Run test for basic, bank-integrated withdrawal.
|
||||
*/
|
||||
runTest(async (t: GlobalTestState) => {
|
||||
// Set up test environment
|
||||
|
||||
const {
|
||||
wallet,
|
||||
bank,
|
||||
exchange,
|
||||
merchant,
|
||||
} = await createSimpleTestkudosEnvironment(t);
|
||||
|
||||
// Withdraw digital cash into the wallet.
|
||||
|
||||
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
|
||||
|
||||
// Set up order.
|
||||
|
||||
const orderResp = await merchant.createOrder("default", {
|
||||
order: {
|
||||
summary: "Buy me!",
|
||||
amount: "TESTKUDOS:5",
|
||||
fulfillment_url: "taler://fulfillment-success/thx",
|
||||
},
|
||||
});
|
||||
|
||||
let orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "unpaid")
|
||||
|
||||
// Make wallet pay for the order
|
||||
|
||||
const r1 = await wallet.apiRequest("preparePay", {
|
||||
talerPayUri: orderStatus.taler_pay_uri,
|
||||
});
|
||||
t.assertTrue(r1.type === "response");
|
||||
|
||||
const r2 = await wallet.apiRequest("confirmPay", {
|
||||
// FIXME: should be validated, don't cast!
|
||||
proposalId: (r1.result as any).proposalId,
|
||||
});
|
||||
t.assertTrue(r2.type === "response");
|
||||
|
||||
// Check if payment was successful.
|
||||
|
||||
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "paid");
|
||||
|
||||
let ref = await merchant.giveRefund({
|
||||
amount: "TESTKUDOS:2.5",
|
||||
instance: "default",
|
||||
justification: "foo",
|
||||
orderId: orderResp.order_id,
|
||||
});
|
||||
|
||||
console.log("first refund increase response", ref);
|
||||
|
||||
// Wait at least a second, because otherwise the increased
|
||||
// refund will be grouped with the previous one.
|
||||
await delayMs(1.2);
|
||||
|
||||
ref = await merchant.giveRefund({
|
||||
amount: "TESTKUDOS:5",
|
||||
instance: "default",
|
||||
justification: "bar",
|
||||
orderId: orderResp.order_id,
|
||||
});
|
||||
|
||||
console.log("second refund increase response", ref);
|
||||
|
||||
let r = await wallet.apiRequest("applyRefund", {
|
||||
talerRefundUri: ref.talerRefundUri,
|
||||
});
|
||||
console.log(r);
|
||||
|
||||
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "paid");
|
||||
|
||||
t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5");
|
||||
|
||||
console.log(JSON.stringify(orderStatus, undefined, 2));
|
||||
|
||||
await wallet.runUntilDone();
|
||||
|
||||
r = await wallet.apiRequest("getBalances", {});
|
||||
console.log(JSON.stringify(r, undefined, 2));
|
||||
|
||||
r = await wallet.apiRequest("getTransactions", {});
|
||||
console.log(JSON.stringify(r, undefined, 2));
|
||||
|
||||
await t.shutdown();
|
||||
});
|
102
packages/taler-integrationtests/src/test-refund.ts
Normal file
102
packages/taler-integrationtests/src/test-refund.ts
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2020 Taler Systems S.A.
|
||||
|
||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { runTest, GlobalTestState } from "./harness";
|
||||
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
|
||||
|
||||
/**
|
||||
* Run test for basic, bank-integrated withdrawal.
|
||||
*/
|
||||
runTest(async (t: GlobalTestState) => {
|
||||
// Set up test environment
|
||||
|
||||
const {
|
||||
wallet,
|
||||
bank,
|
||||
exchange,
|
||||
merchant,
|
||||
} = await createSimpleTestkudosEnvironment(t);
|
||||
|
||||
// Withdraw digital cash into the wallet.
|
||||
|
||||
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
|
||||
|
||||
// Set up order.
|
||||
|
||||
const orderResp = await merchant.createOrder("default", {
|
||||
order: {
|
||||
summary: "Buy me!",
|
||||
amount: "TESTKUDOS:5",
|
||||
fulfillment_url: "taler://fulfillment-success/thx",
|
||||
},
|
||||
});
|
||||
|
||||
let orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "unpaid")
|
||||
|
||||
// Make wallet pay for the order
|
||||
|
||||
const r1 = await wallet.apiRequest("preparePay", {
|
||||
talerPayUri: orderStatus.taler_pay_uri,
|
||||
});
|
||||
t.assertTrue(r1.type === "response");
|
||||
|
||||
const r2 = await wallet.apiRequest("confirmPay", {
|
||||
// FIXME: should be validated, don't cast!
|
||||
proposalId: (r1.result as any).proposalId,
|
||||
});
|
||||
t.assertTrue(r2.type === "response");
|
||||
|
||||
// Check if payment was successful.
|
||||
|
||||
orderStatus = await merchant.queryPrivateOrderStatus(
|
||||
"default",
|
||||
orderResp.order_id,
|
||||
);
|
||||
|
||||
t.assertTrue(orderStatus.order_status === "paid");
|
||||
|
||||
const ref = await merchant.giveRefund({
|
||||
amount: "TESTKUDOS:5",
|
||||
instance: "default",
|
||||
justification: "foo",
|
||||
orderId: orderResp.order_id,
|
||||
});
|
||||
|
||||
console.log(ref);
|
||||
|
||||
let r = await wallet.apiRequest("applyRefund", {
|
||||
talerRefundUri: ref.talerRefundUri,
|
||||
});
|
||||
console.log(r);
|
||||
|
||||
await wallet.runUntilDone();
|
||||
|
||||
r = await wallet.apiRequest("getBalances", {});
|
||||
console.log(JSON.stringify(r, undefined, 2));
|
||||
|
||||
r = await wallet.apiRequest("getTransactions", {});
|
||||
console.log(JSON.stringify(r, undefined, 2));
|
||||
|
||||
await t.shutdown();
|
||||
});
|
@ -17,7 +17,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
cd $DIR
|
||||
|
||||
./node_modules/.bin/tsc
|
||||
./node_modules/.bin/tsc -b
|
||||
|
||||
export ESM_OPTIONS='{"sourceMap": true}'
|
||||
|
||||
|
@ -24,9 +24,6 @@
|
||||
"typeRoots": ["./node_modules/@types"]
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../idb-bridge/",
|
||||
},
|
||||
{
|
||||
"path": "../taler-wallet-core"
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ async function acceptRefunds(
|
||||
refunds: MerchantCoinRefundStatus[],
|
||||
reason: RefundReason,
|
||||
): Promise<void> {
|
||||
console.log("handling refunds", refunds);
|
||||
logger.trace("handling refunds", refunds);
|
||||
const now = getTimestampNow();
|
||||
|
||||
await ws.db.runWithWriteTransaction(
|
||||
@ -302,37 +302,6 @@ async function acceptRefunds(
|
||||
});
|
||||
}
|
||||
|
||||
async function startRefundQuery(
|
||||
ws: InternalWalletState,
|
||||
proposalId: string,
|
||||
): Promise<void> {
|
||||
const success = await ws.db.runWithWriteTransaction(
|
||||
[Stores.purchases],
|
||||
async (tx) => {
|
||||
const p = await tx.get(Stores.purchases, proposalId);
|
||||
if (!p) {
|
||||
logger.error("no purchase found for refund URL");
|
||||
return false;
|
||||
}
|
||||
p.refundStatusRequested = true;
|
||||
p.lastRefundStatusError = undefined;
|
||||
p.refundStatusRetryInfo = initRetryInfo();
|
||||
await tx.put(Stores.purchases, p);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
ws.notify({
|
||||
type: NotificationType.RefundStarted,
|
||||
});
|
||||
|
||||
await processPurchaseQueryRefund(ws, proposalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a refund, return the contract hash for the contract
|
||||
* that was involved in the refund.
|
||||
@ -360,8 +329,31 @@ export async function applyRefund(
|
||||
);
|
||||
}
|
||||
|
||||
const proposalId = purchase.proposalId;
|
||||
|
||||
logger.info("processing purchase for refund");
|
||||
await startRefundQuery(ws, purchase.proposalId);
|
||||
const success = await ws.db.runWithWriteTransaction(
|
||||
[Stores.purchases],
|
||||
async (tx) => {
|
||||
const p = await tx.get(Stores.purchases, proposalId);
|
||||
if (!p) {
|
||||
logger.error("no purchase found for refund URL");
|
||||
return false;
|
||||
}
|
||||
p.refundStatusRequested = true;
|
||||
p.lastRefundStatusError = undefined;
|
||||
p.refundStatusRetryInfo = initRetryInfo();
|
||||
await tx.put(Stores.purchases, p);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
if (success) {
|
||||
ws.notify({
|
||||
type: NotificationType.RefundStarted,
|
||||
});
|
||||
await processPurchaseQueryRefund(ws, proposalId);
|
||||
}
|
||||
|
||||
return {
|
||||
contractTermsHash: purchase.contractData.contractTermsHash,
|
||||
@ -422,7 +414,7 @@ async function processPurchaseQueryRefundImpl(
|
||||
|
||||
const request = await ws.http.get(requestUrl.href);
|
||||
|
||||
console.log("got json", JSON.stringify(await request.json(), undefined, 2));
|
||||
logger.trace("got json", JSON.stringify(await request.json(), undefined, 2));
|
||||
|
||||
const refundResponse = await readSuccessResponseJsonOrThrow(
|
||||
request,
|
||||
|
@ -18,7 +18,12 @@
|
||||
* Imports.
|
||||
*/
|
||||
import { InternalWalletState } from "./state";
|
||||
import { Stores, WithdrawalSourceType } from "../types/dbTypes";
|
||||
import {
|
||||
Stores,
|
||||
WithdrawalSourceType,
|
||||
WalletRefundItem,
|
||||
RefundState,
|
||||
} from "../types/dbTypes";
|
||||
import { Amounts, AmountJson } from "../util/amounts";
|
||||
import { timestampCmp } from "../util/time";
|
||||
import {
|
||||
@ -29,8 +34,10 @@ import {
|
||||
PaymentStatus,
|
||||
WithdrawalType,
|
||||
WithdrawalDetails,
|
||||
PaymentShortInfo,
|
||||
} from "../types/transactions";
|
||||
import { getFundingPaytoUris } from "./reserves";
|
||||
import { ResultLevel } from "idb-bridge";
|
||||
|
||||
/**
|
||||
* Create an event ID from the type and the primary key for the event.
|
||||
@ -224,6 +231,18 @@ export async function getTransactions(
|
||||
if (!proposal) {
|
||||
return;
|
||||
}
|
||||
const info: PaymentShortInfo = {
|
||||
fulfillmentUrl: pr.contractData.fulfillmentUrl,
|
||||
merchant: pr.contractData.merchant,
|
||||
orderId: pr.contractData.orderId,
|
||||
products: pr.contractData.products,
|
||||
summary: pr.contractData.summary,
|
||||
summary_i18n: pr.contractData.summaryI18n,
|
||||
};
|
||||
const paymentTransactionId = makeEventId(
|
||||
TransactionType.Payment,
|
||||
pr.proposalId,
|
||||
);
|
||||
transactions.push({
|
||||
type: TransactionType.Payment,
|
||||
amountRaw: Amounts.stringify(pr.contractData.amount),
|
||||
@ -233,15 +252,62 @@ export async function getTransactions(
|
||||
: PaymentStatus.Accepted,
|
||||
pending: !pr.timestampFirstSuccessfulPay,
|
||||
timestamp: pr.timestampAccept,
|
||||
transactionId: makeEventId(TransactionType.Payment, pr.proposalId),
|
||||
info: {
|
||||
fulfillmentUrl: pr.contractData.fulfillmentUrl,
|
||||
merchant: pr.contractData.merchant,
|
||||
orderId: pr.contractData.orderId,
|
||||
products: pr.contractData.products,
|
||||
summary: pr.contractData.summary,
|
||||
summary_i18n: pr.contractData.summaryI18n,
|
||||
},
|
||||
transactionId: paymentTransactionId,
|
||||
info: info,
|
||||
});
|
||||
|
||||
const refundGroupKeys = new Set<string>();
|
||||
|
||||
for (const rk of Object.keys(pr.refunds)) {
|
||||
const refund = pr.refunds[rk];
|
||||
const groupKey = `${refund.executionTime.t_ms}`;
|
||||
refundGroupKeys.add(groupKey);
|
||||
}
|
||||
|
||||
refundGroupKeys.forEach((groupKey: string) => {
|
||||
const refundTransactionId = makeEventId(
|
||||
TransactionType.Payment,
|
||||
pr.proposalId,
|
||||
groupKey,
|
||||
);
|
||||
let r0: WalletRefundItem | undefined;
|
||||
let amountEffective = Amounts.getZero(
|
||||
pr.contractData.amount.currency,
|
||||
);
|
||||
let amountRaw = Amounts.getZero(pr.contractData.amount.currency);
|
||||
for (const rk of Object.keys(pr.refunds)) {
|
||||
const refund = pr.refunds[rk];
|
||||
if (!r0) {
|
||||
r0 = refund;
|
||||
}
|
||||
if (refund.type === RefundState.Applied) {
|
||||
amountEffective = Amounts.add(
|
||||
amountEffective,
|
||||
refund.refundAmount,
|
||||
).amount;
|
||||
amountRaw = Amounts.add(
|
||||
amountRaw,
|
||||
Amounts.sub(
|
||||
refund.refundAmount,
|
||||
refund.refundFee,
|
||||
refund.totalRefreshCostBound,
|
||||
).amount,
|
||||
).amount;
|
||||
}
|
||||
}
|
||||
if (!r0) {
|
||||
throw Error("invariant violated");
|
||||
}
|
||||
transactions.push({
|
||||
type: TransactionType.Refund,
|
||||
info,
|
||||
refundedTransactionId: paymentTransactionId,
|
||||
transactionId: refundTransactionId,
|
||||
timestamp: r0.executionTime,
|
||||
amountEffective: Amounts.stringify(amountEffective),
|
||||
amountRaw: Amounts.stringify(amountRaw),
|
||||
pending: false,
|
||||
});
|
||||
});
|
||||
|
||||
// for (const rg of pr.refundGroups) {
|
||||
|
@ -218,7 +218,7 @@ export interface TransactionPayment extends TransactionCommon {
|
||||
amountEffective: AmountString;
|
||||
}
|
||||
|
||||
interface PaymentShortInfo {
|
||||
export interface PaymentShortInfo {
|
||||
/**
|
||||
* Order ID, uniquely identifies the order within a merchant instance
|
||||
*/
|
||||
@ -259,9 +259,6 @@ interface TransactionRefund extends TransactionCommon {
|
||||
// Additional information about the refunded payment
|
||||
info: PaymentShortInfo;
|
||||
|
||||
// Part of the refund that couldn't be applied because the refund permissions were expired
|
||||
amountInvalid: AmountString;
|
||||
|
||||
// Amount that has been refunded by the merchant
|
||||
amountRaw: AmountString;
|
||||
|
||||
|
@ -36,16 +36,12 @@ importers:
|
||||
axios: 0.19.2
|
||||
taler-wallet-core: 'link:../taler-wallet-core'
|
||||
tslib: 2.0.0
|
||||
typescript: 3.9.7
|
||||
devDependencies:
|
||||
'@ava/typescript': 1.1.1
|
||||
ava: 3.11.1
|
||||
esm: 3.2.25
|
||||
source-map-support: 0.5.19
|
||||
ts-node: 8.10.2_typescript@3.9.7
|
||||
typescript: 3.9.7
|
||||
specifiers:
|
||||
'@ava/typescript': ^1.1.1
|
||||
ava: ^3.11.1
|
||||
axios: ^0.19.2
|
||||
esm: ^3.2.25
|
||||
source-map-support: ^0.5.19
|
||||
@ -1123,69 +1119,6 @@ packages:
|
||||
hasBin: true
|
||||
resolution:
|
||||
integrity: sha512-y5U8BGeSRjs/OypsC4CJxr+L1KtLKU5kUyHr5hcghXn7HNr2f4LE/4gvl0Q5lNkLX1obdRW1oODphNdU/glwmA==
|
||||
/ava/3.11.1:
|
||||
dependencies:
|
||||
'@concordance/react': 2.0.0
|
||||
acorn: 7.3.1
|
||||
acorn-walk: 7.2.0
|
||||
ansi-styles: 4.2.1
|
||||
arrgv: 1.0.2
|
||||
arrify: 2.0.1
|
||||
callsites: 3.1.0
|
||||
chalk: 4.1.0
|
||||
chokidar: 3.4.1
|
||||
chunkd: 2.0.1
|
||||
ci-info: 2.0.0
|
||||
ci-parallel-vars: 1.0.1
|
||||
clean-yaml-object: 0.1.0
|
||||
cli-cursor: 3.1.0
|
||||
cli-truncate: 2.1.0
|
||||
code-excerpt: 3.0.0
|
||||
common-path-prefix: 3.0.0
|
||||
concordance: 5.0.0
|
||||
convert-source-map: 1.7.0
|
||||
currently-unhandled: 0.4.1
|
||||
debug: 4.1.1
|
||||
del: 5.1.0
|
||||
emittery: 0.7.1
|
||||
equal-length: 1.0.1
|
||||
figures: 3.2.0
|
||||
globby: 11.0.1
|
||||
ignore-by-default: 2.0.0
|
||||
import-local: 3.0.2
|
||||
indent-string: 4.0.0
|
||||
is-error: 2.2.2
|
||||
is-plain-object: 4.1.1
|
||||
is-promise: 4.0.0
|
||||
lodash: 4.17.19
|
||||
matcher: 3.0.0
|
||||
md5-hex: 3.0.1
|
||||
mem: 6.1.0
|
||||
ms: 2.1.2
|
||||
ora: 4.0.5
|
||||
p-map: 4.0.0
|
||||
picomatch: 2.2.2
|
||||
pkg-conf: 3.1.0
|
||||
plur: 4.0.0
|
||||
pretty-ms: 7.0.0
|
||||
read-pkg: 5.2.0
|
||||
resolve-cwd: 3.0.0
|
||||
slash: 3.0.0
|
||||
source-map-support: 0.5.19
|
||||
stack-utils: 2.0.2
|
||||
strip-ansi: 6.0.0
|
||||
supertap: 1.0.0
|
||||
temp-dir: 2.0.0
|
||||
trim-off-newlines: 1.0.1
|
||||
update-notifier: 4.1.0
|
||||
write-file-atomic: 3.0.3
|
||||
yargs: 15.4.1
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=10.18.0 <11 || >=12.14.0 <12.17.0 || >=12.17.0 <13 || >=14.0.0'
|
||||
hasBin: true
|
||||
resolution:
|
||||
integrity: sha512-yGPD0msa5Qronw7GHDNlLaB7oU5zryYtXeuvny40YV6TMskSghqK7Ky3NisM/sr+aqI3DY7sfmORx8dIWQgMoQ==
|
||||
/axe-core/3.5.5:
|
||||
dev: true
|
||||
engines:
|
||||
@ -4757,6 +4690,7 @@ packages:
|
||||
resolution:
|
||||
integrity: sha512-/OyrHCJ8jtzu+QZ+771YaxQ9s4g5Z3XsQE3Ma7q+BL392xxBn4UMvvCdVnqKC2T/dz03/VXSLVKOP3lHmDdc/w==
|
||||
/typescript/3.9.7:
|
||||
dev: true
|
||||
engines:
|
||||
node: '>=4.2.0'
|
||||
hasBin: true
|
||||
|
Loading…
Reference in New Issue
Block a user