diff --git a/packages/foundryup/CHANGELOG.md b/packages/foundryup/CHANGELOG.md index f6ce1417dc..28e96f663f 100644 --- a/packages/foundryup/CHANGELOG.md +++ b/packages/foundryup/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Replace duplicated cache directory and cache-clean helpers with + `@metamask/local-node-utils` ([#9239](https://github.com/MetaMask/core/pull/9239)). + - Scope foundryup downloads under a `foundryup` cache namespace, matching + other MetaMask installer packages. + ## [1.0.1] ### Fixed diff --git a/packages/foundryup/package.json b/packages/foundryup/package.json index 19bbdf0f17..3bc312aea5 100644 --- a/packages/foundryup/package.json +++ b/packages/foundryup/package.json @@ -45,6 +45,7 @@ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" }, "dependencies": { + "@metamask/local-node-utils": "workspace:^", "minipass": "^7.1.2", "tar": "^7.4.3", "unzipper": "^0.12.3", @@ -64,8 +65,7 @@ "ts-jest": "^29.2.5", "typedoc": "^0.25.13", "typedoc-plugin-missing-exports": "^2.0.0", - "typescript": "~5.3.3", - "yaml": "^2.3.4" + "typescript": "~5.3.3" }, "engines": { "node": "^18.18 || >=20" diff --git a/packages/foundryup/src/foundryup.test.ts b/packages/foundryup/src/foundryup.test.ts index da3268a3df..fe88a452e8 100644 --- a/packages/foundryup/src/foundryup.test.ts +++ b/packages/foundryup/src/foundryup.test.ts @@ -1,9 +1,11 @@ import type { Dir } from 'fs'; -import { readFileSync } from 'fs'; +import { mkdtempSync, writeFileSync } from 'fs'; import fs from 'fs/promises'; import nock, { cleanAll } from 'nock'; +import { tmpdir } from 'os'; import { join, relative } from 'path'; -import { parse as parseYaml } from 'yaml'; + +import { cleanInstallerCache } from '@metamask/local-node-utils'; import { checkAndDownloadBinaries, @@ -52,9 +54,13 @@ jest.mock('fs/promises', () => { }; }); -jest.mock('fs'); -jest.mock('yaml'); +jest.mock('node:fs/promises', () => jest.requireMock('fs/promises')); + +jest.mock('fs', () => ({ + ...jest.requireActual('fs'), +})); jest.mock('os', () => ({ + ...jest.requireActual('os'), homedir: jest.fn().mockReturnValue('/home/user'), })); @@ -121,7 +127,10 @@ const mockDownloadAndInstallFoundryBinaries = async (): Promise< const CACHE_DIR = getCacheDirectory(); if (parsedArgs.command === 'cache clean') { - await fs.rm(CACHE_DIR, { recursive: true, force: true }); + await cleanInstallerCache({ + cacheDirectory: CACHE_DIR, + namespace: 'foundryup', + }); operations.push({ operation: 'cleanCache', details: { path: CACHE_DIR } }); return operations; } @@ -148,9 +157,12 @@ const mockDownloadAndInstallFoundryBinaries = async (): Promise< ); const url = new URL(BIN_ARCHIVE_URL); + const cacheKey = 'mock-cache-key'; + const cachePath = join(CACHE_DIR, 'foundryup', cacheKey); + operations.push({ operation: 'checkAndDownloadBinaries', - details: { url, binaries, cachePath: CACHE_DIR, platform, arch }, + details: { url, binaries, cachePath, platform, arch }, }); operations.push({ @@ -158,7 +170,7 @@ const mockDownloadAndInstallFoundryBinaries = async (): Promise< details: { binaries, binDir: 'node_modules/.bin', - cachePath: CACHE_DIR, + cachePath, }, }); @@ -167,20 +179,29 @@ const mockDownloadAndInstallFoundryBinaries = async (): Promise< describe('foundryup', () => { describe('getCacheDirectory', () => { + const originalCwd = process.cwd; + + afterEach(() => { + process.chdir(originalCwd()); + }); + it('uses global cache when enabled in .yarnrc.yml', () => { - (parseYaml as jest.Mock).mockReturnValue({ enableGlobalCache: true }); - (readFileSync as jest.Mock).mockReturnValue('dummy yaml content'); + const projectDir = mkdtempSync(join(tmpdir(), 'foundryup-')); + process.chdir(projectDir); + writeFileSync(join(projectDir, '.yarnrc.yml'), 'enableGlobalCache: true\n'); - const result = getCacheDirectory(); - expect(result).toMatch(/\/(home|Users)\/.*\/\.cache\/metamask$/u); + expect(getCacheDirectory()).toMatch(/\.cache\/metamask$/u); }); it('uses local cache when global cache is disabled', () => { - (parseYaml as jest.Mock).mockReturnValue({ enableGlobalCache: false }); - (readFileSync as jest.Mock).mockReturnValue('dummy yaml content'); + const projectDir = mkdtempSync(join(tmpdir(), 'foundryup-')); + process.chdir(projectDir); + writeFileSync( + join(projectDir, '.yarnrc.yml'), + 'enableGlobalCache: false\n', + ); - const result = getCacheDirectory(); - expect(result).toContain('.metamask/cache'); + expect(getCacheDirectory()).toContain('.metamask/cache'); }); }); @@ -398,7 +419,7 @@ describe('foundryup', () => { }; (parseArgs as jest.Mock).mockReturnValue(mockCleanArgs); - const rmSpy = jest.spyOn(fs, 'rm').mockResolvedValue(); + const rmSpy = jest.spyOn(fs, 'rm').mockResolvedValue(undefined); const operations = await mockDownloadAndInstallFoundryBinaries(); @@ -411,7 +432,10 @@ describe('foundryup', () => { }, }, ]); - expect(rmSpy).toHaveBeenCalled(); + expect(rmSpy).toHaveBeenCalledWith( + expect.stringContaining('foundryup'), + expect.objectContaining({ force: true, recursive: true }), + ); }); it('should handle errors gracefully', async () => { diff --git a/packages/foundryup/src/index.ts b/packages/foundryup/src/index.ts index 309c759275..21fb2b1d2c 100755 --- a/packages/foundryup/src/index.ts +++ b/packages/foundryup/src/index.ts @@ -1,20 +1,21 @@ #!/usr/bin/env -S node --require "./node_modules/tsx/dist/preflight.cjs" --import "./node_modules/tsx/dist/loader.mjs" +import { + cleanInstallerCache, + getMetamaskCacheDirectory, + isFileMissingError, +} from '@metamask/local-node-utils'; import { createHash } from 'node:crypto'; -import { readFileSync } from 'node:fs'; import type { Dir } from 'node:fs'; import { copyFile, mkdir, opendir, - rm, symlink, unlink, } from 'node:fs/promises'; -import { homedir } from 'node:os'; import { dirname, join, relative } from 'node:path'; import { cwd, exit } from 'node:process'; -import { parse as parseYaml } from 'yaml'; import { extractFrom } from './extract'; import { parseArgs, printBanner } from './options'; @@ -28,6 +29,8 @@ import { transformChecksums, } from './utils'; +const FOUNDRYUP_CACHE_NAMESPACE = 'foundryup'; + /** * Determines the cache directory based on the .yarnrc.yml configuration. * If global cache is enabled, returns a path in the user's home directory. @@ -36,25 +39,10 @@ import { * @returns The path to the cache directory */ export function getCacheDirectory(): string { - let enableGlobalCache = false; - try { - const configFileContent = readFileSync('.yarnrc.yml', 'utf8'); - const parsedConfig = parseYaml(configFileContent); - enableGlobalCache = parsedConfig?.enableGlobalCache ?? false; - } catch (error) { - // If file doesn't exist or can't be read, default to local cache - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - return join(cwd(), '.metamask', 'cache'); - } - // For other errors, log but continue with default - console.warn( - 'Warning: Error reading .yarnrc.yml, using local cache:', - error, - ); - } - return enableGlobalCache - ? join(homedir(), '.cache', 'metamask') - : join(cwd(), '.metamask', 'cache'); + return getMetamaskCacheDirectory({ + cwd: cwd(), + toolName: FOUNDRYUP_CACHE_NAMESPACE, + }); } /** @@ -104,7 +92,7 @@ export async function checkAndDownloadBinaries( say(`found binaries in cache`); } catch (e: unknown) { say(`binaries not in cache`); - if ((e as NodeJS.ErrnoException).code === 'ENOENT') { + if (isFileMissingError(e)) { say(`installing from ${url.toString()}`); // directory doesn't exist, download and extract const platformChecksums = transformChecksums(checksums, platform, arch); @@ -176,7 +164,10 @@ export async function downloadAndInstallFoundryBinaries(): Promise { const CACHE_DIR = getCacheDirectory(); if (parsedArgs.command === 'cache clean') { - await rm(CACHE_DIR, { recursive: true, force: true }); + await cleanInstallerCache({ + cacheDirectory: CACHE_DIR, + namespace: FOUNDRYUP_CACHE_NAMESPACE, + }); say('done!'); exit(0); } @@ -207,7 +198,7 @@ export async function downloadAndInstallFoundryBinaries(): Promise { const cacheKey = createHash('sha256') .update(`${BIN_ARCHIVE_URL}-${bins}`) .digest('hex'); - const cachePath = join(CACHE_DIR, cacheKey); + const cachePath = join(CACHE_DIR, FOUNDRYUP_CACHE_NAMESPACE, cacheKey); const downloadedBinaries = await checkAndDownloadBinaries( url, diff --git a/packages/foundryup/tsconfig.build.json b/packages/foundryup/tsconfig.build.json index 66e72c5769..9b7d28868f 100644 --- a/packages/foundryup/tsconfig.build.json +++ b/packages/foundryup/tsconfig.build.json @@ -6,6 +6,6 @@ "outDir": "./dist", "rootDir": "./src" }, - "references": [], + "references": [{ "path": "../local-node-utils/tsconfig.build.json" }], "include": ["../../types", "./types", "./src"] } diff --git a/packages/foundryup/tsconfig.json b/packages/foundryup/tsconfig.json index 4ebb84c6cc..22eb20cc98 100644 --- a/packages/foundryup/tsconfig.json +++ b/packages/foundryup/tsconfig.json @@ -4,6 +4,6 @@ "baseUrl": "./", "lib": ["ES2021", "DOM"] }, - "references": [], + "references": [{ "path": "../local-node-utils/tsconfig.json" }], "include": ["../../types", "./types", "./src"] } diff --git a/yarn.lock b/yarn.lock index a1548e9386..f1262c02fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6953,6 +6953,7 @@ __metadata: resolution: "@metamask/foundryup@workspace:packages/foundryup" dependencies: "@metamask/auto-changelog": "npm:^6.1.0" + "@metamask/local-node-utils": "workspace:^" "@ts-bridge/cli": "npm:^0.6.4" "@types/jest": "npm:^29.5.14" "@types/unzipper": "npm:^0.10.10" @@ -6968,7 +6969,6 @@ __metadata: typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.3.3" unzipper: "npm:^0.12.3" - yaml: "npm:^2.3.4" yargs: "npm:^17.7.2" yargs-parser: "npm:^21.1.1" bin: @@ -7280,7 +7280,7 @@ __metadata: languageName: node linkType: hard -"@metamask/local-node-utils@workspace:packages/local-node-utils": +"@metamask/local-node-utils@workspace:^, @metamask/local-node-utils@workspace:packages/local-node-utils": version: 0.0.0-use.local resolution: "@metamask/local-node-utils@workspace:packages/local-node-utils" dependencies: