diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2364b40 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# Path to app-monorepo for local sync +APP_MONOREPO_LOCAL_PATH=/path/to/app-monorepo diff --git a/.gitignore b/.gitignore index 8689e49..8e1be32 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,4 @@ dist !.yarn/sdks !.yarn/versions +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ef727d1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "search.exclude": { - "**/.yarn": true, - "**/.pnp.*": true - }, - "typescript.tsdk": ".yarn/sdks/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, - "cSpell.words": ["DEREGISTRATION", "drep", "preprod"] -} diff --git a/package.json b/package.json index efb29cf..44f9261 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/cardano-coin-selection-asmjs", - "version": "1.1.5", + "version": "1.1.10-alpha.8", "description": "", "keywords": [ "coin selection" @@ -29,7 +29,8 @@ "type-check": "yarn tsc -p tsconfig.types.json", "test": "yarn run-s 'test:*'", "test:unit": "jest -c ./jest.config.js", - "test:badges": "make-coverage-badge --output-path ./docs/badge-coverage.svg" + "test:badges": "make-coverage-badge --output-path ./docs/badge-coverage.svg", + "debug:watcher": "node scripts/sync-to-monorepo.js" }, "devDependencies": { "@emurgo/cardano-serialization-lib-nodejs": "13.2.0", @@ -42,10 +43,13 @@ "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "8.8.0", "bignumber.js": "^9.0.1", + "chokidar": "^5.0.0", + "dotenv": "^17.2.3", "eslint": "^9.11.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.30.0", "eslint-plugin-prettier": "^5.2.1", + "fs-extra": "^11.3.2", "jest": "^29.7.0", "make-coverage-badge": "^1.2.0", "npm-run-all": "^4.1.5", diff --git a/scripts/sync-to-monorepo.js b/scripts/sync-to-monorepo.js new file mode 100644 index 0000000..b5ee6be --- /dev/null +++ b/scripts/sync-to-monorepo.js @@ -0,0 +1,158 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +require('dotenv').config(); +const chokidar = require('chokidar'); +const fs = require('fs-extra'); +const path = require('path'); +const { exec } = require('child_process'); + +// Configuration +const config = { + // Path to app-monorepo, read from environment variable + targetDir: process.env.APP_MONOREPO_LOCAL_PATH, + // Current package name + packageName: '@onekeyfe/cardano-coin-selection-asmjs', + // Source directory to watch + watchDir: 'src', + // Build output directory + buildDir: 'lib', + // Debounce time in milliseconds + debounceTime: 300, + // Apps whose caches need to be cleared + appCacheDirs: ['desktop', 'ext', 'web', 'mobile'], +}; + +if (!config.targetDir) { + console.error( + 'โŒ APP_MONOREPO_LOCAL_PATH is not set. Please specify it in the .env file.\n' + + 'Example: APP_MONOREPO_LOCAL_PATH=/path/to/app-monorepo' + ); + process.exit(1); +} + +const projectRoot = path.join(__dirname, '..'); +const destNodeModulesPath = path.join( + config.targetDir, + 'node_modules', + config.packageName +); + +console.log('๐Ÿ“ฆ Package:', config.packageName); +console.log('๐Ÿ“‚ Source:', path.join(projectRoot, config.watchDir)); +console.log('๐Ÿ“ Target:', destNodeModulesPath); +console.log(''); + +// Debounced build state +let buildTimeout = null; +let isBuilding = false; + +function triggerBuild() { + if (buildTimeout) { + clearTimeout(buildTimeout); + } + + buildTimeout = setTimeout(() => { + if (isBuilding) { + console.log('โณ Build already in progress, queuing...'); + triggerBuild(); + return; + } + + isBuilding = true; + console.log('\n๐Ÿ”จ Building...'); + + exec('yarn build', { cwd: projectRoot }, (error, stdout, stderr) => { + isBuilding = false; + + if (error) { + console.error('โŒ Build failed:', error.message); + if (stderr) console.error(stderr); + return; + } + + console.log('โœ… Build complete'); + syncBuildOutput(); + }); + }, config.debounceTime); +} + +async function clearAppCaches() { + const clearedApps = []; + + for (const app of config.appCacheDirs) { + const cachePath = path.join(config.targetDir, 'apps', app, 'node_modules', '.cache'); + if (await fs.pathExists(cachePath)) { + await fs.remove(cachePath); + clearedApps.push(app); + } + } + + if (clearedApps.length > 0) { + console.log(`๐Ÿงน Cleared cache for: ${clearedApps.join(', ')}`); + } +} + +async function syncBuildOutput() { + const srcLibPath = path.join(projectRoot, config.buildDir); + const destLibPath = path.join(destNodeModulesPath, config.buildDir); + + try { + // Sync the entire lib directory + await fs.copy(srcLibPath, destLibPath, { overwrite: true }); + console.log(`๐Ÿ“ค Synced ${config.buildDir}/ -> ${destLibPath}`); + + // Sync package.json (in case version or other fields changed) + const srcPackageJson = path.join(projectRoot, 'package.json'); + const destPackageJson = path.join(destNodeModulesPath, 'package.json'); + await fs.copy(srcPackageJson, destPackageJson, { overwrite: true }); + console.log('๐Ÿ“ค Synced package.json'); + + // Clear webpack caches for all apps + await clearAppCaches(); + + console.log('โœจ Sync complete!\n'); + } catch (err) { + console.error('โŒ Sync error:', err); + } +} + +// Watch for source file changes +const watcher = chokidar.watch(path.join(projectRoot, config.watchDir), { + ignored: [ + /(^|[\/\\])\./, // Ignore dotfiles + /node_modules/, + /\.test\./, // Ignore test files + /\.spec\./, + ], + persistent: true, + ignoreInitial: true, +}); + +watcher + .on('add', filePath => { + console.log(`โž• Added: ${path.relative(projectRoot, filePath)}`); + triggerBuild(); + }) + .on('change', filePath => { + console.log(`โœ๏ธ Changed: ${path.relative(projectRoot, filePath)}`); + triggerBuild(); + }) + .on('unlink', filePath => { + console.log(`โž– Removed: ${path.relative(projectRoot, filePath)}`); + triggerBuild(); + }) + .on('error', error => console.error(`Watcher error: ${error}`)) + .on('ready', () => { + console.log('๐Ÿ‘€ Watching for changes in src/...'); + console.log('๐Ÿ’ก Tip: Press Ctrl+C to stop\n'); + + // Run initial build and sync on startup + console.log('๐Ÿš€ Initial build and sync...'); + triggerBuild(); + }); + +process.on('SIGINT', () => { + console.log('\n๐Ÿ‘‹ Closing watcher...'); + watcher.close(); + console.log('Bye!'); + process.exit(0); +}); diff --git a/src/utils/onekey/dapp.ts b/src/utils/onekey/dapp.ts index 9d2517f..abc224a 100644 --- a/src/utils/onekey/dapp.ts +++ b/src/utils/onekey/dapp.ts @@ -4,13 +4,18 @@ import * as CardanoMessage from '@emurgo/cardano-message-signing-asmjs/cardano_m import BigNumber from 'bignumber.js'; import { getUtxos as getRawUtxos, requestAccountKey } from './signTx'; import { DataSignError } from './error'; +import { CoinSelectionError } from '../errors'; +import { ERROR } from '../../constants'; import { IAdaAmount, IAdaUTXO, - IChangeAddress, IEncodedTxADA, IEncodeInput, IEncodeOutput, + IStakingInfo, + ICardanoCertificate, + CardanoCertificateType, + IConvertCborTxParams, } from './types'; const getBalance = async (balances: IAdaAmount[]) => { @@ -93,19 +98,101 @@ const getUtxos = async ( ); }; -const convertCborTxToEncodeTx = async ( - txHex: string, - utxos: IAdaUTXO[], - addresses: string[], - changeAddress: IChangeAddress, -): Promise => { - const tx = CardanoWasm.Transaction.from_bytes(Buffer.from(txHex, 'hex')); - const body = tx.body(); +const convertCborTxToEncodeTx = async ({ + txHex, + utxos, + addresses, + changeAddress, + isSignOnly, +}: IConvertCborTxParams): Promise => { + let body: CardanoWasm.TransactionBody; + let rawTxHex: string; + + // console.log('CARDANO LOCAL_VERSION : 333====>>>'); + try { + const tx = CardanoWasm.Transaction.from_bytes(Buffer.from(txHex, 'hex')); + body = tx.body(); + rawTxHex = txHex; + } catch { + // Input is TransactionBody only (e.g., from staking providers like stakefish) + // Build a complete Transaction with empty witness set + body = CardanoWasm.TransactionBody.from_bytes(Buffer.from(txHex, 'hex')); + const emptyWitnessSet = CardanoWasm.TransactionWitnessSet.new(); + const tx = CardanoWasm.Transaction.new(body, emptyWitnessSet); + rawTxHex = Buffer.from(tx.to_bytes() as any, 'hex').toString('hex'); + } // Fee const fee = body.fee().to_str(); const totalFeeInNative = new BigNumber(fee).shiftedBy(-1 * 6).toFixed(); + // Parse certificates first (needed to determine if this is a staking transaction) + const certificates: ICardanoCertificate[] = []; + let poolId: string | undefined; + const certs = body.certs(); + if (certs) { + const certsLen = certs.len(); + for (let i = 0; i < certsLen; i++) { + const cert = certs.get(i); + const certKind = cert.kind(); + + // Stake Registration (kind = 0) + if (certKind === CardanoWasm.CertificateKind.StakeRegistration) { + const stakeReg = cert.as_stake_registration(); + if (stakeReg) { + const stakeCredential = stakeReg.stake_credential(); + const keyHash = stakeCredential.to_keyhash(); + certificates.push({ + type: CardanoCertificateType.STAKE_REGISTRATION, + stakeCredential: keyHash + ? Buffer.from(keyHash.to_bytes() as any, 'hex').toString('hex') + : undefined, + }); + } + } + + // Stake Deregistration (kind = 1) + if (certKind === CardanoWasm.CertificateKind.StakeDeregistration) { + const stakeDeReg = cert.as_stake_deregistration(); + if (stakeDeReg) { + const stakeCredential = stakeDeReg.stake_credential(); + const keyHash = stakeCredential.to_keyhash(); + certificates.push({ + type: CardanoCertificateType.STAKE_DEREGISTRATION, + stakeCredential: keyHash + ? Buffer.from(keyHash.to_bytes() as any, 'hex').toString('hex') + : undefined, + }); + } + } + + // Stake Delegation (kind = 2) + if (certKind === CardanoWasm.CertificateKind.StakeDelegation) { + const stakeDelegation = cert.as_stake_delegation(); + if (stakeDelegation) { + const stakeCredential = stakeDelegation.stake_credential(); + const keyHash = stakeCredential.to_keyhash(); + const poolKeyHash = stakeDelegation.pool_keyhash(); + poolId = Buffer.from(poolKeyHash.to_bytes() as any, 'hex').toString( + 'hex', + ); + certificates.push({ + type: CardanoCertificateType.STAKE_DELEGATION, + stakeCredential: keyHash + ? Buffer.from(keyHash.to_bytes() as any, 'hex').toString('hex') + : undefined, + poolKeyHash: poolId, + }); + } + } + } + } + + const isStakingTx = certificates.length > 0; + const stakingInfo: IStakingInfo | undefined = isStakingTx + ? { isStakingTx: true, certificates, poolId } + : undefined; + // inputs txs const encodeInputs: IEncodedTxADA['inputs'] = []; const inputs: { tx_hash: string; tx_index: number }[] = []; @@ -121,12 +208,32 @@ const convertCborTxToEncodeTx = async ( const utxo = utxos.find( utxo => utxo.tx_hash === txHash && +utxo.tx_index === +index, ); - encodeInputs.push(utxo as unknown as IEncodeInput); + // Only push matched UTXOs, skip external inputs (e.g., from DeFi protocols) + if (utxo) { + encodeInputs.push(utxo as unknown as IEncodeInput); + } } // outputs txs const outputs: IEncodeOutput[] = []; const outputsLen = body.outputs().len(); + + // Staking transactions (delegation, registration, deregistration) can have empty outputs + // Regular transactions must have at least one output + if (outputsLen === 0 && !isStakingTx) { + console.log('[convertCborTxToEncodeTx] Empty outputs detected, transaction data:', { + fee, + totalFeeInNative, + inputsCount: inputsLen, + inputs: inputs, + matchedUtxos: encodeInputs.map(u => ({ + tx_hash: u.tx_hash, + tx_index: u.tx_index, + amount: u.amount, + })), + }); + throw new CoinSelectionError(ERROR.UTXO_BALANCE_INSUFFICIENT); + } for (let i = 0; i < outputsLen; i++) { const output = body.outputs().get(i); const address = output.address().to_bech32(); @@ -176,13 +283,24 @@ const convertCborTxToEncodeTx = async ( }); } - const totalSpent = BigNumber.sum(...outputs.map(o => o.amount)).toFixed(); + const totalSpent = outputs.length > 0 + ? BigNumber.sum(...outputs.map(o => o.amount)).toFixed() + : '0'; const token = outputs .filter(o => !addresses.includes(o.address)) .find(o => o.assets.length > 0)?.assets?.[0].unit; - const encodedTx = { + // Determine 'from' address: prefer matched input, fallback to changeAddress + const fromAddress = + encodeInputs[0]?.address || changeAddress.address || ''; + + // For staking transactions, 'to' can be the pool id or empty + const toAddress = isStakingTx + ? fromAddress || '' + : outputs[0]?.address || ''; + + const encodedTx: IEncodedTxADA = { inputs: encodeInputs.map(input => ({ ...input, txHash: input.tx_hash, @@ -193,8 +311,8 @@ const convertCborTxToEncodeTx = async ( totalSpent, totalFeeInNative, transferInfo: { - from: encodeInputs[0].address, - to: outputs[0].address, + from: fromAddress, + to: toAddress, amount: totalSpent, token, }, @@ -204,9 +322,10 @@ const convertCborTxToEncodeTx = async ( .transaction_hash() .to_hex(), size: 0, - rawTxHex: txHex, + rawTxHex, }, - signOnly: true, + signOnly: isSignOnly, + staking: stakingInfo, }; console.log('Cardano DApp EncodedTx: ', encodedTx); diff --git a/src/utils/onekey/txToOneKey.ts b/src/utils/onekey/txToOneKey.ts index e3f56c2..2283914 100644 --- a/src/utils/onekey/txToOneKey.ts +++ b/src/utils/onekey/txToOneKey.ts @@ -28,6 +28,16 @@ export enum CardanoCertificateType { STAKE_DEREGISTRATION = 1, STAKE_DELEGATION = 2, STAKE_POOL_REGISTRATION = 3, + STAKE_REGISTRATION_CONWAY = 7, + STAKE_DEREGISTRATION_CONWAY = 8, + VOTE_DELEGATION = 9, +} + +export enum CardanoDRepType { + KEY_HASH = 0, + SCRIPT_HASH = 1, + ABSTAIN = 2, + NO_CONFIDENCE = 3, } export enum CardanoPoolRelayType { @@ -218,9 +228,22 @@ export const txToOneKey = async ( for (let i = 0; i < certificates.len(); i++) { const cert = certificates.get(i); const certificate: any = {}; - if (cert.kind() === 0) { - const credential = cert.as_stake_registration()?.stake_credential(); - certificate.type = CardanoCertificateType.STAKE_REGISTRATION; + const certKind = cert.kind(); + + if (certKind === 0) { + const stakeRegistration = cert.as_stake_registration(); + const credential = stakeRegistration?.stake_credential(); + const deposit = stakeRegistration?.coin?.(); + + if (deposit) { + // Conway era - reg_cert with deposit + certificate.type = CardanoCertificateType.STAKE_REGISTRATION_CONWAY; + certificate.deposit = deposit.to_str(); + } else { + // Pre-Conway - stake_registration without deposit + certificate.type = CardanoCertificateType.STAKE_REGISTRATION; + } + if (credential?.kind() === 0) { certificate.path = keys.stake.path; } else { @@ -229,9 +252,20 @@ export const txToOneKey = async ( ).toString('hex'); certificate.scriptHash = scriptHash; } - } else if (cert.kind() === 1) { - const credential = cert.as_stake_deregistration().stake_credential(); - certificate.type = CardanoCertificateType.STAKE_DEREGISTRATION; + } else if (certKind === 1) { + const stakeDeregistration = cert.as_stake_deregistration(); + const credential = stakeDeregistration.stake_credential(); + const deposit = stakeDeregistration.coin?.(); + + if (deposit) { + // Conway era - unreg_cert with deposit + certificate.type = CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY; + certificate.deposit = deposit.to_str(); + } else { + // Pre-Conway - stake_deregistration without deposit + certificate.type = CardanoCertificateType.STAKE_DEREGISTRATION; + } + if (credential.kind() === 0) { certificate.path = keys.stake.path; } else { @@ -240,7 +274,7 @@ export const txToOneKey = async ( ).toString('hex'); certificate.scriptHash = scriptHash; } - } else if (cert.kind() === 2) { + } else if (certKind === 2) { const delegation = cert.as_stake_delegation(); const credential = delegation.stake_credential(); const poolKeyHashHex = Buffer.from( @@ -256,7 +290,7 @@ export const txToOneKey = async ( certificate.scriptHash = scriptHash; } certificate.pool = poolKeyHashHex; - } else if (cert.kind() === 3) { + } else if (certKind === 3) { const params = cert.as_pool_registration().pool_params(); certificate.type = CardanoCertificateType.STAKE_POOL_REGISTRATION; const owners = params.pool_owners(); @@ -342,6 +376,45 @@ export const txToOneKey = async ( relays: onekeyRelays, metadata, }; + } else if (certKind === 15) { + // VoteDelegation (CertificateKind.VoteDelegation = 15 in WASM lib) + const voteDelegation = cert.as_vote_delegation(); + const credential = voteDelegation.stake_credential(); + certificate.type = CardanoCertificateType.VOTE_DELEGATION; + + if (credential.kind() === 0) { + certificate.path = keys.stake.path; + } else { + const scriptHash = Buffer.from( + credential.to_scripthash().to_bytes(), + ).toString('hex'); + certificate.scriptHash = scriptHash; + } + + // Parse DRep + const drep = voteDelegation.drep(); + const drepKind = drep.kind(); + // DRepKind: KeyHash = 0, ScriptHash = 1, AlwaysAbstain = 2, AlwaysNoConfidence = 3 + certificate.dRep = { + type: drepKind as CardanoDRepType, + }; + + if (drepKind === 0) { + // KEY_HASH + certificate.dRep.keyHash = drep.to_key_hash()?.to_hex(); + } else if (drepKind === 1) { + // SCRIPT_HASH + certificate.dRep.scriptHash = drep.to_script_hash()?.to_hex(); + } + // ABSTAIN (2) and NO_CONFIDENCE (3) don't need additional fields + } else { + // Skip unsupported certificate types (kind 4, 5, 6, etc.) + // Don't push empty certificate objects + console.warn( + `[txToOneKey] Unsupported certificate kind: ${certKind}, cert:`, + cert.to_hex(), + ); + continue; } onekeyCertificates.push(certificate); } diff --git a/src/utils/onekey/types.ts b/src/utils/onekey/types.ts index 91ac02a..8b31fcb 100644 --- a/src/utils/onekey/types.ts +++ b/src/utils/onekey/types.ts @@ -50,6 +50,7 @@ type ITxInfo = { body: string; hash: string; size: number; + rawTxHex?: string; }; export type IEncodedTxADA = { @@ -61,6 +62,7 @@ export type IEncodedTxADA = { transferInfo: ITransferInfo; tx: ITxInfo; signOnly?: boolean; + staking?: IStakingInfo; }; enum CardanoAddressType { @@ -85,3 +87,31 @@ export type IChangeAddress = { stakingPath: string; }; }; + +// Staking certificate types +export enum CardanoCertificateType { + STAKE_REGISTRATION = 0, + STAKE_DEREGISTRATION = 1, + STAKE_DELEGATION = 2, + STAKE_POOL_REGISTRATION = 3, +} + +export type IStakingInfo = { + isStakingTx: boolean; + certificates: ICardanoCertificate[]; + poolId?: string; // For delegation +}; + +export type ICardanoCertificate = { + type: CardanoCertificateType; + stakeCredential?: string; // stake key hash + poolKeyHash?: string; // for delegation +}; + +export type IConvertCborTxParams = { + txHex: string; + utxos: IAdaUTXO[]; + addresses: string[]; + changeAddress: IChangeAddress; + isSignOnly: boolean; +}; diff --git a/yarn.lock b/yarn.lock index 616dca5..5d4e3aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1624,10 +1624,13 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^8.8.0" "@typescript-eslint/parser": "npm:8.8.0" bignumber.js: "npm:^9.0.1" + chokidar: "npm:^5.0.0" + dotenv: "npm:^17.2.3" eslint: "npm:^9.11.1" eslint-config-prettier: "npm:^9.1.0" eslint-plugin-import: "npm:^2.30.0" eslint-plugin-prettier: "npm:^5.2.1" + fs-extra: "npm:^11.3.2" jest: "npm:^29.7.0" make-coverage-badge: "npm:^1.2.0" npm-run-all: "npm:^4.1.5" @@ -2788,6 +2791,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^5.0.0": + version: 5.0.0 + resolution: "chokidar@npm:5.0.0" + dependencies: + readdirp: "npm:^5.0.0" + checksum: 10/a1c2a4ee6ee81ba6409712c295a47be055fb9de1186dfbab33c1e82f28619de962ba02fc5f9d433daaedc96c35747460d8b2079ac2907de2c95e3f7cce913113 + languageName: node + linkType: hard + "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -3149,6 +3161,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^17.2.3": + version: 17.2.3 + resolution: "dotenv@npm:17.2.3" + checksum: 10/f8b78626ebfff6e44420f634773375c9651808b3e1a33df6d4cc19120968eea53e100f59f04ec35f2a20b2beb334b6aba4f24040b2f8ad61773f158ac042a636 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -3791,6 +3810,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.3.2": + version: 11.3.2 + resolution: "fs-extra@npm:11.3.2" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/d559545c73fda69c75aa786f345c2f738b623b42aea850200b1582e006a35278f63787179e3194ba19413c26a280441758952b0c7e88dd96762d497e365a6c3e + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -4036,6 +4066,13 @@ __metadata: languageName: node linkType: hard +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 + languageName: node + linkType: hard + "graceful-fs@npm:^4.2.9": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" @@ -5239,6 +5276,19 @@ __metadata: languageName: node linkType: hard +"jsonfile@npm:^6.0.1": + version: 6.2.0 + resolution: "jsonfile@npm:6.2.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10/513aac94a6eff070767cafc8eb4424b35d523eec0fcd8019fe5b975f4de5b10a54640c8d5961491ddd8e6f562588cf62435c5ddaf83aaf0986cd2ee789e0d7b9 + languageName: node + linkType: hard + "keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -6224,6 +6274,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^5.0.0": + version: 5.0.0 + resolution: "readdirp@npm:5.0.0" + checksum: 10/a17a591b51d8b912083660df159e8bd17305dc1a9ef27c869c818bd95ff59e3a6496f97e91e724ef433e789d559d24e39496ea1698822eb5719606dc9c1a923d + languageName: node + linkType: hard + "regexp.prototype.flags@npm:^1.5.2": version: 1.5.2 resolution: "regexp.prototype.flags@npm:1.5.2" @@ -7184,6 +7241,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10/ecd8469fe0db28e7de9e5289d32bd1b6ba8f7183db34f3bfc4ca53c49891c2d6aa05f3fb3936a81285a905cc509fb641a0c3fc131ec786167eff41236ae32e60 + languageName: node + linkType: hard + "update-browserslist-db@npm:^1.0.5": version: 1.0.5 resolution: "update-browserslist-db@npm:1.0.5"