-
-
Notifications
You must be signed in to change notification settings - Fork 197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🐛 fix: Align cache hashes #1248
base: dev
Are you sure you want to change the base?
Changes from all commits
241e6af
3e81e68
9f4ef40
da05776
135f033
0f1df5c
609f1a5
dddfbb0
87afe50
c65b17d
b9ed81d
bd22ef9
73a59ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,20 @@ | ||
import { getUniqueWalletSetupFunctions } from './utils/getUniqueWalletSetupFunctions' | ||
import { triggerCacheCreation } from './utils/triggerCacheCreation' | ||
|
||
export async function createCache(walletSetupDirPath: string, downloadExtension: () => Promise<string>, force = false) { | ||
export async function createCache( | ||
walletSetupDirPath: string, | ||
hashes: string[], | ||
downloadExtension: () => Promise<string>, | ||
force = false | ||
) { | ||
const setupFunctions = await getUniqueWalletSetupFunctions(walletSetupDirPath) | ||
|
||
const cacheCreationPromises = await triggerCacheCreation(setupFunctions, downloadExtension, force) | ||
const cacheCreationOutput = await triggerCacheCreation(setupFunctions, hashes, downloadExtension, force) | ||
|
||
if (cacheCreationPromises.length === 0) { | ||
if (cacheCreationOutput.length === 0) { | ||
console.log('No new setup functions to cache. Exiting...') | ||
return | ||
} | ||
|
||
// TODO: This line has no unit test. Not sure how to do it. Look into it later. | ||
await Promise.all(cacheCreationPromises) | ||
|
||
console.log('All wallet setup functions are now cached!') | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { transformSync } from 'esbuild' | ||
|
||
export default function buildWalletSetupFunction(walletSetupFunctionString: string) { | ||
const { code } = transformSync(walletSetupFunctionString, { | ||
format: 'esm', | ||
minifyWhitespace: true, | ||
target: 'es2022', | ||
drop: ['console', 'debugger'], | ||
loader: 'ts', | ||
logLevel: 'silent', | ||
platform: 'node' | ||
}) | ||
|
||
return code | ||
} |
Original file line number | Diff line number | Diff line change | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,12 @@ | |||||||||||||||||
export function extractWalletSetupFunction(sourceCode: string): string { | |||||||||||||||||
const match = sourceCode.match( | |||||||||||||||||
/defineWalletSetup\s*\([^,]*,\s*(async\s*\([^)]*\)\s*=>\s*{(?:[^{}]*|{(?:[^{}]*|{[^{}]*})*})*})\s*\)/ | |||||||||||||||||
Check failure Code scanning / CodeQL Inefficient regular expression High
This part of the regular expression may cause exponential backtracking on strings starting with 'defineWalletSetup(,async()=>{{' and containing many repetitions of 'z'.
Copilot Autofix AI 25 days ago To fix the problem, we need to modify the regular expression to remove the ambiguity that leads to catastrophic backtracking. This can be achieved by making the pattern more specific and less ambiguous. In this case, we can replace the ambiguous The best way to fix the problem without changing existing functionality is to rewrite the regular expression to avoid nested quantifiers and ambiguous patterns. Specifically, we can use a non-recursive approach to match the nested braces and their contents.
Suggested changeset
1
packages/cache/src/utils/extractWalletSetupFunction.ts
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||
) | |||||||||||||||||
|
|||||||||||||||||
if (!match || !match[1]) { | |||||||||||||||||
console.log('Failed to extract defineWalletSetup callback from:', sourceCode) | |||||||||||||||||
throw new Error('Could not find defineWalletSetup callback') | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
return match[1] | |||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,12 @@ | ||
import { createHash } from 'node:crypto' | ||
import esbuild from 'esbuild' | ||
|
||
// Same length as the file part (first part before the `-`) of a Playwright Test ID. | ||
export const WALLET_SETUP_FUNC_HASH_LENGTH = 10 | ||
|
||
// biome-ignore lint/suspicious/noExplicitAny: any type here is intentional | ||
type AnyFunction = (...args: any) => Promise<any> | ||
|
||
export function getWalletSetupFuncHash(walletSetupFunc: AnyFunction) { | ||
// This transformation is necessary because a user could end up using a different execution engine than Playwright. | ||
// Different execution engines -> different codes -> different hashes. | ||
const { code } = esbuild.transformSync(walletSetupFunc.toString(), { | ||
format: 'esm', | ||
minifyWhitespace: true, | ||
drop: ['console', 'debugger'], | ||
loader: 'ts', | ||
logLevel: 'silent' | ||
}) | ||
|
||
export function getWalletSetupFuncHash(walletSetupString: string) { | ||
const hash = createHash('shake256', { | ||
outputLength: WALLET_SETUP_FUNC_HASH_LENGTH | ||
}) | ||
|
||
return hash.update(code).digest('hex') | ||
return hash.update(walletSetupString).digest('hex') | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,50 @@ | ||
import path from 'node:path' | ||
import fs from 'fs-extra' | ||
import type { WalletSetupFunction } from '../defineWalletSetup' | ||
import { ensureCacheDirExists } from '../ensureCacheDirExists' | ||
import { createCacheForWalletSetupFunction } from './createCacheForWalletSetupFunction' | ||
import { getUniqueWalletSetupFunctions } from './getUniqueWalletSetupFunctions' | ||
import { isDirEmpty } from './isDirEmpty' | ||
|
||
export async function triggerCacheCreation( | ||
setupFunctions: Awaited<ReturnType<typeof getUniqueWalletSetupFunctions>>, | ||
setupFunctions: Map<string, { fileName: string; setupFunction: WalletSetupFunction }>, | ||
hashes: string[], | ||
downloadExtension: () => Promise<string>, | ||
force: boolean | ||
) { | ||
const cacheDirPath = ensureCacheDirExists() | ||
const extensionPath = await downloadExtension() | ||
|
||
const cacheCreationPromises = [] | ||
|
||
for (const [funcHash, { fileName, setupFunction }] of setupFunctions) { | ||
const cachePath = path.join(cacheDirPath, funcHash) | ||
const doesCacheDirExist = await fs.exists(cachePath) | ||
const isCacheDirEmpty = await isDirEmpty(cachePath) | ||
return await Promise.all( | ||
Array.from(setupFunctions).map(async ([_, { fileName, setupFunction }], index) => { | ||
if (!hashes[index]) { | ||
throw new Error(`No hash found for ${fileName}`) | ||
} | ||
|
||
if (doesCacheDirExist) { | ||
if (isCacheDirEmpty) { | ||
// In case of incorrect Playwright setup, the cache dir will be empty. For now, we're just deleting it. | ||
await fs.remove(cachePath) | ||
} else { | ||
if (!force) { | ||
console.log(`Cache already exists for ${funcHash}. Skipping...`) | ||
continue | ||
} | ||
const funcHash = hashes[index] | ||
|
||
console.log(`Cache already exists for ${funcHash} but force flag is set. Deleting cache...`) | ||
await fs.remove(cachePath) | ||
} | ||
} | ||
const cachePath = path.join(cacheDirPath, funcHash || 'unknown') | ||
const doesCacheDirExist = await fs.exists(cachePath) | ||
const isCacheDirEmpty = await isDirEmpty(cachePath) | ||
|
||
const fileNameWithCorrectExtension = fileName.replace(/\.(ts|js|mjs)$/, '.{ts,js,mjs}') | ||
console.log(`Triggering cache creation for: ${funcHash} (${fileNameWithCorrectExtension})`) | ||
if (doesCacheDirExist) { | ||
if (isCacheDirEmpty) { | ||
// In case of incorrect Playwright setup, the cache dir will be empty. For now, we're just deleting it. | ||
await fs.remove(cachePath) | ||
} else { | ||
if (!force) { | ||
console.log(`Cache already exists for ${funcHash}. Skipping...`) | ||
return | ||
} | ||
|
||
// We're not inferring the return type here to make sure we don't accidentally await the function. | ||
const createCachePromise: Promise<void> = createCacheForWalletSetupFunction( | ||
extensionPath, | ||
cachePath, | ||
setupFunction, | ||
fileNameWithCorrectExtension | ||
) | ||
cacheCreationPromises.push(createCachePromise) | ||
} | ||
console.log(`Cache already exists for ${funcHash} but force flag is set. Deleting cache...`) | ||
await fs.remove(cachePath) | ||
} | ||
} | ||
|
||
return cacheCreationPromises | ||
const fileNameWithCorrectExtension = fileName.replace(/\.(ts|js|mjs)$/, '.{ts,js,mjs}') | ||
console.log(`Triggering cache creation for: ${funcHash} (${fileNameWithCorrectExtension})`) | ||
// We're not inferring the return type here to make sure we don't accidentally await the function. | ||
return createCacheForWalletSetupFunction(extensionPath, cachePath, setupFunction, fileNameWithCorrectExtension) | ||
}) | ||
) | ||
} |
Check failure
Code scanning / CodeQL
Inefficient regular expression High
Copilot Autofix AI 25 days ago
To fix the problem, we need to modify the regular expression to remove the ambiguity and reduce the potential for excessive backtracking. This can be achieved by making the sub-expressions more specific and avoiding patterns that can match the same string in multiple ways. Specifically, we can replace
[^{}]*
with a more precise pattern that matches the intended input without allowing for excessive backtracking.The best way to fix the problem is to rewrite the regular expression to avoid ambiguous sub-expressions and nested quantifiers. We can achieve this by using non-capturing groups and more specific character classes.