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
4 changes: 4 additions & 0 deletions .github/workflows/deploy-ghpages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches:
- main
- emily/gh-pages-deploy
schedule:
# Rebuild weekly (Saturday night / Sunday 00:00 UTC)
- cron: "0 0 * * 0"
workflow_dispatch: # Allow manual trigger

jobs:
build_and_deploy:
Expand Down
37 changes: 35 additions & 2 deletions plugins/repo-data-omit-list.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,37 @@
{
"description": "List of repositories to exclude from the Repository Explorer. Add repository full names (e.g., 'owner/repo-name') to the omitRepos array.",
"omitRepos": ["voqal/voqal"]
"description": "Configuration for Repository Explorer exclusions and tag normalization.",
"omitRepos": ["voqal/voqal", "Jailsonfs/community"],
"implicitTags": [
"talonvoice",
"talon",
"voice",
"voice-recognition",
"speech-recognition",
"voice-commands",
"voice-control"
],
"tagAliases": {
"a11y": "accessibility",
"macos-accessibility": "accessibility",
"maths": "math",
"python3": "python",
"hci": "human-computer-interaction",
"games": "game",
"garrysmod": "game",
"gmod": "game",
"slaythespire": "game",
"slaythespire-mod": "game",
"gameboyadvance": "game",
"mgba": "game",
"blazor-server": "blazor",
"blazor-components": "blazor",
"awesome": "list",
"awesome-list": "list",
"gpt": "ai",
"openai": "ai",
"chatgpt": "ai",
"llm": "ai",
"copilot": "ai"
},
"matchNamesToExistingTags": true
}
102 changes: 100 additions & 2 deletions plugins/repo-data-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ module.exports = function (context, options) {
"../.docusaurus/repo-data-plugin/default/repos.json",
);

// Load omit configuration from repo-explorer-omit-list.json
// Load omit configuration from repo-data-omit-list.json
let omitRepos = [];
let implicitTags = [];
let tagAliases = {};
let matchNamesToExistingTags = false;

try {
const omitListFile = path.join(__dirname, "repo-data-omit-list.json");
Expand All @@ -21,6 +24,21 @@ module.exports = function (context, options) {
if (omitConfig.omitRepos && Array.isArray(omitConfig.omitRepos)) {
omitRepos = omitConfig.omitRepos;
}
if (
omitConfig.implicitTags &&
Array.isArray(omitConfig.implicitTags)
) {
implicitTags = omitConfig.implicitTags;
}
if (
omitConfig.tagAliases &&
typeof omitConfig.tagAliases === "object"
) {
tagAliases = omitConfig.tagAliases;
}
if (omitConfig.matchNamesToExistingTags === true) {
matchNamesToExistingTags = true;
}
}
} catch (error) {
console.warn("Failed to load repo-data-omit-list.json:", error.message);
Expand All @@ -33,7 +51,70 @@ module.exports = function (context, options) {
console.log(
`Repository omit list loaded: ${omitRepos.length} repositories will be excluded`,
);
} // Determine if we should fetch fresh data
}

// canonicalTags is built after repos are loaded, since we need
// to include tags that actually exist across repos.
let canonicalTags = null;

function buildCanonicalTags(repos) {
const tags = new Set(Object.values(tagAliases));
repos.forEach((repo) => {
repo.topics.forEach((t) => {
const canonical = tagAliases[t] || t;
if (!implicitTags.includes(canonical)) {
tags.add(canonical);
}
});
});
return tags;
}

/**
* Split a repo name into words, handling kebab-case, snake_case,
* camelCase, PascalCase, and mixed conventions.
* e.g. "VoiceLauncherBlazor" -> ["voice", "launcher", "blazor"]
* "talon-mouse-rig" -> ["talon", "mouse", "rig"]
* "talon_mgba_http" -> ["talon", "mgba", "http"]
*/
function splitRepoName(name) {
return (
name
// Insert boundary before uppercase runs: "VoiceLauncher" -> "Voice Launcher"
.replace(/([a-z])([A-Z])/g, "$1 $2")
// Split acronym from next word: "HTTPServer" -> "HTTP Server"
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
.split(/[-_.\s]+/)
.map((w) => w.toLowerCase())
.filter((w) => w.length > 2)
);
}

/**
* Infer tags from a repo's name by matching words against known
* canonical tags. Only adds tags the repo doesn't already have
* (after alias resolution).
*/
function inferTags(repo) {
if (!matchNamesToExistingTags || !canonicalTags) return;
const words = splitRepoName(repo.name);
const existingCanonical = new Set(
repo.topics.map((t) => tagAliases[t] || t),
);
for (const word of words) {
const canonical = tagAliases[word] || word;
if (
canonicalTags.has(canonical) &&
!existingCanonical.has(canonical) &&
!implicitTags.includes(canonical)
) {
repo.topics.push(word);
existingCanonical.add(canonical);
}
}
}

// Determine if we should fetch fresh data
const isUpdateRepos =
(process.env.npm_config_argv &&
JSON.parse(process.env.npm_config_argv).original.includes(
Expand Down Expand Up @@ -74,12 +155,17 @@ module.exports = function (context, options) {
);
}

canonicalTags = buildCanonicalTags(filteredRepos);
filteredRepos.forEach(inferTags);

return {
...cachedData,
repositories: filteredRepos,
filtered_count: filteredRepos.length,
omitted_count:
(cachedData.repositories.length || 0) - filteredRepos.length,
implicitTags,
tagAliases,
};
}
} catch (error) {
Expand Down Expand Up @@ -157,12 +243,17 @@ module.exports = function (context, options) {
`After filtering: ${filteredRepos.length} repositories (${allRepos.length - filteredRepos.length} omitted)`,
);

canonicalTags = buildCanonicalTags(filteredRepos);
filteredRepos.forEach(inferTags);

return {
repositories: filteredRepos,
total_count: totalCount,
filtered_count: filteredRepos.length,
omitted_count: allRepos.length - filteredRepos.length,
generated_at: new Date().toISOString(),
implicitTags,
tagAliases,
};
} catch (error) {
console.error("Failed to fetch repository data:", error);
Expand All @@ -179,13 +270,18 @@ module.exports = function (context, options) {
return !omitRepos.includes(fullName);
});

canonicalTags = buildCanonicalTags(filteredRepos);
filteredRepos.forEach(inferTags);

return {
...cachedData,
repositories: filteredRepos,
filtered_count: filteredRepos.length,
omitted_count:
(cachedData.repositories.length || 0) - filteredRepos.length,
error: `Build-time fetch failed: ${error.message}. Using cached data.`,
implicitTags,
tagAliases,
};
}
} catch (cacheError) {
Expand All @@ -198,6 +294,8 @@ module.exports = function (context, options) {
total_count: 0,
generated_at: new Date().toISOString(),
error: error.message,
implicitTags,
tagAliases,
};
}
},
Expand Down
Loading