feat: you can now abort transaction confirmations in web3.js (#29057)
* Upgrade Typescript, `@types/node`, and `typedoc` to versions that play well together In this instance it means they: * understand `AbortSignal` * don't cause build errors * You can now abort transaction confirmation using an `AbortSignal` * Pipe an `AbortSignal` down through `sendAndConfirmTransaction()` * Add `AbortController` polyfill to test so that test works in Node 14
This commit is contained in:
parent
9725a4552e
commit
35f3c18aa8
|
@ -95,7 +95,7 @@
|
|||
"@types/express-serve-static-core": "^4.17.21",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/mz": "^2.7.3",
|
||||
"@types/node": "^17.0.24",
|
||||
"@types/node": "^18.11.10",
|
||||
"@types/node-fetch": "2",
|
||||
"@types/sinon": "^10.0.0",
|
||||
"@types/sinon-chai": "^3.2.8",
|
||||
|
@ -114,6 +114,7 @@
|
|||
"mocha": "^10.1.0",
|
||||
"mockttp": "^2.0.1",
|
||||
"mz": "^2.7.0",
|
||||
"node-abort-controller": "^3.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.0",
|
||||
|
@ -129,8 +130,8 @@
|
|||
"ts-mocha": "^10.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"tslib": "^2.1.0",
|
||||
"typedoc": "^0.22.2",
|
||||
"typescript": "^4.3.2"
|
||||
"typedoc": "^0.23",
|
||||
"typescript": "^4.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
|
|
|
@ -311,9 +311,39 @@ export type BlockhashWithExpiryBlockHeight = Readonly<{
|
|||
* A strategy for confirming transactions that uses the last valid
|
||||
* block height for a given blockhash to check for transaction expiration.
|
||||
*/
|
||||
export type BlockheightBasedTransactionConfirmationStrategy = {
|
||||
export type BlockheightBasedTransactionConfirmationStrategy =
|
||||
BaseTransactionConfirmationStrategy & BlockhashWithExpiryBlockHeight;
|
||||
|
||||
/**
|
||||
* A strategy for confirming durable nonce transactions.
|
||||
*/
|
||||
export type DurableNonceTransactionConfirmationStrategy =
|
||||
BaseTransactionConfirmationStrategy & {
|
||||
/**
|
||||
* The lowest slot at which to fetch the nonce value from the
|
||||
* nonce account. This should be no lower than the slot at
|
||||
* which the last-known value of the nonce was fetched.
|
||||
*/
|
||||
minContextSlot: number;
|
||||
/**
|
||||
* The account where the current value of the nonce is stored.
|
||||
*/
|
||||
nonceAccountPubkey: PublicKey;
|
||||
/**
|
||||
* The nonce value that was used to sign the transaction
|
||||
* for which confirmation is being sought.
|
||||
*/
|
||||
nonceValue: DurableNonce;
|
||||
};
|
||||
|
||||
/**
|
||||
* Properties shared by all transaction confirmation strategies
|
||||
*/
|
||||
export type BaseTransactionConfirmationStrategy = Readonly<{
|
||||
/** A signal that, when aborted, cancels any outstanding transaction confirmation operations */
|
||||
abortSignal?: AbortSignal;
|
||||
signature: TransactionSignature;
|
||||
} & BlockhashWithExpiryBlockHeight;
|
||||
}>;
|
||||
|
||||
/* @internal */
|
||||
function assertEndpointUrl(putativeUrl: string) {
|
||||
|
@ -340,28 +370,6 @@ function extractCommitmentFromConfig<TConfig>(
|
|||
return {commitment, config};
|
||||
}
|
||||
|
||||
/**
|
||||
* A strategy for confirming durable nonce transactions.
|
||||
*/
|
||||
export type DurableNonceTransactionConfirmationStrategy = {
|
||||
/**
|
||||
* The lowest slot at which to fetch the nonce value from the
|
||||
* nonce account. This should be no lower than the slot at
|
||||
* which the last-known value of the nonce was fetched.
|
||||
*/
|
||||
minContextSlot: number;
|
||||
/**
|
||||
* The account where the current value of the nonce is stored.
|
||||
*/
|
||||
nonceAccountPubkey: PublicKey;
|
||||
/**
|
||||
* The nonce value that was used to sign the transaction
|
||||
* for which confirmation is being sought.
|
||||
*/
|
||||
nonceValue: DurableNonce;
|
||||
signature: TransactionSignature;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -3571,6 +3579,9 @@ export class Connection {
|
|||
const config = strategy as
|
||||
| BlockheightBasedTransactionConfirmationStrategy
|
||||
| DurableNonceTransactionConfirmationStrategy;
|
||||
if (config.abortSignal?.aborted) {
|
||||
return Promise.reject(config.abortSignal.reason);
|
||||
}
|
||||
rawSignature = config.signature;
|
||||
}
|
||||
|
||||
|
@ -3602,6 +3613,21 @@ export class Connection {
|
|||
}
|
||||
}
|
||||
|
||||
private getCancellationPromise(signal?: AbortSignal): Promise<never> {
|
||||
return new Promise<never>((_, reject) => {
|
||||
if (signal == null) {
|
||||
return;
|
||||
}
|
||||
if (signal.aborted) {
|
||||
reject(signal.reason);
|
||||
} else {
|
||||
signal.addEventListener('abort', () => {
|
||||
reject(signal.reason);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getTransactionConfirmationPromise({
|
||||
commitment,
|
||||
signature,
|
||||
|
@ -3722,7 +3748,7 @@ export class Connection {
|
|||
|
||||
private async confirmTransactionUsingBlockHeightExceedanceStrategy({
|
||||
commitment,
|
||||
strategy: {lastValidBlockHeight, signature},
|
||||
strategy: {abortSignal, lastValidBlockHeight, signature},
|
||||
}: {
|
||||
commitment?: Commitment;
|
||||
strategy: BlockheightBasedTransactionConfirmationStrategy;
|
||||
|
@ -3753,9 +3779,14 @@ export class Connection {
|
|||
});
|
||||
const {abortConfirmation, confirmationPromise} =
|
||||
this.getTransactionConfirmationPromise({commitment, signature});
|
||||
const cancellationPromise = this.getCancellationPromise(abortSignal);
|
||||
let result: RpcResponseAndContext<SignatureResult>;
|
||||
try {
|
||||
const outcome = await Promise.race([confirmationPromise, expiryPromise]);
|
||||
const outcome = await Promise.race([
|
||||
cancellationPromise,
|
||||
confirmationPromise,
|
||||
expiryPromise,
|
||||
]);
|
||||
if (outcome.__type === TransactionStatus.PROCESSED) {
|
||||
result = outcome.response;
|
||||
} else {
|
||||
|
@ -3770,7 +3801,13 @@ export class Connection {
|
|||
|
||||
private async confirmTransactionUsingDurableNonceStrategy({
|
||||
commitment,
|
||||
strategy: {minContextSlot, nonceAccountPubkey, nonceValue, signature},
|
||||
strategy: {
|
||||
abortSignal,
|
||||
minContextSlot,
|
||||
nonceAccountPubkey,
|
||||
nonceValue,
|
||||
signature,
|
||||
},
|
||||
}: {
|
||||
commitment?: Commitment;
|
||||
strategy: DurableNonceTransactionConfirmationStrategy;
|
||||
|
@ -3821,9 +3858,14 @@ export class Connection {
|
|||
});
|
||||
const {abortConfirmation, confirmationPromise} =
|
||||
this.getTransactionConfirmationPromise({commitment, signature});
|
||||
const cancellationPromise = this.getCancellationPromise(abortSignal);
|
||||
let result: RpcResponseAndContext<SignatureResult>;
|
||||
try {
|
||||
const outcome = await Promise.race([confirmationPromise, expiryPromise]);
|
||||
const outcome = await Promise.race([
|
||||
cancellationPromise,
|
||||
confirmationPromise,
|
||||
expiryPromise,
|
||||
]);
|
||||
if (outcome.__type === TransactionStatus.PROCESSED) {
|
||||
result = outcome.response;
|
||||
} else {
|
||||
|
|
|
@ -26,7 +26,7 @@ function nextPowerOfTwo(n: number) {
|
|||
/**
|
||||
* Epoch schedule
|
||||
* (see https://docs.solana.com/terminology#epoch)
|
||||
* Can be retrieved with the {@link connection.getEpochSchedule} method
|
||||
* Can be retrieved with the {@link Connection.getEpochSchedule} method
|
||||
*/
|
||||
export class EpochSchedule {
|
||||
/** The maximum number of slots in each epoch */
|
||||
|
|
|
@ -19,7 +19,11 @@ export async function sendAndConfirmTransaction(
|
|||
connection: Connection,
|
||||
transaction: Transaction,
|
||||
signers: Array<Signer>,
|
||||
options?: ConfirmOptions,
|
||||
options?: ConfirmOptions &
|
||||
Readonly<{
|
||||
// A signal that, when aborted, cancels any outstanding transaction confirmation operations
|
||||
abortSignal?: AbortSignal;
|
||||
}>,
|
||||
): Promise<TransactionSignature> {
|
||||
const sendOptions = options && {
|
||||
skipPreflight: options.skipPreflight,
|
||||
|
@ -42,6 +46,7 @@ export async function sendAndConfirmTransaction(
|
|||
status = (
|
||||
await connection.confirmTransaction(
|
||||
{
|
||||
abortSignal: options?.abortSignal,
|
||||
signature: signature,
|
||||
blockhash: transaction.recentBlockhash,
|
||||
lastValidBlockHeight: transaction.lastValidBlockHeight,
|
||||
|
@ -58,6 +63,7 @@ export async function sendAndConfirmTransaction(
|
|||
status = (
|
||||
await connection.confirmTransaction(
|
||||
{
|
||||
abortSignal: options?.abortSignal,
|
||||
minContextSlot: transaction.minNonceContextSlot,
|
||||
nonceAccountPubkey,
|
||||
nonceValue: transaction.nonceInfo.nonce,
|
||||
|
@ -67,6 +73,13 @@ export async function sendAndConfirmTransaction(
|
|||
)
|
||||
).value;
|
||||
} else {
|
||||
if (options?.abortSignal != null) {
|
||||
console.warn(
|
||||
'sendAndConfirmTransaction(): A transaction with a deprecated confirmation strategy was ' +
|
||||
'supplied along with an `abortSignal`. Only transactions having `lastValidBlockHeight` ' +
|
||||
'or a combination of `nonceInfo` and `minNonceContextSlot` are abortable.',
|
||||
);
|
||||
}
|
||||
status = (
|
||||
await connection.confirmTransaction(
|
||||
signature,
|
||||
|
|
|
@ -3,6 +3,7 @@ import {Buffer} from 'buffer';
|
|||
import * as splToken from '@solana/spl-token';
|
||||
import {expect, use} from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import {AbortController} from 'node-abort-controller';
|
||||
import {mock, useFakeTimers, SinonFakeTimers} from 'sinon';
|
||||
import sinonChai from 'sinon-chai';
|
||||
|
||||
|
@ -1167,6 +1168,44 @@ describe('Connection', function () {
|
|||
});
|
||||
|
||||
describe('block height strategy', () => {
|
||||
it('rejects if called with an already-aborted `abortSignal`', () => {
|
||||
const mockSignature =
|
||||
'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt';
|
||||
const abortController = new AbortController();
|
||||
abortController.abort();
|
||||
expect(
|
||||
connection.confirmTransaction({
|
||||
abortSignal: abortController.signal,
|
||||
blockhash: 'sampleBlockhash',
|
||||
lastValidBlockHeight: 1,
|
||||
signature: mockSignature,
|
||||
}),
|
||||
).to.eventually.be.rejectedWith('AbortError');
|
||||
});
|
||||
|
||||
it('rejects upon receiving an abort signal', async () => {
|
||||
const mockSignature =
|
||||
'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt';
|
||||
const abortController = new AbortController();
|
||||
// Keep the subscription from ever returning data.
|
||||
await mockRpcMessage({
|
||||
method: 'signatureSubscribe',
|
||||
params: [mockSignature, {commitment: 'finalized'}],
|
||||
result: new Promise(() => {}), // Never resolve.
|
||||
});
|
||||
clock.runAllAsync();
|
||||
const confirmationPromise = connection.confirmTransaction({
|
||||
abortSignal: abortController.signal,
|
||||
blockhash: 'sampleBlockhash',
|
||||
lastValidBlockHeight: 1,
|
||||
signature: mockSignature,
|
||||
});
|
||||
clock.runAllAsync();
|
||||
expect(confirmationPromise).not.to.have.been.rejected;
|
||||
abortController.abort();
|
||||
await expect(confirmationPromise).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('throws a `TransactionExpiredBlockheightExceededError` when the block height advances past the last valid one for this transaction without a signature confirmation', async () => {
|
||||
const mockSignature =
|
||||
'4oCEqwGrMdBeMxpzuWiukCYqSfV4DsSKXSiVVCh1iJ6pS772X7y219JZP3mgqBz5PhsvprpKyhzChjYc3VSBQXzG';
|
||||
|
@ -1295,6 +1334,46 @@ describe('Connection', function () {
|
|||
});
|
||||
|
||||
describe('nonce strategy', () => {
|
||||
it('rejects if called with an already-aborted `abortSignal`', () => {
|
||||
const mockSignature =
|
||||
'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt';
|
||||
const abortController = new AbortController();
|
||||
abortController.abort();
|
||||
expect(
|
||||
connection.confirmTransaction({
|
||||
abortSignal: abortController.signal,
|
||||
minContextSlot: 1,
|
||||
nonceAccountPubkey: new PublicKey(1),
|
||||
nonceValue: 'fakenonce',
|
||||
signature: mockSignature,
|
||||
}),
|
||||
).to.eventually.be.rejectedWith('AbortError');
|
||||
});
|
||||
|
||||
it('rejects upon receiving an abort signal', async () => {
|
||||
const mockSignature =
|
||||
'w2Zeq8YkpyB463DttvfzARD7k9ZxGEwbsEw4boEK7jDp3pfoxZbTdLFSsEPhzXhpCcjGi2kHtHFobgX49MMhbWt';
|
||||
const abortController = new AbortController();
|
||||
// Keep the subscription from ever returning data.
|
||||
await mockRpcMessage({
|
||||
method: 'signatureSubscribe',
|
||||
params: [mockSignature, {commitment: 'finalized'}],
|
||||
result: new Promise(() => {}), // Never resolve.
|
||||
});
|
||||
clock.runAllAsync();
|
||||
const confirmationPromise = connection.confirmTransaction({
|
||||
abortSignal: abortController.signal,
|
||||
minContextSlot: 1,
|
||||
nonceAccountPubkey: new PublicKey(1),
|
||||
nonceValue: 'fakenonce',
|
||||
signature: mockSignature,
|
||||
});
|
||||
clock.runAllAsync();
|
||||
expect(confirmationPromise).not.to.have.been.rejected;
|
||||
abortController.abort();
|
||||
await expect(confirmationPromise).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('confirms the transaction if the signature confirmation is received before the nonce is advanced', async () => {
|
||||
const mockSignature =
|
||||
'4oCEqwGrMdBeMxpzuWiukCYqSfV4DsSKXSiVVCh1iJ6pS772X7y219JZP3mgqBz5PhsvprpKyhzChjYc3VSBQXzG';
|
||||
|
|
|
@ -1654,7 +1654,7 @@
|
|||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*", "@types/node@^17.0.24":
|
||||
"@types/node@*":
|
||||
version "17.0.35"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz"
|
||||
|
||||
|
@ -1666,6 +1666,11 @@
|
|||
version "16.11.27"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz"
|
||||
|
||||
"@types/node@^18.11.10":
|
||||
version "18.11.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34"
|
||||
integrity sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz"
|
||||
|
@ -3569,7 +3574,7 @@ glob-parent@^6.0.1:
|
|||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
|
||||
glob@7.2.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
|
||||
glob@7.2.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz"
|
||||
dependencies:
|
||||
|
@ -4633,10 +4638,15 @@ marked-terminal@^5.0.0:
|
|||
node-emoji "^1.11.0"
|
||||
supports-hyperlinks "^2.2.0"
|
||||
|
||||
marked@^4.0.10, marked@^4.0.12:
|
||||
marked@^4.0.10:
|
||||
version "4.0.16"
|
||||
resolved "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz"
|
||||
|
||||
marked@^4.0.19:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.3.tgz#bd76a5eb510ff1d8421bc6c3b2f0b93488c15bea"
|
||||
integrity sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==
|
||||
|
||||
matched@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/matched/-/matched-5.0.1.tgz"
|
||||
|
@ -4743,6 +4753,13 @@ minimatch@^5.0.1:
|
|||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^5.1.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.1.tgz#6c9dffcf9927ff2a31e74b5af11adf8b9604b022"
|
||||
integrity sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist-options@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz"
|
||||
|
@ -4981,6 +4998,11 @@ no-case@^3.0.4:
|
|||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-abort-controller@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e"
|
||||
integrity sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==
|
||||
|
||||
node-addon-api@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz"
|
||||
|
@ -6224,13 +6246,14 @@ shell-quote@^1.6.1:
|
|||
version "1.7.3"
|
||||
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz"
|
||||
|
||||
shiki@^0.10.1:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz"
|
||||
shiki@^0.11.1:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.11.1.tgz#df0f719e7ab592c484d8b73ec10e215a503ab8cc"
|
||||
integrity sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==
|
||||
dependencies:
|
||||
jsonc-parser "^3.0.0"
|
||||
vscode-oniguruma "^1.6.1"
|
||||
vscode-textmate "5.2.0"
|
||||
vscode-textmate "^6.0.0"
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
@ -6812,19 +6835,20 @@ typedarray-to-buffer@^3.1.5:
|
|||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typedoc@^0.22.2:
|
||||
version "0.22.15"
|
||||
resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.22.15.tgz"
|
||||
typedoc@^0.23:
|
||||
version "0.23.21"
|
||||
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.23.21.tgz#2a6b0e155f91ffa9689086706ad7e3e4bc11d241"
|
||||
integrity sha512-VNE9Jv7BgclvyH9moi2mluneSviD43dCE9pY8RWkO88/DrEgJZk9KpUk7WO468c9WWs/+aG6dOnoH7ccjnErhg==
|
||||
dependencies:
|
||||
glob "^7.2.0"
|
||||
lunr "^2.3.9"
|
||||
marked "^4.0.12"
|
||||
minimatch "^5.0.1"
|
||||
shiki "^0.10.1"
|
||||
marked "^4.0.19"
|
||||
minimatch "^5.1.0"
|
||||
shiki "^0.11.1"
|
||||
|
||||
typescript@^4.3.2:
|
||||
version "4.6.4"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz"
|
||||
typescript@^4.9:
|
||||
version "4.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db"
|
||||
integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.15.3"
|
||||
|
@ -6978,9 +7002,10 @@ vscode-oniguruma@^1.6.1:
|
|||
version "1.6.2"
|
||||
resolved "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz"
|
||||
|
||||
vscode-textmate@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz"
|
||||
vscode-textmate@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-6.0.0.tgz#a3777197235036814ac9a92451492f2748589210"
|
||||
integrity sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==
|
||||
|
||||
wait-on@6.0.0:
|
||||
version "6.0.0"
|
||||
|
|
Loading…
Reference in New Issue