Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (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/`)
Comment thread
egrace479 marked this conversation as resolved.
- `PLATFORM` — code repository platform; only `github` is currently supported
- `ADDITIONAL_REPOS` — forked or external GitHub repos to include
Expand Down
5 changes: 3 additions & 2 deletions docs/personalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ 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)
* `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
Comment thread
egrace479 marked this conversation as resolved.
* `CATALOG_REPO_NAME`: Repository name for the catalog itself (used for stats badge)

### Branding
Expand Down
16 changes: 10 additions & 6 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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) {
Comment thread
egrace479 marked this conversation as resolved.
throw new Error(`HTTP error! status: ${response.status}`);
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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' },
Expand All @@ -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;
Expand Down Expand Up @@ -808,10 +809,13 @@ 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){
console.error("Organization name is missing for one or both APIs. Halting initialization.");
return;
}

fetchCatalogStats();

Expand Down
6 changes: 4 additions & 2 deletions public/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 (for API calls)
HF_ORGANIZATION_NAME: imageomics # Hugging Face organization name (case-sensitive, for API calls)
Comment thread
egrace479 marked this conversation as resolved.
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
Expand Down Expand Up @@ -43,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"
Expand Down
4 changes: 2 additions & 2 deletions scripts/export-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 15 additions & 1 deletion src/validateConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@ export function validateConfig(config) {
return errors;
}

if (!config.ORGANIZATION_NAME) errors.push('ORGANIZATION_NAME');
/** Validate organization names, existence, format, and case */
Comment thread
egrace479 marked this conversation as resolved.
Outdated
const ghOrgRegex = /^[a-zA-Z0-9-]+$/;
const orgName = config.ORGANIZATION_NAME;
if (typeof orgName !== 'string' || !orgName.trim()) {
errors.push('ORGANIZATION_NAME');
} else if (!ghOrgRegex.test(orgName)) {
errors.push(`ORGANIZATION_NAME (${orgName}) is invalid, only letters, numbers, and hyphens are allowed`);
}
Comment thread
egrace479 marked this conversation as resolved.
const hfOrgRegex = /^[a-zA-Z0-9-_\.]+$/;
Comment thread
Copilot marked this conversation as resolved.
Outdated
const hfOrgName = config.HF_ORGANIZATION_NAME;
if (typeof hfOrgName !== 'string' || !hfOrgName.trim()) {
errors.push('HF_ORGANIZATION_NAME');
} else if (!hfOrgRegex.test(hfOrgName)) {
errors.push(`HF_ORGANIZATION_NAME (${hfOrgName}) is invalid, only letters, numbers, hyphens, and underscores are allowed`);
}
Comment thread
egrace479 marked this conversation as resolved.
Comment thread
egrace479 marked this conversation as resolved.
if (!config.ORG_NAME) errors.push('ORG_NAME');
if (!config.CATALOG_REPO_NAME) errors.push('CATALOG_REPO_NAME');
if (!config.PLATFORM) errors.push('PLATFORM');
Comment thread
egrace479 marked this conversation as resolved.
Outdated
Expand Down
23 changes: 23 additions & 0 deletions tests/validateConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -53,6 +54,27 @@ describe('validateConfig', () => {
expect(validateConfig({ ...VALID_CONFIG, ORGANIZATION_NAME: '' })).toContain('ORGANIZATION_NAME');
});

it('errors when ORGANIZATION_NAME is not in accepted format', () => {
expect(
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', () => {
const { HF_ORGANIZATION_NAME: _, ...config } = VALID_CONFIG;
expect(validateConfig(config)).toContain('HF_ORGANIZATION_NAME');
});
Comment thread
egrace479 marked this conversation as resolved.

it('errors when HF_ORGANIZATION_NAME is empty string', () => {
expect(validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: '' })).toContain('HF_ORGANIZATION_NAME');
});

it('errors when HF_ORGANIZATION_NAME is not in accepted format', () => {
expect(
validateConfig({ ...VALID_CONFIG, HF_ORGANIZATION_NAME: 'abc center' })
).toContain('HF_ORGANIZATION_NAME (abc center) is invalid, only letters, numbers, hyphens, and underscores are allowed');
});

Comment thread
egrace479 marked this conversation as resolved.
it('errors when ORG_NAME is missing', () => {
const { ORG_NAME: _, ...config } = VALID_CONFIG;
expect(validateConfig(config)).toContain('ORG_NAME');
Expand Down Expand Up @@ -141,6 +163,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');
Expand Down
28 changes: 28 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -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';

const CONFIG_PATH = './public/config.yaml';
let parsedConfig;
Comment thread
egrace479 marked this conversation as resolved.

// Run config validation before Vite starts (dev/build/preview)
try {
const fileContents = fs.readFileSync(CONFIG_PATH, 'utf8');
parsedConfig = jsYaml.load(fileContents);
Comment thread
egrace479 marked this conversation as resolved.
} catch (error) {
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
}

// 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 Please fix the listed issue(s) 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]

Expand Down