Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/foundryup/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/foundryup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
58 changes: 41 additions & 17 deletions packages/foundryup/src/foundryup.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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'),
}));

Expand Down Expand Up @@ -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;
}
Expand All @@ -148,17 +157,20 @@ 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({
operation: 'installBinaries',
details: {
binaries,
binDir: 'node_modules/.bin',
cachePath: CACHE_DIR,
cachePath,
},
});

Expand All @@ -167,20 +179,29 @@ const mockDownloadAndInstallFoundryBinaries = async (): Promise<

describe('foundryup', () => {
describe('getCacheDirectory', () => {
const originalCwd = process.cwd;

afterEach(() => {
process.chdir(originalCwd());
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test teardown saves cwd function

Low Severity

The getCacheDirectory suite stores process.cwd (the function) instead of calling it once to capture the starting directory. Each afterEach then invokes that function and chdirs to whatever directory is current—usually the temp dir from the test—so the process working directory is not restored for later tests.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a77d669. Configure here.


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');
});
});

Expand Down Expand Up @@ -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();

Expand All @@ -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 () => {
Expand Down
43 changes: 17 additions & 26 deletions packages/foundryup/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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.
Expand All @@ -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,
});
}

/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -176,7 +164,10 @@ export async function downloadAndInstallFoundryBinaries(): Promise<void> {
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);
}
Expand Down Expand Up @@ -207,7 +198,7 @@ export async function downloadAndInstallFoundryBinaries(): Promise<void> {
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,
Expand Down
2 changes: 1 addition & 1 deletion packages/foundryup/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"outDir": "./dist",
"rootDir": "./src"
},
"references": [],
"references": [{ "path": "../local-node-utils/tsconfig.build.json" }],
"include": ["../../types", "./types", "./src"]
}
2 changes: 1 addition & 1 deletion packages/foundryup/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"baseUrl": "./",
"lib": ["ES2021", "DOM"]
},
"references": [],
"references": [{ "path": "../local-node-utils/tsconfig.json" }],
"include": ["../../types", "./types", "./src"]
}
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Loading