Skip to content
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

feat: cache latest fuels version #3186

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/kind-chairs-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels": patch
---

feat: cache latest `fuels` version
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as versionsMod from '@fuel-ts/versions';

import * as checkForAndDisplayUpdatesMod from './checkForAndDisplayUpdates';
import * as getLatestFuelsVersionMod from './getLatestFuelsVersion';
import * as loggerMod from './logger';

/**
Expand All @@ -17,7 +18,7 @@

const mockDeps = (params: { latestVersion: string; userVersion: string }) => {
const { latestVersion, userVersion } = params;
vi.spyOn(Promise, 'race').mockReturnValue(Promise.resolve(latestVersion));
vi.spyOn(getLatestFuelsVersionMod, 'getLatestFuelsVersion').mockResolvedValue(latestVersion);

vi.spyOn(versionsMod, 'versions', 'get').mockReturnValue({
FUELS: userVersion,
Expand All @@ -37,7 +38,7 @@
);
const log = vi.spyOn(loggerMod, 'log');
await expect(checkForAndDisplayUpdatesMod.checkForAndDisplayUpdates()).resolves.not.toThrow();
expect(log).toHaveBeenCalledWith('\n Unable to fetch latest fuels version. Skipping...\n');

Check failure on line 41 in packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts

View workflow job for this annotation

GitHub Actions / node@18

packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts > checkForAndDisplayUpdates > should fail gracefully if the fetch fails

AssertionError: expected "log" to be called with arguments: [ Array(1) ] Received: 1st log call: Array [ " - Unable to fetch latest fuels version. Skipping... + ✅ Your fuels version is up to date: 0.94.6 ", ] Number of calls: 1 ❯ packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts:41:17

Check failure on line 41 in packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts

View workflow job for this annotation

GitHub Actions / node@20

packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts > checkForAndDisplayUpdates > should fail gracefully if the fetch fails

AssertionError: expected "log" to be called with arguments: [ Array(1) ] Received: 1st log call: Array [ " - Unable to fetch latest fuels version. Skipping... + ✅ Your fuels version is up to date: 0.94.6 ", ] Number of calls: 1 ❯ packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts:41:17

Check failure on line 41 in packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts

View workflow job for this annotation

GitHub Actions / node@22

packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts > checkForAndDisplayUpdates > should fail gracefully if the fetch fails

AssertionError: expected "log" to be called with arguments: [ Array(1) ] Received: 1st log call: Array [ " - Unable to fetch latest fuels version. Skipping... + ✅ Your fuels version is up to date: 0.94.6 ", ] Number of calls: 1 ❯ packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts:41:17
});

test('should log a warning if the version is outdated', async () => {
Expand All @@ -63,6 +64,6 @@
);
const log = vi.spyOn(loggerMod, 'log');
await expect(checkForAndDisplayUpdatesMod.checkForAndDisplayUpdates()).resolves.not.toThrow();
expect(log).toHaveBeenCalledWith('\n Unable to fetch latest fuels version. Skipping...\n');

Check failure on line 67 in packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts

View workflow job for this annotation

GitHub Actions / node@18

packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts > checkForAndDisplayUpdates > should handle fetch timing out

AssertionError: expected "log" to be called with arguments: [ Array(1) ] Received: 1st log call: Array [ " - Unable to fetch latest fuels version. Skipping... + ✅ Your fuels version is up to date: 0.94.6 ", ] Number of calls: 1 ❯ packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts:67:17

Check failure on line 67 in packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts

View workflow job for this annotation

GitHub Actions / node@20

packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts > checkForAndDisplayUpdates > should handle fetch timing out

AssertionError: expected "log" to be called with arguments: [ Array(1) ] Received: 1st log call: Array [ " - Unable to fetch latest fuels version. Skipping... + ✅ Your fuels version is up to date: 0.94.6 ", ] Number of calls: 1 ❯ packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts:67:17

Check failure on line 67 in packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts

View workflow job for this annotation

GitHub Actions / node@22

packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts > checkForAndDisplayUpdates > should handle fetch timing out

AssertionError: expected "log" to be called with arguments: [ Array(1) ] Received: 1st log call: Array [ " - Unable to fetch latest fuels version. Skipping... + ✅ Your fuels version is up to date: 0.94.6 ", ] Number of calls: 1 ❯ packages/fuels/src/cli/utils/checkForAndDisplayUpdates.test.ts:67:17
});
});
14 changes: 2 additions & 12 deletions packages/fuels/src/cli/utils/checkForAndDisplayUpdates.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { versions, gt, eq } from '@fuel-ts/versions';

import { getLatestFuelsVersion } from './getLatestFuelsVersion';
import { warn, log } from './logger';

export const getLatestFuelsVersion = async () => {
const response = await fetch('https://registry.npmjs.org/fuels/latest');
const data = await response.json();
return data.version as string;
};

export const checkForAndDisplayUpdates = async () => {
try {
const { FUELS: userFuelsVersion } = versions;

const latestFuelsVersion = await Promise.race<string | undefined>([
new Promise((resolve) => {
setTimeout(resolve, 3000);
}),
getLatestFuelsVersion(),
]);
const latestFuelsVersion = await getLatestFuelsVersion();

if (!latestFuelsVersion) {
log(`\n Unable to fetch latest fuels version. Skipping...\n`);
Expand Down
23 changes: 23 additions & 0 deletions packages/fuels/src/cli/utils/fuelsVersionCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from 'fs';
import path from 'path';

export const FUELS_VERSION_CACHE_FILE = path.join(__dirname, 'FUELS_VERSION');

export type FuelsVersionCache = string;

export const saveToCache = (cache: FuelsVersionCache) => {
fs.writeFileSync(FUELS_VERSION_CACHE_FILE, cache, 'utf-8');
};

export const FUELS_VERSION_CACHE_TTL = 6 * 60 * 60 * 1000; // 6 hours in milliseconds

export const checkAndLoadCache = (): FuelsVersionCache | null => {
if (fs.existsSync(FUELS_VERSION_CACHE_FILE)) {
const cachedVersion = fs.readFileSync(FUELS_VERSION_CACHE_FILE, 'utf-8');
const { mtimeMs: cacheTimestamp } = fs.statSync(FUELS_VERSION_CACHE_FILE);
if (cachedVersion && Date.now() - cacheTimestamp < FUELS_VERSION_CACHE_TTL) {
return cachedVersion;
}
}
return null;
};
42 changes: 42 additions & 0 deletions packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as cacheMod from './fuelsVersionCache';
import { getLatestFuelsVersion } from './getLatestFuelsVersion';

/**
* @group node
*/
describe('getLatestFuelsVersion', () => {
it('should fail if fetch fails', async () => {
vi.spyOn(global, 'fetch').mockImplementation(() =>
Promise.reject(new Error('Failed to fetch'))
);
await expect(getLatestFuelsVersion()).rejects.toThrowError('Failed to fetch');

Check failure on line 12 in packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts

View workflow job for this annotation

GitHub Actions / node@18

packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts > getLatestFuelsVersion > should fail if fetch fails

AssertionError: promise resolved "'0.94.6'" instead of rejecting - Expected: [Error: rejected promise] + Received: "0.94.6" ❯ packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts:12:41

Check failure on line 12 in packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts

View workflow job for this annotation

GitHub Actions / node@20

packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts > getLatestFuelsVersion > should fail if fetch fails

AssertionError: promise resolved "'0.94.6'" instead of rejecting - Expected: [Error: rejected promise] + Received: "0.94.6" ❯ packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts:12:41

Check failure on line 12 in packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts

View workflow job for this annotation

GitHub Actions / node@22

packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts > getLatestFuelsVersion > should fail if fetch fails

AssertionError: promise resolved "'0.94.6'" instead of rejecting - Expected: [Error: rejected promise] + Received: "0.94.6" ❯ packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts:12:41
});

it('should throw if fetch times out', async () => {
vi.spyOn(global, 'fetch').mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(resolve, 5000);
})
);
await expect(getLatestFuelsVersion()).rejects.toThrow();

Check failure on line 22 in packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts

View workflow job for this annotation

GitHub Actions / node@18

packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts > getLatestFuelsVersion > should throw if fetch times out

AssertionError: promise resolved "'0.94.6'" instead of rejecting - Expected: [Error: rejected promise] + Received: "0.94.6" ❯ packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts:22:41

Check failure on line 22 in packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts

View workflow job for this annotation

GitHub Actions / node@20

packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts > getLatestFuelsVersion > should throw if fetch times out

AssertionError: promise resolved "'0.94.6'" instead of rejecting - Expected: [Error: rejected promise] + Received: "0.94.6" ❯ packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts:22:41

Check failure on line 22 in packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts

View workflow job for this annotation

GitHub Actions / node@22

packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts > getLatestFuelsVersion > should throw if fetch times out

AssertionError: promise resolved "'0.94.6'" instead of rejecting - Expected: [Error: rejected promise] + Received: "0.94.6" ❯ packages/fuels/src/cli/utils/getLatestFuelsVersion.test.ts:22:41
});

it('should return cached version if it exists', async () => {
const cachedVersion = '1.0.0';
vi.spyOn(cacheMod, 'checkAndLoadCache').mockReturnValue(cachedVersion);
const result = await getLatestFuelsVersion();
expect(result).toEqual('1.0.0');
});

it('should fetch if there is no cache or the cache is expired', async () => {
const mockResponse = new Response(JSON.stringify({ version: '1.0.0' }));
const fetchSpy = vi.spyOn(global, 'fetch').mockReturnValue(Promise.resolve(mockResponse));
const saveCacheSpy = vi.spyOn(cacheMod, 'saveToCache').mockImplementation(() => {});
vi.spyOn(cacheMod, 'checkAndLoadCache').mockReturnValue(null);
const version = await getLatestFuelsVersion();
expect(fetchSpy).toHaveBeenCalled();
expect(version).toEqual('1.0.0');
expect(saveCacheSpy).toHaveBeenCalledWith('1.0.0');
});
});
25 changes: 25 additions & 0 deletions packages/fuels/src/cli/utils/getLatestFuelsVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { checkAndLoadCache, saveToCache } from './fuelsVersionCache';

export const getLatestFuelsVersion = async (): Promise<string | undefined> => {
const cachedVersion = checkAndLoadCache();
if (cachedVersion) {
return cachedVersion;
}

const data: { version: string } | null = await Promise.race([
new Promise((resolve) => {
setTimeout(() => resolve(null), 3000);
}),
arboleya marked this conversation as resolved.
Show resolved Hide resolved
fetch('https://registry.npmjs.org/fuels/latest').then((response) => response.json()),
]);

if (!data) {
throw new Error('Failed to fetch latest fuels version.');
}
arboleya marked this conversation as resolved.
Show resolved Hide resolved

const version = data.version as string;

saveToCache(version);

return version;
};
3 changes: 2 additions & 1 deletion packages/fuels/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { checkForAndDisplayUpdates } from './cli/utils/checkForAndDisplayUpdates
import { error } from './cli/utils/logger';

export const run = async (argv: string[]) => {
await checkForAndDisplayUpdates().catch(error);
const program = configureCli();
return Promise.all([await checkForAndDisplayUpdates().catch(error), program.parseAsync(argv)]);
return program.parseAsync(argv);
};
Loading