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
33 changes: 20 additions & 13 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { load } from 'js-yaml';
import { initializeUIFromConfig, setThemeToggle } from './src/ui/initUserInterface.js';
import { parseUrlParams, updateUrlParams, getCurrentState } from './src/ui/urlManager.js';
import { renderItemList } from './src/ui/render.js';
import { getPlatformApiUrls } from './src/utils/defineApiUrls.js';
import { getPlatformApiUrls } from './src/utils/definePlatformVals.js';
import { filterItems, sortItems } from './src/utils/filterAndSort.js';
import { fetchCodeRepos } from './src/api/fetchCodeRepos.js';
import { fetchHfRepos } from './src/api/fetchHfRepos.js';
Expand All @@ -28,7 +28,7 @@ const configPromise = fetch('config.yaml')
// Module-scope lets — assigned after config loads, used by all functions below
let CONFIG;
let ORGANIZATION_NAME, HF_ORGANIZATION_NAME, CATALOG_REPO_NAME, PLATFORM, API_BASE_URL, REFRESH_INTERVAL_DAYS, ADDITIONAL_REPOS, ADDITIONAL_HF_REPOS;
let ORG_API_URL, REPO_API_URL;
let ORG_API_URL, REPO_API_URL, RELEASE_SUFFIX;

let releasesMap = {};

Expand Down Expand Up @@ -206,16 +206,23 @@ document.addEventListener('DOMContentLoaded', async () => {
}

// Assign module-scope variables used by all functions
ORGANIZATION_NAME = CONFIG.ORGANIZATION_NAME;
HF_ORGANIZATION_NAME = CONFIG.HF_ORGANIZATION_NAME;
CATALOG_REPO_NAME = CONFIG.CATALOG_REPO_NAME;
PLATFORM = CONFIG.PLATFORM;
ORG_API_URL = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME).org;
REPO_API_URL = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME).repo;
API_BASE_URL = CONFIG.API_BASE_URL;
REFRESH_INTERVAL_DAYS = CONFIG.REFRESH_INTERVAL_DAYS;
ADDITIONAL_REPOS = CONFIG.ADDITIONAL_REPOS;
ADDITIONAL_HF_REPOS = CONFIG.ADDITIONAL_HF_REPOS;
// Destructure CONFIG into individual variables for easier access
({
ORGANIZATION_NAME,
HF_ORGANIZATION_NAME,
CATALOG_REPO_NAME,
PLATFORM,
API_BASE_URL,
REFRESH_INTERVAL_DAYS,
ADDITIONAL_REPOS,
ADDITIONAL_HF_REPOS
} = CONFIG);
// Destructure platform-specific API URLs from getPlatformApiUrls
({
org: ORG_API_URL,
repo: REPO_API_URL,
releaseSuffix: RELEASE_SUFFIX
} = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME));
Comment thread
egrace479 marked this conversation as resolved.

// Guard: if ORGANIZATION_NAME or HF_ORGANIZATION_NAME is missing (e.g. config.yaml failed to load),
// stop here — proceeding would fire requests like ?author=&full=true which
Expand Down Expand Up @@ -289,7 +296,7 @@ document.addEventListener('DOMContentLoaded', async () => {
});

// Initialize the Catalog Badge (Stars/Forks/Version)
fetchCatalogStats(REPO_API_URL, ORGANIZATION_NAME, CATALOG_REPO_NAME)
fetchCatalogStats(REPO_API_URL, ORGANIZATION_NAME, CATALOG_REPO_NAME, RELEASE_SUFFIX)

// Load pre-built release data (written by scripts/fetch-releases.js at build time)
releasesMap = await fetch('./releases.json')
Expand Down
7 changes: 4 additions & 3 deletions scripts/export-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import path from 'path';
import { fileURLToPath } from 'url';
import { load } from 'js-yaml';
import { validateConfig } from '../src/validateConfig.js';
import { getPlatformApiUrls } from '../src/utils/defineApiUrls.js';
import { getPlatformDisplay } from '../src/utils/defineRibbonVals.js';
import { filterNewAdditionalEntries } from '../src/utils/filterNewAdditionalEntries.js';
import { getPlatformVals, getPlatformApiUrls } from '../src/utils/definePlatformVals.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

Expand Down Expand Up @@ -60,6 +60,7 @@ const get = async (url) => {
// ---------------------------------------------------------------------------
const allTags = new Set();
const { org: ORG_API_URL, repo: REPO_API_URL } = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME);
const { profileRepo, fullNameKey, forkKey } = getPlatformVals(PLATFORM);

const collectCodePlatformTags = async () => {
const platformDisplay = getPlatformDisplay(PLATFORM);
Expand All @@ -85,8 +86,8 @@ const collectCodePlatformTags = async () => {
);
const additionalRepos = additionalData.filter(Boolean);

const additionalNames = new Set(additionalRepos.map(r => r.full_name));
const orgNonForks = allRepos.filter(r => r.name !== '.github' && !r.fork && !additionalNames.has(r.full_name));
const additionalNames = new Set(additionalRepos.map(r => r[fullNameKey]));
const orgNonForks = allRepos.filter(r => r.name !== profileRepo && !r[forkKey] && !additionalNames.has(r[fullNameKey]));

[...additionalRepos, ...orgNonForks].forEach(repo => {
(repo.topics || []).forEach(t => allTags.add(t.toLowerCase()));
Expand Down
17 changes: 9 additions & 8 deletions scripts/fetch-releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { load } from 'js-yaml';
import { validateConfig } from '../src/validateConfig.js';
import { getPlatformApiUrls } from '../src/utils/defineApiUrls.js';
import { getPlatformVals, getPlatformApiUrls } from '../src/utils/definePlatformVals.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -28,7 +28,8 @@ const headers = TOKEN
const TWO_WEEKS_MS = 14 * 24 * 60 * 60 * 1000;

// Step 1: Fetch all public org repos (paginated, same logic as main.js)
const { org: ORG_API_URL, repo: REPO_API_URL } = getPlatformApiUrls(CONFIG.PLATFORM, CONFIG.ORGANIZATION_NAME);
const { org: ORG_API_URL, repo: REPO_API_URL, releaseSuffix: RELEASE_SUFFIX } = getPlatformApiUrls(CONFIG.PLATFORM, CONFIG.ORGANIZATION_NAME);
const { profileRepo, fullNameKey, forkKey, urlKey, releasePublishedAtKey } = getPlatformVals(CONFIG.PLATFORM);
let allOrgRepos = [];
let nextUrl = `${ORG_API_URL}`;
while (nextUrl) {
Expand All @@ -47,24 +48,24 @@ while (nextUrl) {
// Step 2: Collect all code repo IDs (mirrors main.js deduplication logic)
const additionalRepoIds = CONFIG.ADDITIONAL_REPOS || [];
const additionalRepoSet = new Set(additionalRepoIds);
const orgNonForks = allOrgRepos.filter(r => r.name !== '.github' && !r.fork && !additionalRepoSet.has(r.full_name));
const orgNonForks = allOrgRepos.filter(r => r.name !== profileRepo && !r[forkKey] && !additionalRepoSet.has(r[fullNameKey]));
const repoIds = [
...additionalRepoIds,
...orgNonForks.map(r => r.full_name),
...orgNonForks.map(r => r[fullNameKey]),
];

// Step 3: Fetch latest release for each repo
const releases = {};
for (const id of repoIds) {
try {
const res = await fetch(`${REPO_API_URL}${id}/releases/latest`, { headers });
const res = await fetch(`${REPO_API_URL}${id}/${RELEASE_SUFFIX}`, { headers });
if (!res.ok) { releases[id] = null; continue; }
const data = await res.json();
releases[id] = {
tag: data.tag_name,
url: data.html_url,
publishedAt: data.published_at,
isNew: (Date.now() - new Date(data.published_at)) < TWO_WEEKS_MS,
url: data[urlKey],
publishedAt: data[releasePublishedAtKey],
isNew: (Date.now() - new Date(data[releasePublishedAtKey])) < TWO_WEEKS_MS,
};
} catch {
releases[id] = null;
Expand Down
40 changes: 11 additions & 29 deletions src/api/fetchCodeRepos.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import { handleError } from '../ui/render.js';
import { normalizeTag, filterDisplayTags } from '../utils/normalizeTag.js';
import { getPlatformVals } from '../utils/definePlatformVals.js';
import { getPlatformDisplay } from '../utils/defineRibbonVals.js';

// Define platform-specific values
const PLATFORM_CONFIGS = {
github: {
starsKey: 'stargazers_count',
profileRepo: '.github'
},
/* gitlab: {
starsKey: 'star_count',
profileRepo: 'gitlab-profile',
//TODO
}, */
codeberg: {
starsKey: 'stars_count',
profileRepo: '.profile'
}
};
import { normalizeTag, filterDisplayTags } from '../utils/normalizeTag.js';

/**
* Function for fetching code repositories from the specified platform (GitHub, GitLab, or Codeberg).
Expand Down Expand Up @@ -46,8 +30,7 @@ export async function fetchCodeRepos(
let allRepos = [];
let nextUrl = `${orgApiUrl}`;
// get platform-specific keys
const platformConfig = PLATFORM_CONFIGS[platform.toLowerCase()];
const { starsKey, profileRepo } = platformConfig;
const { starsKey, profileRepo, fullNameKey, forkKey, urlKey } = getPlatformVals(platform);
try {
Comment thread
egrace479 marked this conversation as resolved.
while (nextUrl) {
const ghResponse = await fetch(nextUrl);
Expand All @@ -68,7 +51,7 @@ export async function fetchCodeRepos(

// For org-owned entries in additionalRepos, reuse data already in allRepos to avoid redundant API calls.
// Only fetch entries that belong to a different org (external repos).
const allReposByFullName = new Map(allRepos.map(r => [r.full_name, r]));
const allReposByFullName = new Map(allRepos.map(r => [r[fullNameKey], r]));
const toFetch = additionalRepos.filter(ownerRepo => !allReposByFullName.has(ownerRepo));
const fromAllRepos = additionalRepos.map(ownerRepo => allReposByFullName.get(ownerRepo)).filter(Boolean);

Expand All @@ -91,11 +74,11 @@ export async function fetchCodeRepos(
const filteredAdditionalRepos = [...fromAllRepos, ...fetchedExternalData.filter(Boolean)];

// Keep only non-forks from org; deduplicate against additional repos by full_name
const orgRepoNames = new Set(filteredAdditionalRepos.map(r => r.full_name));
const orgRepoNames = new Set(filteredAdditionalRepos.map(r => r[fullNameKey]));
const orgNonForks = allRepos.filter(repo =>
repo.name !== profileRepo &&
!repo.fork &&
!orgRepoNames.has(repo.full_name));
!repo[forkKey] &&
!orgRepoNames.has(repo[fullNameKey]));

// Process additional repos and all remaining org non-forks to include metadata and 'new' flag as appropriate
let processedItems = [...filteredAdditionalRepos, ...orgNonForks]
Expand All @@ -108,10 +91,10 @@ export async function fetchCodeRepos(
const tags = [...new Set(rawTags.flatMap(t => normalizeTag(t)).filter(Boolean))];
const displayTags = filterDisplayTags(rawTags);

const release = releasesMap[repo.full_name] ?? null;
const release = releasesMap[repo[fullNameKey]] ?? null;

return {
id: repo.full_name, // "Imageomics/<repo-name>", used as backup if can't get repo.name
id: repo[fullNameKey], // "Imageomics/<repo-name>", used as backup if can't get repo.name
repoType: "code",
createdAt,
lastModified,
Expand All @@ -120,14 +103,13 @@ export async function fetchCodeRepos(
tags,
rawTags,
displayTags,
description: repo.description || "No description provided.",
html_url: repo.html_url,
description: repo.description, // fallback in display (render.js)
html_url: repo[urlKey],
hasNewRelease: release?.isNew ?? false,
latestReleaseUrl: release?.url ?? null,
latestReleaseTag: release?.tag ?? null,
cardData: {
pretty_name: repo.name, // <repo-name>, the one used for card title display
description: repo.description,
stars: repo[starsKey] ?? 0
}
};
Expand Down
8 changes: 5 additions & 3 deletions src/api/fetchStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
* @param {string} repoApiUrl - The base API URL for the code platform (e.g., GitHub API URL)
* @param {string} organizationName - The organization name for the catalog repository
* @param {string} catalogRepoName - The repository name for the catalog itself
* @param {string} releaseSuffix - The suffix used for fetching the latest release information (e.g., 'releases/latest')
* @returns {Promise<void>} - A promise that resolves when the stats have been fetched and displayed
*/
export const fetchCatalogStats = async (repoApiUrl, organizationName, catalogRepoName) => {
export const fetchCatalogStats = async (repoApiUrl, organizationName, catalogRepoName, releaseSuffix) => {
// Helper: Updates text, shows the specific stat, and ensures the divider is visible
const update = (textId, containerId, value) => {
const el = document.getElementById(textId);
Expand All @@ -21,15 +22,16 @@ export const fetchCatalogStats = async (repoApiUrl, organizationName, catalogRep
};

try {
//TODO: Update stars and forks to support other platforms (GitLab, Codeberg) once implemented
//TODO: Update stars and forks to support other platforms: Add another || star for GitLab, platform isn't passed
// forks_count is shared
// 1. Get Stars & Forks
const repo = await fetch(`${repoApiUrl}${organizationName}/${catalogRepoName}`).then(r => r.ok ? r.json() : {});
if (repo.stargazers_count !== undefined || repo.stars_count !== undefined) update('gh-stars', 'gh-star-container', repo.stargazers_count || repo.stars_count);
if (repo.forks_count !== undefined) update('gh-forks', 'gh-fork-container', repo.forks_count);

// 2. Get Version (Tag)
// TODO: Import from package.json
const release = await fetch(`${repoApiUrl}${organizationName}/${catalogRepoName}/releases/latest`).then(r => r.ok ? r.json() : {});
const release = await fetch(`${repoApiUrl}${organizationName}/${catalogRepoName}/${releaseSuffix}`).then(r => r.ok ? r.json() : {});
if (release.tag_name !== undefined) update('gh-tag', 'gh-version-container', release.tag_name);

} catch (e) {
Expand Down
34 changes: 0 additions & 34 deletions src/utils/defineApiUrls.js

This file was deleted.

78 changes: 78 additions & 0 deletions src/utils/definePlatformVals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Collection of functions to define platform-specific values for the catalog, such as API URLs, display values, and repo data keys.
* Each function returns dictionaries with the platform values that have been keyed by platform name (e.g., 'github', 'codeberg', 'gitlab') and potentially other instance-specific values (e.g., organizationName, repoApiUrl).
* Platform and organization name are defined from config.yaml and passed to the functions requiring them.
*/


/**
* Utility function to get platform-specific values for repo data keys (e.g., star count and profile repo name).
* @param {string} platform
* @returns {Object} platformVals - An object containing platform-specific values for repo data keys (e.g., star count and profile repo name)
*/
export function getPlatformVals(platform) {
const platformVals = {
github: {
starsKey: 'stargazers_count',
profileRepo: '.github',
fullNameKey: 'full_name',
forkKey: 'fork', //forks_count is shared
urlKey: 'html_url',
releasePublishedAtKey: 'published_at'
},
/* gitlab: {
starsKey: 'star_count',
profileRepo: 'gitlab-profile',
fullNameKey: 'name_with_namespace',
forkKey: 'forked_from_project',
urlKey: 'web_url',
releasePublishedAtKey: 'released_at'
}, */
codeberg: {
starsKey: 'stars_count',
profileRepo: '.profile',
fullNameKey: 'full_name',
forkKey: 'fork',
urlKey: 'html_url',
releasePublishedAtKey: 'published_at'
}
};
return platformVals[platform.toLowerCase()];
}


/**
* Utility function to get the platform-specific API URLs for organization repos and individual repo details.
* Defines API URLs based on the selected platform (GitHub or Codeberg, note: GitLab support under development).
* This allows the rest of the codebase to use these constants when making API calls,
* abstracting away platform-specific URL structures.
*
* Usage: import { getPlatformApiUrls } from './definePlatformVals.js';
*
* Input: platform and organizationName (e.g., 'github' and 'imageomics'), defined from config.yaml and passed to this function.
* Output: platformApiUrls[platform] = { org: ORG_API_URL, repo: REPO_API_URL, releaseSuffix: RELEASE_SUFFIX }
*
* @param {string} platform - 'github' or 'codeberg', pending: 'gitlab'
* @param {string} organizationName - The name of the organization (used in URL construction)
* @returns {object} An object containing ORG_API_URL, REPO_API_URL, and RELEASE_SUFFIX
*/
export function getPlatformApiUrls(platform, organizationName) {
const platformApiUrls = {
github: {
org: `https://api.github.com/orgs/${organizationName}/repos?type=public&per_page=100`,
repo: "https://api.github.com/repos/",
releaseSuffix: "releases/latest"
},
// gitlab: {
// org: `https://gitlab.com/api/v4/groups/${organizationName}/projects?per_page=100`,
// repo: "https://gitlab.com/api/v4/projects/",
// releaseSuffix: "releases/permalink/latest"
// },
codeberg: {
org: `https://codeberg.org/api/v1/orgs/${organizationName}/repos?limit=50`,
repo: "https://codeberg.org/api/v1/repos/",
releaseSuffix: "releases/latest"
}
};
return platformApiUrls[platform.toLowerCase()];
}
Loading