Skip to content

Commit

Permalink
chore: new cache generation
Browse files Browse the repository at this point in the history
  • Loading branch information
matstyler committed Nov 28, 2024
1 parent 9f4ef40 commit da05776
Show file tree
Hide file tree
Showing 14 changed files with 431 additions and 545 deletions.
27 changes: 20 additions & 7 deletions packages/cache/src/cli/cliEntrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ export const cliEntrypoint = async () => {
.addHelpText('afterAll', `\n${footer}\n`)
.parse(process.argv)

let walletSetupDir = program.args[0]
if (!walletSetupDir) {
walletSetupDir = path.join(process.cwd(), 'test', WALLET_SETUP_DIR_NAME)
let walletSetupDir = path.join(process.cwd(), 'test', WALLET_SETUP_DIR_NAME)

if (program.args[0]) {
walletSetupDir = path.join(process.cwd(), program.args[0])
}

const flags: CliFlags = program.opts()
Expand All @@ -47,7 +48,14 @@ export const cliEntrypoint = async () => {

if (flags.debug) {
console.log('[DEBUG] Running with the following options:')
console.log({ cacheDir: walletSetupDir, ...flags, headless: Boolean(process.env.HEADLESS) ?? false }, '\n')
console.log(
{
cacheDir: walletSetupDir,
...flags,
headless: Boolean(process.env.HEADLESS) ?? false
},
'\n'
)
}

if (os.platform() === 'win32') {
Expand All @@ -62,10 +70,15 @@ export const cliEntrypoint = async () => {
process.exit(1)
}

const { outDir: compiledWalletSetupDirPath, functionStrings } = await compileWalletSetupFunctions(walletSetupDir, flags.debug)
console.log(chalk.greenBright('🚀 Building the cache for wallet setup functions... 🚀\n'))

const { outDir: compiledWalletSetupDirPath, setupFunctionHashes } = await compileWalletSetupFunctions(
walletSetupDir,
flags.debug
)

// TODO: We should be using `prepareExtension` function from the wallet itself!
await createCache(compiledWalletSetupDirPath, functionStrings, prepareExtension, flags.force)
// TODO: We should be using `prepareExtension` function from the wallet itself!
await createCache(compiledWalletSetupDirPath, setupFunctionHashes, prepareExtension, flags.force)

if (!flags.debug) {
await rimraf(compiledWalletSetupDirPath)
Expand Down
81 changes: 39 additions & 42 deletions packages/cache/src/cli/compileWalletSetupFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,73 @@
import path from "node:path";
import { glob } from "glob";
import { build } from "tsup";
import { ensureCacheDirExists } from "../ensureCacheDirExists";
import { FIXES_BANNER } from "./compilationFixes";
import buildWalletSetupFunction from "../utils/buildWalletSetupFunction";
import path from 'node:path'
import fs from 'fs-extra'
import { glob } from 'glob'
import { build } from 'tsup'

const OUT_DIR_NAME = "wallet-setup-dist";
import { ensureCacheDirExists } from '../ensureCacheDirExists'
import buildWalletSetupFunction from '../utils/buildWalletSetupFunction'
import { extractWalletSetupFunction } from '../utils/extractWalletSetupFunction'
import { getWalletSetupFuncHash } from '../utils/getWalletSetupFuncHash'
import { FIXES_BANNER } from './compilationFixes'

const createGlobPattern = (walletSetupDir: string) =>
path.join(walletSetupDir, "**", "*.setup.{ts,js,mjs}");
const OUT_DIR_NAME = '.wallet-setup-dist'

export async function compileWalletSetupFunctions(
walletSetupDir: string,
debug: boolean
) {
const outDir = path.join(ensureCacheDirExists(), OUT_DIR_NAME);
const createGlobPattern = (walletSetupDir: string) => path.join(walletSetupDir, '**', '*.setup.{ts,js,mjs}')

const globPattern = createGlobPattern(walletSetupDir);
const fileList = await glob(globPattern);
export async function compileWalletSetupFunctions(walletSetupDir: string, debug: boolean) {
const outDir = path.join(ensureCacheDirExists(), OUT_DIR_NAME)

fs.ensureDirSync(outDir)

const globPattern = createGlobPattern(walletSetupDir)
const fileList = await glob(globPattern)

if (debug) {
console.log("[DEBUG] Found the following wallet setup files:");
console.log(fileList, "\n");
console.log('[DEBUG] Found the following wallet setup files:')
console.log(fileList, '\n')
}

// TODO: This error message is copied over from another function. Refactor this.
if (!fileList.length) {
throw new Error(
[
`No wallet setup files found at ${walletSetupDir}`,
"Remember that all wallet setup files must end with `.setup.{ts,js,mjs}` extension!",
].join("\n")
);
'Remember that all wallet setup files must end with `.setup.{ts,js,mjs}` extension!'
].join('\n')
)
}

await build({
name: "cli-build",
name: 'cli-build',
silent: true,
entry: fileList,
clean: true,
outDir,
format: "esm",
format: 'esm',
splitting: true,
sourcemap: false,
config: false,
// TODO: Make this list configurable.
external: [
"@synthetixio/synpress",
"@playwright/test",
"playwright-core",
"esbuild",
"tsup",
],
external: ['@synthetixio/synpress', '@playwright/test', 'playwright-core', 'esbuild', 'tsup'],
banner: {
js: FIXES_BANNER,
js: FIXES_BANNER
},
esbuildOptions(options) {
// TODO: In this step, if the debug file is present, we should modify `console.log` so it prints from which file the log is coming from.
// We're dropping `console.log` and `debugger` statements because they do not play nicely with the Playwright Test Runner.
options.drop = debug ? [] : ["console", "debugger"];
},
});
options.drop = debug ? [] : ['console', 'debugger']
}
})

const functionStrings = await Promise.all(
fileList.map(async (fileName) => {
const walletSetupFunction = await import(fileName);
const setupFunctionHashes = await Promise.all(
fileList.map(async (filePath) => {
const sourceCode = fs.readFileSync(filePath, 'utf8')
const functionString = extractWalletSetupFunction(sourceCode)

return buildWalletSetupFunction(walletSetupFunction.toString());
})
);
const rawFunctionBuild = buildWalletSetupFunction(functionString)

console.log({functionStrings})
return getWalletSetupFuncHash(rawFunctionBuild)
})
)

return { outDir, functionStrings: functionStrings };
return { outDir, setupFunctionHashes }
}
25 changes: 9 additions & 16 deletions packages/cache/src/createCache.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import { getUniqueWalletSetupFunctions } from "./utils/getUniqueWalletSetupFunctions";
import { triggerCacheCreation } from "./utils/triggerCacheCreation";
import { getUniqueWalletSetupFunctions } from './utils/getUniqueWalletSetupFunctions'
import { triggerCacheCreation } from './utils/triggerCacheCreation'

export async function createCache(
walletSetupDirPath: string,
functionStrings: string[],
hashes: string[],
downloadExtension: () => Promise<string>,
force = false
) {
const setupFunctions = await getUniqueWalletSetupFunctions(
walletSetupDirPath
);
const setupFunctions = await getUniqueWalletSetupFunctions(walletSetupDirPath)

const cacheCreationPromises = await triggerCacheCreation(
setupFunctions,
functionStrings,
downloadExtension,
force
);
const cacheCreationPromises = await triggerCacheCreation(setupFunctions, hashes, downloadExtension, force)

if (cacheCreationPromises.length === 0) {
console.log("No new setup functions to cache. Exiting...");
return;
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);
await Promise.all(cacheCreationPromises)

console.log("All wallet setup functions are now cached!");
console.log('All wallet setup functions are now cached!')
}
2 changes: 1 addition & 1 deletion packages/cache/src/defineWalletSetup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BrowserContext, Page } from 'playwright-core'
import buildWalletSetupFunction from './utils/buildWalletSetupFunction'
import { getWalletSetupFuncHash } from './utils/getWalletSetupFuncHash'
import buildWalletSetupFunction from './utils/buildWalletSetupFunction';

// TODO: Should we export this type in the `release` package?
export type WalletSetupFunction = (context: BrowserContext, walletPage: Page) => Promise<void>
Expand Down
24 changes: 10 additions & 14 deletions packages/cache/src/utils/buildWalletSetupFunction.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { transformSync } from "esbuild";
import { FIXES_BANNER } from "../cli/compilationFixes";
import { transformSync } from 'esbuild'

export default function buildWalletSetupFunction(
walletSetupFunctionString: string
) {
export default function buildWalletSetupFunction(walletSetupFunctionString: string) {
const { code } = transformSync(walletSetupFunctionString, {
format: "esm",
format: 'esm',
minifyWhitespace: true,
target: "es2022",
drop: ["console", "debugger"],
loader: "ts",
logLevel: "silent",
platform: "node",
banner: FIXES_BANNER,
});
target: 'es2022',
drop: ['console', 'debugger'],
loader: 'ts',
logLevel: 'silent',
platform: 'node'
})

return code;
return code
}
11 changes: 11 additions & 0 deletions packages/cache/src/utils/extractWalletSetupFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function extractWalletSetupFunction(sourceCode: string): string {
const match = sourceCode.match(/defineWalletSetup\s*\([^,]*,\s*(async\s*\([^)]*\)\s*=>\s*{[\s\S]*?})\s*\)/)

if (!match || !match[1]) {
console.log('Failed to extract defineWalletSetup callback from:', sourceCode)
throw new Error('Could not find defineWalletSetup callback')
}

// Return just the callback function (second parameter)
return match[1]
}
13 changes: 6 additions & 7 deletions packages/cache/src/utils/getWalletSetupFuncHash.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { createHash } from "node:crypto";
import { createHash } from 'node:crypto'

// Same length as the file part (first part before the `-`) of a Playwright Test ID.
export const WALLET_SETUP_FUNC_HASH_LENGTH = 10;
export const WALLET_SETUP_FUNC_HASH_LENGTH = 10

export function getWalletSetupFuncHash(walletSetupString: string) {
const hash = createHash("shake256", {
outputLength: WALLET_SETUP_FUNC_HASH_LENGTH,
});
const hash = createHash('shake256', {
outputLength: WALLET_SETUP_FUNC_HASH_LENGTH
})

return hash.update(walletSetupString).digest("hex");
return hash.update(walletSetupString).digest('hex')
}

85 changes: 35 additions & 50 deletions packages/cache/src/utils/triggerCacheCreation.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,48 @@
import path from "node:path";
import fs from "fs-extra";
import { ensureCacheDirExists } from "../ensureCacheDirExists";
import { createCacheForWalletSetupFunction } from "./createCacheForWalletSetupFunction";
import { isDirEmpty } from "./isDirEmpty";
import { getWalletSetupFuncHash } from "./getWalletSetupFuncHash";
import type { WalletSetupFunction } from "../defineWalletSetup";
import path from 'node:path'
import fs from 'fs-extra'
import type { WalletSetupFunction } from '../defineWalletSetup'
import { ensureCacheDirExists } from '../ensureCacheDirExists'
import { createCacheForWalletSetupFunction } from './createCacheForWalletSetupFunction'
import { isDirEmpty } from './isDirEmpty'

export async function triggerCacheCreation(
setupFunctions: Map<
string,
{ fileName: string; setupFunction: WalletSetupFunction }
>,
functionStrings: string[],
setupFunctions: Map<string, { fileName: string; setupFunction: WalletSetupFunction }>,
hashes: string[],
downloadExtension: () => Promise<string>,
force: boolean
) {
const cacheDirPath = ensureCacheDirExists();
const extensionPath = await downloadExtension();
const cacheDirPath = ensureCacheDirExists()
const extensionPath = await downloadExtension()

return Array.from(setupFunctions).map(
async ([_, { fileName, setupFunction }], index) => {
// @ts-ignore
const funcHash = getWalletSetupFuncHash(functionStrings[index]);
return Array.from(setupFunctions).map(async ([_, { fileName, setupFunction }], index) => {
if (!hashes[index]) {
throw new Error(`No hash found for ${fileName}`)
}

const cachePath = path.join(cacheDirPath, funcHash);
const doesCacheDirExist = await fs.exists(cachePath);
const isCacheDirEmpty = await isDirEmpty(cachePath);
const funcHash = hashes[index]

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;
}
const cachePath = path.join(cacheDirPath, funcHash || 'unknown')
const doesCacheDirExist = await fs.exists(cachePath)
const isCacheDirEmpty = await isDirEmpty(cachePath)

console.log(
`Cache already exists for ${funcHash} but force flag is set. Deleting cache...`
);
await fs.remove(cachePath);
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
}
}

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
);
console.log(`Cache already exists for ${funcHash} but force flag is set. Deleting cache...`)
await fs.remove(cachePath)
}
}
);

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)
})
}
Loading

0 comments on commit da05776

Please sign in to comment.