From 02928e10cb2b0cdcbf43354a3e98b7cb437d2207 Mon Sep 17 00:00:00 2001
From: Shaun Hamilton
Date: Wed, 19 Nov 2025 21:55:54 +0200
Subject: [PATCH] fix(client): fetch non-prod exam links on deploymentEnv
(#63978)
---
.../Challenges/exam-download/show.tsx | 83 ++++++++++++++++---
1 file changed, 71 insertions(+), 12 deletions(-)
diff --git a/client/src/templates/Challenges/exam-download/show.tsx b/client/src/templates/Challenges/exam-download/show.tsx
index 50fc9d64797..6ae9b4a7302 100644
--- a/client/src/templates/Challenges/exam-download/show.tsx
+++ b/client/src/templates/Challenges/exam-download/show.tsx
@@ -32,16 +32,22 @@ import {
import { examAttempts } from '../../../utils/ajax';
import MissingPrerequisites from '../exam/components/missing-prerequisites';
import { isChallengeCompletedSelector } from '../redux/selectors';
+import envData from '../../../../config/env.json';
import { Attempts } from './attempts';
import ExamTokenControls from './exam-token-controls';
import './show.css';
+const { deploymentEnv } = envData;
+
interface GitProps {
tag_name: string;
assets: {
browser_download_url: string;
}[];
+ name: string;
+ draft: boolean;
+ prerelease: boolean;
}
const mapStateToProps = createSelector(
@@ -154,23 +160,53 @@ function ShowExamDownload({
useEffect(() => {
async function checkLatestVersion() {
try {
- const response = await fetch(
- 'https://api.github.com/repos/freeCodeCamp/exam-env/releases/latest'
- );
- if (response.ok) {
+ let latest: GitProps;
+
+ if (deploymentEnv !== 'production') {
+ const response = await fetch(
+ 'https://api.github.com/repos/freeCodeCamp/exam-env/releases'
+ );
+
+ if (!response.ok) {
+ setLatestVersion(null);
+ return;
+ }
+
+ const data = (await response.json()) as GitProps[];
+ if (!data || data.length === 0) {
+ setLatestVersion(null);
+ return;
+ }
+ latest = getLatest(data);
+ } else {
+ const response = await fetch(
+ 'https://api.github.com/repos/freeCodeCamp/exam-env/releases/latest'
+ );
+ if (!response.ok) {
+ setLatestVersion(null);
+ return;
+ }
const data = (await response.json()) as GitProps;
- const { tag_name, assets } = data;
- setLatestVersion(tag_name);
- const urls = assets.map(link => link.browser_download_url);
- setDownloadLink(handleDownloadLink(urls));
- setDownloadLinks(urls);
+ if (!data) {
+ setLatestVersion(null);
+ return;
+ }
+ latest = data;
}
+
+ const { tag_name, assets } = latest;
+ setLatestVersion(tag_name);
+ const urls = assets.map(link => link.browser_download_url);
+ setDownloadLink(handleDownloadLink(urls));
+ setDownloadLinks(urls);
} catch {
- setLatestVersion('...');
+ setLatestVersion(null);
}
}
- void checkLatestVersion();
+ if (os.os) {
+ void checkLatestVersion();
+ }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [os]);
@@ -235,7 +271,6 @@ function ShowExamDownload({
version: latestVersion || '...'
})}
- {/* TODO: confirm this works on MacOS */}
@@ -283,6 +318,30 @@ function ShowExamDownload({
);
}
+function getLatest(releases: GitProps[]): GitProps {
+ switch (deploymentEnv) {
+ case 'staging':
+ return (
+ releases.find(r => {
+ return !r.draft && r.name.endsWith('/staging');
+ }) || releases[0]
+ );
+ // Currently, this is never the case
+ case 'development':
+ return (
+ releases.find(r => {
+ return !r.draft && r.name.endsWith('/development');
+ }) || releases[0]
+ );
+ default:
+ return (
+ releases.find(r => {
+ return !r.prerelease && !r.draft && r.name.endsWith('/production');
+ }) || releases[0]
+ );
+ }
+}
+
export default connect(mapStateToProps)(withTranslation()(ShowExamDownload));
// GraphQL