From 2b9eab789a8bfc04fd601a153d4cef68b3b8ec2e Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 17:02:16 -0400 Subject: [PATCH 01/15] Allow for HF org with different name from codebase platform --- main.js | 11 ++++++----- public/config.yaml | 5 +++-- tests/validateConfig.test.js | 11 +++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/main.js b/main.js index bf6c297..0ecf421 100644 --- a/main.js +++ b/main.js @@ -24,7 +24,7 @@ const configPromise = fetch('config.yaml') // Module-scope lets — assigned after config loads, used by all functions below let CONFIG; -let ORGANIZATION_NAME, CATALOG_REPO_NAME, PLATFORM, API_BASE_URL, REFRESH_INTERVAL_DAYS, ADDITIONAL_REPOS, ADDITIONAL_HF_REPOS; +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; // Build a reverse lookup from TAG_GROUPS (defined in tag-groups.js): raw tag → [canonical tags] @@ -279,7 +279,7 @@ const fetchHubItems = async (repoType) => { } // hugging face api requests for datasets/models/spaces - const response = await fetch(`${API_BASE_URL}${repoType}?author=${ORGANIZATION_NAME}&full=true`); + const response = await fetch(`${API_BASE_URL}${repoType}?author=${HF_ORGANIZATION_NAME}&full=true`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -703,7 +703,7 @@ document.addEventListener('DOMContentLoaded', async () => { setTimeout(() => errorDiv.remove(), 10000); // Fall back to defaults so the page isn't completely broken CONFIG = { - ORGANIZATION_NAME: '', CATALOG_REPO_NAME: '', ORG_NAME: '', + ORGANIZATION_NAME: '', HF_ORGANIZATION_NAME: '', CATALOG_REPO_NAME: '', ORG_NAME: '', CATALOG_TITLE: 'Catalog', CATALOG_DESCRIPTION: '', LOGO_URL: '', FAVICON_URL: '', COLORS: { primary: '#92991c', secondary: '#5d8095', accent: '#0097b2', accentDark: '#4fd1eb', tag: '#9bcb5e' }, @@ -715,6 +715,7 @@ 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; @@ -808,10 +809,10 @@ document.addEventListener('DOMContentLoaded', async () => { }); // Initialize the Catalog Badge (Stars/Forks/Version) - // Guard: if ORGANIZATION_NAME is missing (e.g. config.yaml failed to load), + // 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 // could return unbounded results from the Hugging Face API. - if (!ORGANIZATION_NAME) return; + if (!ORGANIZATION_NAME || !HF_ORGANIZATION_NAME) return; fetchCatalogStats(); diff --git a/public/config.yaml b/public/config.yaml index 17ef860..b5d0b2b 100644 --- a/public/config.yaml +++ b/public/config.yaml @@ -2,8 +2,9 @@ # Customize these values to personalize the catalog for your organization # Organization & Repository Settings -ORGANIZATION_NAME: imageomics # GitHub/Hugging Face organization name (lowercase for API calls) -ORG_NAME: Imageomics # Display name for GitHub organization (can differ from API name) +ORGANIZATION_NAME: imageomics # Codebase platform organization name (lowercase for API calls) +HF_ORGANIZATION_NAME: imageomics # Hugging Face organization name (lowercase for API calls) +ORG_NAME: Imageomics # Display name for Codebase platform organization (can differ from API name) CATALOG_REPO_NAME: catalog # Repository name for the catalog itself (used for stats badge) # Branding diff --git a/tests/validateConfig.test.js b/tests/validateConfig.test.js index a9ee8c1..ba38031 100644 --- a/tests/validateConfig.test.js +++ b/tests/validateConfig.test.js @@ -3,6 +3,7 @@ import { validateConfig } from '../src/validateConfig.js'; const VALID_CONFIG = { ORGANIZATION_NAME: 'imageomics', + HF_ORGANIZATION_NAME: 'imageomics', ORG_NAME: 'Imageomics', CATALOG_REPO_NAME: 'catalog', PLATFORM: 'github', @@ -53,6 +54,15 @@ describe('validateConfig', () => { expect(validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: '' })).toContain('ORGANIZATION_NAME'); }); + it('errors when HF_ORGANIZATION_NAME is missing', () => { + const { HF_ORGANIZATION_NAME: _, ...config } = VALID_CONFIG; + expect(validateConfig(config)).toContain('HF_ORGANIZATION_NAME'); + }); + + it('errors when HF_ORGANIZATION_NAME is empty string', () => { + expect(validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: '' })).toContain('HF_ORGANIZATION_NAME'); + }); + it('errors when ORG_NAME is missing', () => { const { ORG_NAME: _, ...config } = VALID_CONFIG; expect(validateConfig(config)).toContain('ORG_NAME'); @@ -141,6 +151,7 @@ describe('validateConfig', () => { }; const errors = validateConfig(config); expect(errors).toContain('ORGANIZATION_NAME'); + expect(errors).toContain('HF_ORGANIZATION_NAME'); expect(errors).toContain('ORG_NAME'); expect(errors).toContain('CATALOG_REPO_NAME'); expect(errors).toContain('PLATFORM'); From 1427bce7b686ae05c17b97737e72f44b68bd0f9f Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 17:08:37 -0400 Subject: [PATCH 02/15] Describe new HF Org name option --- AGENTS.md | 3 ++- docs/personalization.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2c07b4e..1668ea2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,7 +100,8 @@ When adding a new utility to `src/`, add a corresponding test file. Do **not** a All runtime behavior is controlled by `public/config.yaml`. It is fetched at page load (not bundled), so changes take effect on the next page load without a rebuild. Key fields: -- `ORGANIZATION_NAME` — used in all API calls (must be lowercase) +- `ORGANIZATION_NAME` — used in all code repository platform API calls (must be lowercase) +- `HF_ORGANIZATION_NAME` - used in all Hugging Face API calls (must be lowercase) - `API_BASE_URL` — Hugging Face API base (default: `https://huggingface.co/api/`) - `PLATFORM` — code repository platform; only `github` is currently supported - `ADDITIONAL_REPOS` — forked or external GitHub repos to include diff --git a/docs/personalization.md b/docs/personalization.md index 3a6d161..3d8423c 100644 --- a/docs/personalization.md +++ b/docs/personalization.md @@ -8,7 +8,8 @@ Welcome to your new catalog repo! The primary way to personalize this catalog is ### Organization & Repository Settings - * `ORGANIZATION_NAME`: Your GitHub/Hugging Face organization name (lowercase for API calls) + * `ORGANIZATION_NAME`: Your code platform (e.g., GitHub) organization name (lowercase for API calls) + * `HF_ORGANIZATION_NAME`: Your Hugging Face organization name (lowercase for API calls) * `ORG_NAME`: Display name for your organization (can differ from API name); used as fallback site title if `CATALOG_TITLE` is not set * `CATALOG_REPO_NAME`: Repository name for the catalog itself (used for stats badge) From 6e73349b36c1943f3b447517f0e102f141cea224 Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 17:38:56 -0400 Subject: [PATCH 03/15] Add rendered error message in case of missing organization name variables --- main.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/main.js b/main.js index 0ecf421..655d88d 100644 --- a/main.js +++ b/main.js @@ -812,7 +812,22 @@ document.addEventListener('DOMContentLoaded', async () => { // 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 // could return unbounded results from the Hugging Face API. - if (!ORGANIZATION_NAME || !HF_ORGANIZATION_NAME) return; + if (!ORGANIZATION_NAME || !HF_ORGANIZATION_NAME){ + console.error("Organization name is missing for one or both APIs. Halting initialization."); + + // Hide any loading skeletons and instead show a clear error message in the main content area + document.querySelectorAll('.skeleton-card').forEach(s => s.classList.add('hidden')); + const itemList = document.getElementById('itemList'); + if (itemList) { + itemList.innerHTML = ` +
+

Critical Configuration Error

+

The application could not start because the organization name is missing for one or both APIs. Define these variables in public/config.yaml.

+
+ `; + } + return + }; fetchCatalogStats(); From 65b7c5776915bb0346c572484d1cc14006a1d9b0 Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 17:56:14 -0400 Subject: [PATCH 04/15] Formatting fixes requested by Copilot --- AGENTS.md | 2 +- main.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1668ea2..1cff2a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -101,7 +101,7 @@ When adding a new utility to `src/`, add a corresponding test file. Do **not** a All runtime behavior is controlled by `public/config.yaml`. It is fetched at page load (not bundled), so changes take effect on the next page load without a rebuild. Key fields: - `ORGANIZATION_NAME` — used in all code repository platform API calls (must be lowercase) -- `HF_ORGANIZATION_NAME` - used in all Hugging Face API calls (must be lowercase) +- `HF_ORGANIZATION_NAME` — used in all Hugging Face API calls (must be lowercase) - `API_BASE_URL` — Hugging Face API base (default: `https://huggingface.co/api/`) - `PLATFORM` — code repository platform; only `github` is currently supported - `ADDITIONAL_REPOS` — forked or external GitHub repos to include diff --git a/main.js b/main.js index 655d88d..28c3766 100644 --- a/main.js +++ b/main.js @@ -826,8 +826,8 @@ document.addEventListener('DOMContentLoaded', async () => { `; } - return - }; + return; + } fetchCatalogStats(); From cdb7cbc538f2f2fb67394eb5dde91092b9c87b2c Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 17:57:37 -0400 Subject: [PATCH 05/15] Use HF org name variable for HF tag export --- scripts/export-tags.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/export-tags.js b/scripts/export-tags.js index f3ec2ba..8e046ec 100644 --- a/scripts/export-tags.js +++ b/scripts/export-tags.js @@ -35,7 +35,7 @@ if (errors.length) { } const CONFIG = rawConfig; -const { ORGANIZATION_NAME, PLATFORM, API_BASE_URL, ADDITIONAL_REPOS } = CONFIG; +const { ORGANIZATION_NAME, HF_ORGANIZATION_NAME, PLATFORM, API_BASE_URL, ADDITIONAL_REPOS } = CONFIG; const ADDITIONAL_HF_REPOS = CONFIG.ADDITIONAL_HF_REPOS; // --------------------------------------------------------------------------- @@ -97,7 +97,7 @@ const collectCodePlatformTags = async () => { const collectHFTags = async (repoType) => { console.log(`Fetching HF ${repoType}...`); - let items = (await get(`${API_BASE_URL}${repoType}?author=${ORGANIZATION_NAME}&full=true`)).json; + let items = (await get(`${API_BASE_URL}${repoType}?author=${HF_ORGANIZATION_NAME}&full=true`)).json; // Fetch additional HF repos of this type const additionalForType = ADDITIONAL_HF_REPOS.filter(entry => entry.type === repoType); From 8289e8a7230ff94b21427588c5c4c06fa1d5181b Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 17:57:58 -0400 Subject: [PATCH 06/15] Add HF org name variable to validation --- src/validateConfig.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/validateConfig.js b/src/validateConfig.js index 7cac8f3..6787155 100644 --- a/src/validateConfig.js +++ b/src/validateConfig.js @@ -15,6 +15,7 @@ export function validateConfig(config) { } if (!config.ORGANIZATION_NAME) errors.push('ORGANIZATION_NAME'); + if (!config.HF_ORGANIZATION_NAME) errors.push('HF_ORGANIZATION_NAME'); if (!config.ORG_NAME) errors.push('ORG_NAME'); if (!config.CATALOG_REPO_NAME) errors.push('CATALOG_REPO_NAME'); if (!config.PLATFORM) errors.push('PLATFORM'); From 4a1a0ab6beabeffbe7d9ae6fa6294f82ad774e6f Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 19:17:14 -0400 Subject: [PATCH 07/15] Remove extra UI errors --- main.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/main.js b/main.js index 28c3766..5190a48 100644 --- a/main.js +++ b/main.js @@ -814,18 +814,6 @@ document.addEventListener('DOMContentLoaded', async () => { // could return unbounded results from the Hugging Face API. if (!ORGANIZATION_NAME || !HF_ORGANIZATION_NAME){ console.error("Organization name is missing for one or both APIs. Halting initialization."); - - // Hide any loading skeletons and instead show a clear error message in the main content area - document.querySelectorAll('.skeleton-card').forEach(s => s.classList.add('hidden')); - const itemList = document.getElementById('itemList'); - if (itemList) { - itemList.innerHTML = ` -
-

Critical Configuration Error

-

The application could not start because the organization name is missing for one or both APIs. Define these variables in public/config.yaml.

-
- `; - } return; } From 0d84a4606251862ed476f94eb8269b12470863a4 Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 19:19:19 -0400 Subject: [PATCH 08/15] Add config validation on dev runs and check organization name variables are lowercase avoid quiet fails --- package.json | 2 +- src/validateConfig.js | 17 +++++++++++++++-- tests/validateConfig.test.js | 8 ++++++++ vite.config.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b33ad85..e95c2fa 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Repository for web-based Imageomics code, data, model, and spaces catalog. This catalog is designed to use the GitHub API for searching all code repositories created under the [Imageomics GitHub Organization](https://github.com/Imageomics) and the Hugging Face API for searching all dataset, model, and spaces repositories created under the [Imageomics Hugging Face Organization](https://huggingface.co/imageomics).", "main": "main.js", "scripts": { - "dev": "vite", + "dev": "node src/validateConfig.js && vite", "build": "node scripts/fetch-releases.js && vite build", "preview": "vite preview", "test": "vitest run", diff --git a/src/validateConfig.js b/src/validateConfig.js index 6787155..4a22af5 100644 --- a/src/validateConfig.js +++ b/src/validateConfig.js @@ -14,8 +14,21 @@ export function validateConfig(config) { return errors; } - if (!config.ORGANIZATION_NAME) errors.push('ORGANIZATION_NAME'); - if (!config.HF_ORGANIZATION_NAME) errors.push('HF_ORGANIZATION_NAME'); + /** Validate organization names, existence and case */ + if (config.ORGANIZATION_NAME) { + if (config.ORGANIZATION_NAME !== config.ORGANIZATION_NAME.toLowerCase()) { + errors.push(`ORGANIZATION_NAME (${config.ORGANIZATION_NAME}) must be lowercase`); + } + } else { + errors.push('ORGANIZATION_NAME'); + } + if (config.HF_ORGANIZATION_NAME) { + if (config.HF_ORGANIZATION_NAME !== config.HF_ORGANIZATION_NAME.toLowerCase()) { + errors.push(`HF_ORGANIZATION_NAME (${config.HF_ORGANIZATION_NAME}) must be lowercase`); + } + } else { + errors.push('HF_ORGANIZATION_NAME'); + } if (!config.ORG_NAME) errors.push('ORG_NAME'); if (!config.CATALOG_REPO_NAME) errors.push('CATALOG_REPO_NAME'); if (!config.PLATFORM) errors.push('PLATFORM'); diff --git a/tests/validateConfig.test.js b/tests/validateConfig.test.js index ba38031..09effd4 100644 --- a/tests/validateConfig.test.js +++ b/tests/validateConfig.test.js @@ -54,6 +54,10 @@ describe('validateConfig', () => { expect(validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: '' })).toContain('ORGANIZATION_NAME'); }); + it('errors when ORGANIZATION_NAME is not lowercase', () => { + expect(validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: 'Imageomics' })).toContain('ORGANIZATION_NAME'); + }); + it('errors when HF_ORGANIZATION_NAME is missing', () => { const { HF_ORGANIZATION_NAME: _, ...config } = VALID_CONFIG; expect(validateConfig(config)).toContain('HF_ORGANIZATION_NAME'); @@ -63,6 +67,10 @@ describe('validateConfig', () => { expect(validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: '' })).toContain('HF_ORGANIZATION_NAME'); }); + it('errors when HF_ORGANIZATION_NAME is not lowercase', () => { + expect(validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: 'Imageomics' })).toContain('HF_ORGANIZATION_NAME'); + }); + it('errors when ORG_NAME is missing', () => { const { ORG_NAME: _, ...config } = VALID_CONFIG; expect(validateConfig(config)).toContain('ORG_NAME'); diff --git a/vite.config.js b/vite.config.js index c216b73..274248f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,5 +1,33 @@ import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' +import fs from 'fs'; +import jsYaml from 'js-yaml'; +import { validateConfig } from './src/validateConfig.js'; + +let CONFIG_PATH = './public/config.yaml'; +let parsedConfig; + +// Run config validation before starting the dev server +try { + const fileContents = fs.readFileSync(CONFIG_PATH, 'utf8'); + parsedConfig = jsYaml.load(fileContents); +} catch (error) { + console.error(`\n❌ [CONFIG ERROR] Failed to read or parse config.yaml: ${error.message}\n`); + process.exit(1); // Stop Vite immediately if the file is completely unreadable or broken YAML +} + +// Any errors will be collected in the 'errors' array, to be printed out and cause a safe crash if not empty. +const errors = validateConfig(parsedConfig); +if (errors && errors.length > 0) { + console.error('\n❌ [CONFIG ERROR] Vite failed to start due to the following configuration errors:\n'); + + errors.forEach((error, index) => { + console.error(` ${index + 1}. ${error}`); + }); + + console.error(`\n Aborting deployment. Please fix these issues in ${CONFIG_PATH} and try again.\n`); + process.exit(1); // Safely kills the npm run dev terminal process +} const repoName = process.env.GITHUB_REPOSITORY?.split('/')[1] From 093bd05690be1752195f64733a731878cad97cdc Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 19:32:28 -0400 Subject: [PATCH 09/15] Fix test expectations, error includes a helpful message --- tests/validateConfig.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/validateConfig.test.js b/tests/validateConfig.test.js index 09effd4..a303b62 100644 --- a/tests/validateConfig.test.js +++ b/tests/validateConfig.test.js @@ -55,7 +55,9 @@ describe('validateConfig', () => { }); it('errors when ORGANIZATION_NAME is not lowercase', () => { - expect(validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: 'Imageomics' })).toContain('ORGANIZATION_NAME'); + expect( + validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: 'Imageomics' }) + ).toContain('ORGANIZATION_NAME (Imageomics) must be lowercase'); }); it('errors when HF_ORGANIZATION_NAME is missing', () => { @@ -68,7 +70,9 @@ describe('validateConfig', () => { }); it('errors when HF_ORGANIZATION_NAME is not lowercase', () => { - expect(validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: 'Imageomics' })).toContain('HF_ORGANIZATION_NAME'); + expect( + validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: 'Imageomics' }) + ).toContain('HF_ORGANIZATION_NAME (Imageomics) must be lowercase'); }); it('errors when ORG_NAME is missing', () => { From 59553c4328328b1535b7478cd18e2ad11c2ab268 Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 19:54:19 -0400 Subject: [PATCH 10/15] improve error message handling and remove unneeded command --- package.json | 2 +- src/validateConfig.js | 18 ++++++++---------- vite.config.js | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index e95c2fa..b33ad85 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Repository for web-based Imageomics code, data, model, and spaces catalog. This catalog is designed to use the GitHub API for searching all code repositories created under the [Imageomics GitHub Organization](https://github.com/Imageomics) and the Hugging Face API for searching all dataset, model, and spaces repositories created under the [Imageomics Hugging Face Organization](https://huggingface.co/imageomics).", "main": "main.js", "scripts": { - "dev": "node src/validateConfig.js && vite", + "dev": "vite", "build": "node scripts/fetch-releases.js && vite build", "preview": "vite preview", "test": "vitest run", diff --git a/src/validateConfig.js b/src/validateConfig.js index 4a22af5..bbc9e6c 100644 --- a/src/validateConfig.js +++ b/src/validateConfig.js @@ -15,19 +15,17 @@ export function validateConfig(config) { } /** Validate organization names, existence and case */ - if (config.ORGANIZATION_NAME) { - if (config.ORGANIZATION_NAME !== config.ORGANIZATION_NAME.toLowerCase()) { - errors.push(`ORGANIZATION_NAME (${config.ORGANIZATION_NAME}) must be lowercase`); - } - } else { + const orgName = config.ORGANIZATION_NAME; + if (typeof orgName !== 'string' || !orgName.trim()) { errors.push('ORGANIZATION_NAME'); + } else if (orgName !== orgName.toLowerCase()) { + errors.push(`ORGANIZATION_NAME (${orgName}) must be lowercase`); } - if (config.HF_ORGANIZATION_NAME) { - if (config.HF_ORGANIZATION_NAME !== config.HF_ORGANIZATION_NAME.toLowerCase()) { - errors.push(`HF_ORGANIZATION_NAME (${config.HF_ORGANIZATION_NAME}) must be lowercase`); - } - } else { + const hfOrgName = config.HF_ORGANIZATION_NAME; + if (typeof hfOrgName !== 'string' || !hfOrgName.trim()) { errors.push('HF_ORGANIZATION_NAME'); + } else if (hfOrgName !== hfOrgName.toLowerCase()) { + errors.push(`HF_ORGANIZATION_NAME (${hfOrgName}) must be lowercase`); } if (!config.ORG_NAME) errors.push('ORG_NAME'); if (!config.CATALOG_REPO_NAME) errors.push('CATALOG_REPO_NAME'); diff --git a/vite.config.js b/vite.config.js index 274248f..2f71784 100644 --- a/vite.config.js +++ b/vite.config.js @@ -25,7 +25,7 @@ if (errors && errors.length > 0) { console.error(` ${index + 1}. ${error}`); }); - console.error(`\n Aborting deployment. Please fix these issues in ${CONFIG_PATH} and try again.\n`); + console.error(`\n Please fix the listed issue(s) in ${CONFIG_PATH} and try again.\n`); process.exit(1); // Safely kills the npm run dev terminal process } From 99fbc3dd54c70dfba2fa61f73cd9ee08192361c3 Mon Sep 17 00:00:00 2001 From: egrace479 Date: Thu, 11 Jun 2026 21:40:00 -0400 Subject: [PATCH 11/15] Clarify validation and use config path variable for loading error --- vite.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vite.config.js b/vite.config.js index 2f71784..aa370c8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,12 +7,12 @@ import { validateConfig } from './src/validateConfig.js'; let CONFIG_PATH = './public/config.yaml'; let parsedConfig; -// Run config validation before starting the dev server +// Run config validation before Vite starts (dev/build/preview) try { const fileContents = fs.readFileSync(CONFIG_PATH, 'utf8'); parsedConfig = jsYaml.load(fileContents); } catch (error) { - console.error(`\n❌ [CONFIG ERROR] Failed to read or parse config.yaml: ${error.message}\n`); + console.error(`\n❌ [CONFIG ERROR] Failed to read or parse ${CONFIG_PATH}: ${error.message}\n`); process.exit(1); // Stop Vite immediately if the file is completely unreadable or broken YAML } From df270ba04e3e2b29d4e3c9b2d7716fea406aae4d Mon Sep 17 00:00:00 2001 From: egrace479 Date: Mon, 15 Jun 2026 19:07:02 -0400 Subject: [PATCH 12/15] Fix: HF API is case-sensitive, fallback used where stated; test org name formats --- AGENTS.md | 4 ++-- docs/personalization.md | 6 +++--- main.js | 2 +- public/config.yaml | 5 +++-- src/validateConfig.js | 12 +++++++----- tests/validateConfig.test.js | 12 ++++++------ vite.config.js | 2 +- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1cff2a4..e601252 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,8 +100,8 @@ When adding a new utility to `src/`, add a corresponding test file. Do **not** a All runtime behavior is controlled by `public/config.yaml`. It is fetched at page load (not bundled), so changes take effect on the next page load without a rebuild. Key fields: -- `ORGANIZATION_NAME` — used in all code repository platform API calls (must be lowercase) -- `HF_ORGANIZATION_NAME` — used in all Hugging Face API calls (must be lowercase) +- `ORGANIZATION_NAME` — used in all code repository platform API calls (GitHub is not case-sensitive) +- `HF_ORGANIZATION_NAME` — used in all Hugging Face API calls (**case-sensitive**) - `API_BASE_URL` — Hugging Face API base (default: `https://huggingface.co/api/`) - `PLATFORM` — code repository platform; only `github` is currently supported - `ADDITIONAL_REPOS` — forked or external GitHub repos to include diff --git a/docs/personalization.md b/docs/personalization.md index 3d8423c..cc26212 100644 --- a/docs/personalization.md +++ b/docs/personalization.md @@ -8,9 +8,9 @@ Welcome to your new catalog repo! The primary way to personalize this catalog is ### Organization & Repository Settings - * `ORGANIZATION_NAME`: Your code platform (e.g., GitHub) organization name (lowercase for API calls) - * `HF_ORGANIZATION_NAME`: Your Hugging Face organization name (lowercase for API calls) - * `ORG_NAME`: Display name for your organization (can differ from API name); used as fallback site title if `CATALOG_TITLE` is not set + * `ORGANIZATION_NAME`: Your code platform (e.g., GitHub) organization name (for API calls) + * `HF_ORGANIZATION_NAME`: Your Hugging Face organization name (**case-sensitive**, for API calls) + * `ORG_NAME`: Display name for your organization (can differ from API name); used for logo alt-text and as fallback site title if `CATALOG_TITLE` is not set * `CATALOG_REPO_NAME`: Repository name for the catalog itself (used for stats badge) ### Branding diff --git a/main.js b/main.js index 5190a48..45f3bd4 100644 --- a/main.js +++ b/main.js @@ -651,7 +651,7 @@ const initializeUIFromConfig = () => { // Set header title and description const headerTitle = document.getElementById('header-title'); if (headerTitle) { - headerTitle.textContent = CONFIG.CATALOG_TITLE; + headerTitle.textContent = CONFIG.CATALOG_TITLE || `${CONFIG.ORG_NAME} Catalog`; headerTitle.style.color = CONFIG.COLORS.primary; } diff --git a/public/config.yaml b/public/config.yaml index b5d0b2b..06b960d 100644 --- a/public/config.yaml +++ b/public/config.yaml @@ -2,8 +2,8 @@ # Customize these values to personalize the catalog for your organization # Organization & Repository Settings -ORGANIZATION_NAME: imageomics # Codebase platform organization name (lowercase for API calls) -HF_ORGANIZATION_NAME: imageomics # Hugging Face organization name (lowercase for API calls) +ORGANIZATION_NAME: imageomics # Codebase platform organization name (for API calls) +HF_ORGANIZATION_NAME: imageomics # Hugging Face organization name (case-sensitive, for API calls) ORG_NAME: Imageomics # Display name for Codebase platform organization (can differ from API name) CATALOG_REPO_NAME: catalog # Repository name for the catalog itself (used for stats badge) @@ -44,6 +44,7 @@ ADDITIONAL_REPOS: # Array of Hugging Face repos from outside the org to include. # Each entry must specify "repo" (owner/name) and "type" (datasets, models, or spaces). +# Organization names are case-sensitive in the Hugging Face API. # ADDITIONAL_HF_REPOS: # - repo: "user/dataset-name" # type: "datasets" diff --git a/src/validateConfig.js b/src/validateConfig.js index bbc9e6c..8f70da1 100644 --- a/src/validateConfig.js +++ b/src/validateConfig.js @@ -14,18 +14,20 @@ export function validateConfig(config) { return errors; } - /** Validate organization names, existence and case */ + /** Validate organization names, existence, format, and case */ + const ghOrgRegex = /^[a-zA-Z0-9-]+$/; const orgName = config.ORGANIZATION_NAME; if (typeof orgName !== 'string' || !orgName.trim()) { errors.push('ORGANIZATION_NAME'); - } else if (orgName !== orgName.toLowerCase()) { - errors.push(`ORGANIZATION_NAME (${orgName}) must be lowercase`); + } else if (!ghOrgRegex.test(orgName)) { + errors.push(`ORGANIZATION_NAME (${orgName}) is invalid, only letters, numbers, and hyphens are allowed`); } + const hfOrgRegex = /^[a-zA-Z0-9-_\.]+$/; const hfOrgName = config.HF_ORGANIZATION_NAME; if (typeof hfOrgName !== 'string' || !hfOrgName.trim()) { errors.push('HF_ORGANIZATION_NAME'); - } else if (hfOrgName !== hfOrgName.toLowerCase()) { - errors.push(`HF_ORGANIZATION_NAME (${hfOrgName}) must be lowercase`); + } else if (!hfOrgRegex.test(hfOrgName)) { + errors.push(`HF_ORGANIZATION_NAME (${hfOrgName}) is invalid, only letters, numbers, hyphens, and underscores are allowed`); } if (!config.ORG_NAME) errors.push('ORG_NAME'); if (!config.CATALOG_REPO_NAME) errors.push('CATALOG_REPO_NAME'); diff --git a/tests/validateConfig.test.js b/tests/validateConfig.test.js index a303b62..431bf61 100644 --- a/tests/validateConfig.test.js +++ b/tests/validateConfig.test.js @@ -54,10 +54,10 @@ describe('validateConfig', () => { expect(validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: '' })).toContain('ORGANIZATION_NAME'); }); - it('errors when ORGANIZATION_NAME is not lowercase', () => { + it('errors when ORGANIZATION_NAME is not in accepted format', () => { expect( - validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: 'Imageomics' }) - ).toContain('ORGANIZATION_NAME (Imageomics) must be lowercase'); + validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: 'abc center' }) + ).toContain('ORGANIZATION_NAME (abc center) is invalid, only letters, numbers, and hyphens are allowed'); }); it('errors when HF_ORGANIZATION_NAME is missing', () => { @@ -69,10 +69,10 @@ describe('validateConfig', () => { expect(validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: '' })).toContain('HF_ORGANIZATION_NAME'); }); - it('errors when HF_ORGANIZATION_NAME is not lowercase', () => { + it('errors when HF_ORGANIZATION_NAME is not in accepted format', () => { expect( - validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: 'Imageomics' }) - ).toContain('HF_ORGANIZATION_NAME (Imageomics) must be lowercase'); + validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: 'abc center' }) + ).toContain('HF_ORGANIZATION_NAME (abc center) is invalid, only letters, numbers, hyphens, and underscores are allowed'); }); it('errors when ORG_NAME is missing', () => { diff --git a/vite.config.js b/vite.config.js index aa370c8..fc44854 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,7 +4,7 @@ import fs from 'fs'; import jsYaml from 'js-yaml'; import { validateConfig } from './src/validateConfig.js'; -let CONFIG_PATH = './public/config.yaml'; +const CONFIG_PATH = './public/config.yaml'; let parsedConfig; // Run config validation before Vite starts (dev/build/preview) From 1fa1c62fac24b7fd5774a99616f3915c0ff8dc79 Mon Sep 17 00:00:00 2001 From: egrace479 Date: Mon, 15 Jun 2026 19:39:49 -0400 Subject: [PATCH 13/15] Move initialization guard to immediately after variables set no need to do more if not defined --- main.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/main.js b/main.js index 45f3bd4..780d760 100644 --- a/main.js +++ b/main.js @@ -725,6 +725,14 @@ document.addEventListener('DOMContentLoaded', async () => { ADDITIONAL_REPOS = CONFIG.ADDITIONAL_REPOS; ADDITIONAL_HF_REPOS = CONFIG.ADDITIONAL_HF_REPOS; + // 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 + // could return unbounded results from the Hugging Face API. + if (!ORGANIZATION_NAME || !HF_ORGANIZATION_NAME){ + console.error("Organization name is missing for one or both APIs. Halting initialization."); + return; + } + // Apply CSS custom properties and document metadata document.title = CONFIG.CATALOG_TITLE || `${CONFIG.ORG_NAME} Catalog`; document.documentElement.style.setProperty('--color-primary', CONFIG.COLORS?.primary || '#92991c'); @@ -809,14 +817,6 @@ document.addEventListener('DOMContentLoaded', async () => { }); // Initialize the Catalog Badge (Stars/Forks/Version) - // 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 - // could return unbounded results from the Hugging Face API. - if (!ORGANIZATION_NAME || !HF_ORGANIZATION_NAME){ - console.error("Organization name is missing for one or both APIs. Halting initialization."); - return; - } - fetchCatalogStats(); // Load pre-built release data (written by scripts/fetch-releases.js at build time) From 8d0432eb65af9aecf7df58a07fbdfea5813b6e1a Mon Sep 17 00:00:00 2001 From: egrace479 Date: Mon, 15 Jun 2026 19:41:16 -0400 Subject: [PATCH 14/15] fix HF regex and clarify code description --- src/validateConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validateConfig.js b/src/validateConfig.js index 8f70da1..3ff4a25 100644 --- a/src/validateConfig.js +++ b/src/validateConfig.js @@ -14,7 +14,7 @@ export function validateConfig(config) { return errors; } - /** Validate organization names, existence, format, and case */ + /** Validate organization names, existence and allowed characters */ const ghOrgRegex = /^[a-zA-Z0-9-]+$/; const orgName = config.ORGANIZATION_NAME; if (typeof orgName !== 'string' || !orgName.trim()) { @@ -22,7 +22,7 @@ export function validateConfig(config) { } else if (!ghOrgRegex.test(orgName)) { errors.push(`ORGANIZATION_NAME (${orgName}) is invalid, only letters, numbers, and hyphens are allowed`); } - const hfOrgRegex = /^[a-zA-Z0-9-_\.]+$/; + const hfOrgRegex = /^[a-zA-Z0-9_\-]+$/; const hfOrgName = config.HF_ORGANIZATION_NAME; if (typeof hfOrgName !== 'string' || !hfOrgName.trim()) { errors.push('HF_ORGANIZATION_NAME'); From 81a23730631dc8c006d8150139c74f41231be79a Mon Sep 17 00:00:00 2001 From: egrace479 Date: Mon, 15 Jun 2026 20:53:30 -0400 Subject: [PATCH 15/15] Validate PLATFORM variable type to provide formatted error --- src/validateConfig.js | 12 +++++++++--- tests/validateConfig.test.js | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/validateConfig.js b/src/validateConfig.js index 3ff4a25..d903afe 100644 --- a/src/validateConfig.js +++ b/src/validateConfig.js @@ -31,12 +31,18 @@ export function validateConfig(config) { } if (!config.ORG_NAME) errors.push('ORG_NAME'); if (!config.CATALOG_REPO_NAME) errors.push('CATALOG_REPO_NAME'); - if (!config.PLATFORM) errors.push('PLATFORM'); - /** Update to include 'codeberg' and 'gitlab' once supported */ + + /** Update to include 'codeberg' and 'gitlab' once supported + * PLATFORM will be parsed without whitespace, but must be string to avoid undefined errors + */ const supportedPlatforms = ['github']; - if (config.PLATFORM && !supportedPlatforms.includes(config.PLATFORM.toLowerCase())) { + const platform = config.PLATFORM; + if (!platform || typeof platform !== 'string') { + errors.push('PLATFORM'); + } else if (!supportedPlatforms.includes(platform.toLowerCase())) { errors.push(`PLATFORM must be one of: ${supportedPlatforms.join(', ')}`); } + if (!config.API_BASE_URL) errors.push('API_BASE_URL'); if (config.REFRESH_INTERVAL_DAYS == null) errors.push('REFRESH_INTERVAL_DAYS'); diff --git a/tests/validateConfig.test.js b/tests/validateConfig.test.js index 431bf61..089e2e6 100644 --- a/tests/validateConfig.test.js +++ b/tests/validateConfig.test.js @@ -97,6 +97,10 @@ describe('validateConfig', () => { const { PLATFORM: _, ...config } = VALID_CONFIG; expect(validateConfig(config)).toContain('PLATFORM'); }); + + it('errors when PLATFORM is not a string', () => { + expect(validateConfig({ ...VALID_CONFIG, PLATFORM: 123 })).toContain('PLATFORM'); + }); it('errors when PLATFORM is unrecognized', () => { const errors = validateConfig({ ...VALID_CONFIG, PLATFORM: 'blah' });