summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-08-10 16:48:38 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-08-10 16:48:38 +0530
commit66d76a35912d7687d76b349f1cac462306306d3f (patch)
tree3cdfce8178c3928dbcfc13263f8752b3f9ca9c7b
parent5f8714091aac80144be118fa6427d65222e7509c (diff)
downloadwallet-core-66d76a35912d7687d76b349f1cac462306306d3f.tar.gz
wallet-core-66d76a35912d7687d76b349f1cac462306306d3f.tar.bz2
wallet-core-66d76a35912d7687d76b349f1cac462306306d3f.zip
simplify refunds a bit, show in transaction history, add integration tests
-rw-r--r--packages/taler-integrationtests/package.json29
-rwxr-xr-xpackages/taler-integrationtests/scenario2
-rw-r--r--packages/taler-integrationtests/src/harness.ts39
-rw-r--r--packages/taler-integrationtests/src/test-refund-incremental.ts126
-rw-r--r--packages/taler-integrationtests/src/test-refund.ts102
-rwxr-xr-xpackages/taler-integrationtests/testrunner2
-rw-r--r--packages/taler-integrationtests/tsconfig.json3
-rw-r--r--packages/taler-wallet-core/src/operations/refund.ts60
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts86
-rw-r--r--packages/taler-wallet-core/src/types/transactions.ts5
-rw-r--r--pnpm-lock.yaml70
11 files changed, 371 insertions, 153 deletions
diff --git a/packages/taler-integrationtests/package.json b/packages/taler-integrationtests/package.json
index 713852370..ba2f112ef 100644
--- a/packages/taler-integrationtests/package.json
+++ b/packages/taler-integrationtests/package.json
@@ -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"
}
}
diff --git a/packages/taler-integrationtests/scenario b/packages/taler-integrationtests/scenario
index a0050258e..9bef68ffa 100755
--- a/packages/taler-integrationtests/scenario
+++ b/packages/taler-integrationtests/scenario
@@ -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}'
diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts
index 027869d15..2507d12f7 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -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,14 +951,39 @@ export class MerchantService {
}
async queryPrivateOrderStatus(instanceName: string, orderId: string) {
- let 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") {
- url = `http://localhost:${this.merchantConfig.httpPort}/private/orders/${orderId}`;
+ return `http://localhost:${this.merchantConfig.httpPort}/`;
} else {
- url = `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceName}/private/orders/${orderId}`;
+ 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,
}
- const resp = await axios.get(url);
- return codecForMerchantOrderPrivateStatusResponse().decode(resp.data);
}
async createOrder(
diff --git a/packages/taler-integrationtests/src/test-refund-incremental.ts b/packages/taler-integrationtests/src/test-refund-incremental.ts
new file mode 100644
index 000000000..29685dd3e
--- /dev/null
+++ b/packages/taler-integrationtests/src/test-refund-incremental.ts
@@ -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();
+});
diff --git a/packages/taler-integrationtests/src/test-refund.ts b/packages/taler-integrationtests/src/test-refund.ts
new file mode 100644
index 000000000..c2f152f53
--- /dev/null
+++ b/packages/taler-integrationtests/src/test-refund.ts
@@ -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();
+});
diff --git a/packages/taler-integrationtests/testrunner b/packages/taler-integrationtests/testrunner
index 6476d73fa..03cb15b3c 100755
--- a/packages/taler-integrationtests/testrunner
+++ b/packages/taler-integrationtests/testrunner
@@ -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}'
diff --git a/packages/taler-integrationtests/tsconfig.json b/packages/taler-integrationtests/tsconfig.json
index 9fa8001a8..2fe0853d4 100644
--- a/packages/taler-integrationtests/tsconfig.json
+++ b/packages/taler-integrationtests/tsconfig.json
@@ -25,9 +25,6 @@
},
"references": [
{
- "path": "../idb-bridge/",
- },
- {
"path": "../taler-wallet-core"
}
],
diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts
index 9792d2268..2b6ee97ae 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -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,
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 2d66b5e9d..8de204d49 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -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) {
diff --git a/packages/taler-wallet-core/src/types/transactions.ts b/packages/taler-wallet-core/src/types/transactions.ts
index de378f51a..fe5580f85 100644
--- a/packages/taler-wallet-core/src/types/transactions.ts
+++ b/packages/taler-wallet-core/src/types/transactions.ts
@@ -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;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d8ff6bb22..16143c47e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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