mirror of
https://github.com/yamadashy/repomix.git
synced 2025-06-11 00:25:54 +03:00
feat(browser): Add TypeScript support and configuration for Repomix extension
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
"./src/**",
|
"./src/**",
|
||||||
"./tests/**",
|
"./tests/**",
|
||||||
"./website/**",
|
"./website/**",
|
||||||
|
"./browser/**",
|
||||||
"./.devcontainer/**",
|
"./.devcontainer/**",
|
||||||
"./.github/**",
|
"./.github/**",
|
||||||
"package.json",
|
"package.json",
|
||||||
@@ -20,7 +21,9 @@
|
|||||||
"website/client/.vitepress/.temp",
|
"website/client/.vitepress/.temp",
|
||||||
"website/client/.vitepress/dist",
|
"website/client/.vitepress/dist",
|
||||||
"website/client/.vitepress/cache",
|
"website/client/.vitepress/cache",
|
||||||
"website/server/dist"
|
"website/server/dist",
|
||||||
|
"browser/dist",
|
||||||
|
"browser/packages"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
# Repomix
|
# Repomix Extension
|
||||||
|
|
||||||
A browser extension that adds a Repomix button to GitHub repository pages.
|
A browser extension that adds a Repomix button to GitHub repository pages.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
- Adds a "Repomix" button to GitHub repository pages
|
- Adds a "Repomix" button to GitHub repository pages
|
||||||
@@ -79,12 +77,3 @@ This extension:
|
|||||||
- Does not track user behavior
|
- Does not track user behavior
|
||||||
- Only accesses github.com
|
- Only accesses github.com
|
||||||
- Requires minimal permissions
|
- Requires minimal permissions
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## 🙋♂️ Related Projects
|
|
||||||
|
|
||||||
- [Repomix](https://github.com/yamadashy/repomix) - AI-friendly repository packing tool
|
|
||||||
- [Repomix Website](https://repomix.com) - Online version of Repomix
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
// This background.js is kept minimal as all implementation is handled in content_scripts
|
|
||||||
|
|
||||||
const injectContentToTab = async (tab) => {
|
|
||||||
// Skip if URL is undefined
|
|
||||||
if (!tab.url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if tab is discarded
|
|
||||||
if (tab.discarded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if tab ID is undefined
|
|
||||||
if (tab.id === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if not a GitHub URL
|
|
||||||
if (!tab.url.startsWith('https://github.com/')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const manifest = chrome.runtime.getManifest();
|
|
||||||
|
|
||||||
// Inject CSS
|
|
||||||
if (manifest.content_scripts && manifest.content_scripts[0].css) {
|
|
||||||
await chrome.scripting.insertCSS({
|
|
||||||
target: { tabId: tab.id },
|
|
||||||
files: manifest.content_scripts[0].css
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inject JavaScript
|
|
||||||
if (manifest.content_scripts && manifest.content_scripts[0].js) {
|
|
||||||
await chrome.scripting.executeScript({
|
|
||||||
target: { tabId: tab.id },
|
|
||||||
files: manifest.content_scripts[0].js
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore errors (e.g., chrome:// pages, extension pages, etc.)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply content to all tabs when installed or updated
|
|
||||||
chrome.runtime.onInstalled.addListener(() => {
|
|
||||||
chrome.tabs.query({}, async (tabs) => {
|
|
||||||
for (const tab of tabs) {
|
|
||||||
try {
|
|
||||||
await injectContentToTab(tab);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to inject content to tab:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply content when a new tab is created
|
|
||||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
||||||
if (changeInfo.status === 'complete') {
|
|
||||||
injectContentToTab(tab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
74
browser/app/scripts/background.ts
Normal file
74
browser/app/scripts/background.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// This background.ts is kept minimal as all implementation is handled in content_scripts
|
||||||
|
|
||||||
|
const injectContentToTab = async (tab: chrome.tabs.Tab): Promise<void> => {
|
||||||
|
// Skip if URL is undefined
|
||||||
|
if (!tab.url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if tab is discarded
|
||||||
|
if (tab.discarded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if tab ID is undefined
|
||||||
|
if (tab.id === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if not a GitHub URL
|
||||||
|
if (!tab.url.startsWith('https://github.com/')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
|
||||||
|
// Inject CSS
|
||||||
|
if (manifest.content_scripts?.[0]?.css) {
|
||||||
|
await chrome.scripting.insertCSS({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: manifest.content_scripts[0].css,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject JavaScript
|
||||||
|
if (manifest.content_scripts?.[0]?.js) {
|
||||||
|
await chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
files: manifest.content_scripts[0].js,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error injecting content script:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle installation
|
||||||
|
chrome.runtime.onInstalled.addListener(async (): Promise<void> => {
|
||||||
|
console.log('Repomix extension installed');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all GitHub tabs
|
||||||
|
const tabs = await chrome.tabs.query({
|
||||||
|
url: 'https://github.com/*',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inject content script to existing GitHub tabs
|
||||||
|
for (const tab of tabs) {
|
||||||
|
await injectContentToTab(tab);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during installation:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle tab updates
|
||||||
|
chrome.tabs.onUpdated.addListener(
|
||||||
|
async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab): Promise<void> => {
|
||||||
|
// Only inject when page is completely loaded
|
||||||
|
if (changeInfo.status === 'complete') {
|
||||||
|
await injectContentToTab(tab);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
// Function to add Repomix button
|
|
||||||
function addRepomixButton() {
|
|
||||||
// If button already exists, do nothing
|
|
||||||
if (document.querySelector('.repomix-button')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get repository main navigation element
|
|
||||||
const navActions = document.querySelector('ul.pagehead-actions');
|
|
||||||
if (!navActions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get repository information from current URL
|
|
||||||
const pathMatch = window.location.pathname.match(/^\/([^/]+)\/([^/]+)/);
|
|
||||||
if (!pathMatch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, owner, repo] = pathMatch;
|
|
||||||
const repoUrl = `https://github.com/${owner}/${repo}`;
|
|
||||||
const repomixUrl = `https://repomix.com/?repo=${encodeURIComponent(repoUrl)}`;
|
|
||||||
|
|
||||||
// Create Repomix button container
|
|
||||||
const container = document.createElement('li');
|
|
||||||
|
|
||||||
// Create BtnGroup container
|
|
||||||
const btnGroup = document.createElement('div');
|
|
||||||
btnGroup.setAttribute('data-view-component', 'true');
|
|
||||||
btnGroup.className = 'BtnGroup';
|
|
||||||
|
|
||||||
// Create button
|
|
||||||
const button = document.createElement('a');
|
|
||||||
button.href = repomixUrl;
|
|
||||||
button.target = '_blank';
|
|
||||||
button.rel = 'noopener noreferrer';
|
|
||||||
button.className = 'repomix-button btn-sm btn BtnGroup-item';
|
|
||||||
button.setAttribute('data-view-component', 'true');
|
|
||||||
|
|
||||||
// Add icon
|
|
||||||
const icon = document.createElement('span');
|
|
||||||
icon.className = 'octicon';
|
|
||||||
icon.innerHTML = `<img src="${chrome.runtime.getURL('images/icon-64.png')}" width="16" height="16" alt="Repomix">`;
|
|
||||||
|
|
||||||
// Add text with i18n support
|
|
||||||
const text = document.createTextNode(' Repomix');
|
|
||||||
button.appendChild(icon);
|
|
||||||
button.appendChild(text);
|
|
||||||
|
|
||||||
btnGroup.appendChild(button);
|
|
||||||
container.appendChild(btnGroup);
|
|
||||||
|
|
||||||
// Add to navigation
|
|
||||||
navActions.insertBefore(container, navActions.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute immediately and on DOMContentLoaded
|
|
||||||
addRepomixButton();
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
addRepomixButton();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle GitHub SPA navigation
|
|
||||||
let lastUrl = location.href;
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
const url = location.href;
|
|
||||||
if (url !== lastUrl) {
|
|
||||||
lastUrl = url;
|
|
||||||
setTimeout(() => {
|
|
||||||
addRepomixButton();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monitor navigation element addition (only if button doesn't exist)
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
const repomixButton = document.querySelector('.repomix-button');
|
|
||||||
if (!repomixButton && mutation.type === 'childList') {
|
|
||||||
mutation.addedNodes.forEach((node) => {
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const navActions = node.querySelector ? node.querySelector('ul.pagehead-actions') : null;
|
|
||||||
if (navActions || (node.matches && node.matches('ul.pagehead-actions'))) {
|
|
||||||
setTimeout(() => {
|
|
||||||
addRepomixButton();
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
145
browser/app/scripts/content.ts
Normal file
145
browser/app/scripts/content.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
interface RepositoryInfo {
|
||||||
|
owner: string;
|
||||||
|
repo: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RepomixButtonOptions {
|
||||||
|
text: string;
|
||||||
|
href: string;
|
||||||
|
iconSrc?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const BUTTON_CLASS = 'repomix-button';
|
||||||
|
const ICON_SIZE = 16;
|
||||||
|
const REPOMIX_BASE_URL = 'https://repomix.com';
|
||||||
|
const BUTTON_TEXT = 'Repomix';
|
||||||
|
const DEFAULT_ICON_PATH = 'images/icon-64.png';
|
||||||
|
|
||||||
|
// Button functions
|
||||||
|
function isRepomixButtonAlreadyExists(): boolean {
|
||||||
|
return document.querySelector(`.${BUTTON_CLASS}`) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRepomixButton(options: RepomixButtonOptions): HTMLElement {
|
||||||
|
const container = document.createElement('li');
|
||||||
|
|
||||||
|
const button = document.createElement('a');
|
||||||
|
button.className = `${BUTTON_CLASS} btn-sm btn BtnGroup-item`;
|
||||||
|
button.href = options.href;
|
||||||
|
button.target = '_blank';
|
||||||
|
button.rel = 'noopener noreferrer';
|
||||||
|
button.title = 'Open with Repomix';
|
||||||
|
|
||||||
|
// Create octicon container
|
||||||
|
const octicon = document.createElement('span');
|
||||||
|
octicon.className = 'octicon';
|
||||||
|
octicon.setAttribute('aria-hidden', 'true');
|
||||||
|
|
||||||
|
// Use chrome.runtime.getURL for the icon
|
||||||
|
const iconSrc = options.iconSrc || chrome.runtime.getURL(DEFAULT_ICON_PATH);
|
||||||
|
octicon.innerHTML = `<img src="${iconSrc}" width="${ICON_SIZE}" height="${ICON_SIZE}" alt="Repomix">`;
|
||||||
|
|
||||||
|
button.appendChild(octicon);
|
||||||
|
|
||||||
|
// Add button text
|
||||||
|
const textSpan = document.createElement('span');
|
||||||
|
textSpan.textContent = options.text;
|
||||||
|
button.appendChild(textSpan);
|
||||||
|
|
||||||
|
container.appendChild(button);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitHub functions
|
||||||
|
function extractRepositoryInfo(): RepositoryInfo | null {
|
||||||
|
const pathMatch = window.location.pathname.match(/^\/([^/]+)\/([^/]+)/);
|
||||||
|
if (!pathMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, owner, repo] = pathMatch;
|
||||||
|
return {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
url: `https://github.com/${owner}/${repo}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNavigationContainer(): Element | null {
|
||||||
|
return document.querySelector('ul.pagehead-actions');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRepositoryPage(): boolean {
|
||||||
|
// Check if we're on a repository page (not user profile, organization, etc.)
|
||||||
|
const pathParts = window.location.pathname.split('/').filter(Boolean);
|
||||||
|
return pathParts.length >= 2 && !pathParts[0].startsWith('@');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main integration functions
|
||||||
|
function addRepomixButton(): void {
|
||||||
|
// Check if button already exists
|
||||||
|
if (isRepomixButtonAlreadyExists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get repository information
|
||||||
|
const repoInfo = extractRepositoryInfo();
|
||||||
|
if (!repoInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find navigation container
|
||||||
|
const navContainer = findNavigationContainer();
|
||||||
|
if (!navContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Repomix URL
|
||||||
|
const repomixUrl = `${REPOMIX_BASE_URL}/?repo=${encodeURIComponent(repoInfo.url)}`;
|
||||||
|
|
||||||
|
// Create button
|
||||||
|
const buttonContainer = createRepomixButton({
|
||||||
|
text: BUTTON_TEXT,
|
||||||
|
href: repomixUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert button at the beginning (left side)
|
||||||
|
navContainer.prepend(buttonContainer);
|
||||||
|
|
||||||
|
console.log(`Repomix button added for ${repoInfo.owner}/${repoInfo.repo}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function observePageChanges(): void {
|
||||||
|
// Observe changes to handle GitHub's dynamic navigation
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
addRepomixButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also listen for popstate events (back/forward navigation)
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
setTimeout(() => addRepomixButton(), 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initRepomixIntegration(): void {
|
||||||
|
if (!isRepositoryPage()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRepomixButton();
|
||||||
|
observePageChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => initRepomixIntegration());
|
||||||
|
} else {
|
||||||
|
initRepomixIntegration();
|
||||||
|
}
|
||||||
1786
browser/package-lock.json
generated
1786
browser/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "repomix",
|
"name": "repomix",
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A browser extension that adds a Repomix button to GitHub repositories",
|
"description": "A browser extension that adds a Repomix button to GitHub repositories",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webextension-toolbox dev",
|
"dev": "webextension-toolbox dev",
|
||||||
"build": "webextension-toolbox build",
|
"build": "webextension-toolbox build",
|
||||||
"build-all": "npm run build chrome && npm run build firefox && npm run build edge",
|
"build-all": "npm run build chrome && npm run build firefox && npm run build edge",
|
||||||
"generate-icons": "node scripts/generate-icons.js"
|
"generate-icons": "tsx scripts/generate-icons.ts",
|
||||||
|
"lint": "npm run lint-tsc",
|
||||||
|
"lint-tsc": "tsc --noEmit",
|
||||||
|
"test": "vitest",
|
||||||
|
"archive": "git archive HEAD -o storage/source.zip"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"chrome",
|
"chrome",
|
||||||
@@ -21,7 +24,21 @@
|
|||||||
"author": "yamadashy",
|
"author": "yamadashy",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.9.4",
|
||||||
|
"@secretlint/secretlint-rule-preset-recommend": "^9.3.1",
|
||||||
|
"@types/chrome": "^0.0.323",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@types/webextension-polyfill": "^0.10.7",
|
||||||
"@webextension-toolbox/webextension-toolbox": "^7.1.1",
|
"@webextension-toolbox/webextension-toolbox": "^7.1.1",
|
||||||
"sharp": "^0.34.1"
|
"secretlint": "^9.3.1",
|
||||||
|
"sharp": "^0.34.1",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"last 2 versions, not dead, > 0.2%"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const sharp = require('sharp');
|
|
||||||
|
|
||||||
const sizes = [16, 19, 32, 38, 48, 64, 128];
|
|
||||||
const inputSvg = path.join(__dirname, 'app/images/icon.svg');
|
|
||||||
const outputDir = path.join(__dirname, 'app/images');
|
|
||||||
|
|
||||||
// Create output directory if it doesn't exist
|
|
||||||
if (!fs.existsSync(outputDir)) {
|
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate PNG files for each size
|
|
||||||
sizes.forEach(size => {
|
|
||||||
sharp(inputSvg)
|
|
||||||
.resize(size, size)
|
|
||||||
.png()
|
|
||||||
.toFile(path.join(outputDir, `icon-${size}.png`))
|
|
||||||
.then(() => console.log(`Generated ${size}x${size} icon`))
|
|
||||||
.catch(err => console.error(`Error generating ${size}x${size} icon:`, err));
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Icon generation started...');
|
|
||||||
77
browser/scripts/generate-icons.ts
Normal file
77
browser/scripts/generate-icons.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
|
interface IconSize {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ICON_SIZES: readonly number[] = [16, 19, 32, 38, 48, 64, 128] as const;
|
||||||
|
const INPUT_SVG_PATH = path.join(__dirname, '../app/images/icon.svg');
|
||||||
|
const OUTPUT_DIR = path.join(__dirname, '../app/images');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the output directory exists
|
||||||
|
*/
|
||||||
|
function ensureOutputDirectory(): void {
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
|
console.log(`Created output directory: ${OUTPUT_DIR}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a PNG icon of the specified size
|
||||||
|
*/
|
||||||
|
async function generateIcon(size: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
const outputPath = path.join(OUTPUT_DIR, `icon-${size}.png`);
|
||||||
|
|
||||||
|
await sharp(INPUT_SVG_PATH).resize(size, size).png().toFile(outputPath);
|
||||||
|
|
||||||
|
console.log(`✅ Generated ${size}x${size} icon: ${outputPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error generating ${size}x${size} icon:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the input SVG file exists
|
||||||
|
*/
|
||||||
|
function validateInputFile(): void {
|
||||||
|
if (!fs.existsSync(INPUT_SVG_PATH)) {
|
||||||
|
throw new Error(`Input SVG file not found: ${INPUT_SVG_PATH}`);
|
||||||
|
}
|
||||||
|
console.log(`📁 Input SVG: ${INPUT_SVG_PATH}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to generate all icon sizes
|
||||||
|
*/
|
||||||
|
async function generateAllIcons(): Promise<void> {
|
||||||
|
console.log('🚀 Starting icon generation...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
validateInputFile();
|
||||||
|
ensureOutputDirectory();
|
||||||
|
|
||||||
|
// Generate all icons in parallel
|
||||||
|
const iconPromises = ICON_SIZES.map((size) => generateIcon(size));
|
||||||
|
await Promise.all(iconPromises);
|
||||||
|
|
||||||
|
console.log(`🎉 Successfully generated ${ICON_SIZES.length} icons!`);
|
||||||
|
console.log(`📂 Output directory: ${OUTPUT_DIR}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('💥 Failed to generate icons:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute if this file is run directly
|
||||||
|
if (require.main === module) {
|
||||||
|
void generateAllIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { generateAllIcons, generateIcon, ICON_SIZES };
|
||||||
47
browser/tests/repomix-integration.test.ts
Normal file
47
browser/tests/repomix-integration.test.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
// Mock DOM environment
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
value: {
|
||||||
|
pathname: '/yamadashy/repomix',
|
||||||
|
href: 'https://github.com/yamadashy/repomix',
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RepomixIntegration', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset DOM
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
|
||||||
|
// Mock GitHub page structure
|
||||||
|
const navActions = document.createElement('ul');
|
||||||
|
navActions.className = 'pagehead-actions';
|
||||||
|
document.body.appendChild(navActions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract repository information correctly', () => {
|
||||||
|
// This is a placeholder test since we're testing static methods
|
||||||
|
// In a real scenario, we'd need to import and test the actual classes
|
||||||
|
const pathMatch = window.location.pathname.match(/^\/([^/]+)\/([^/]+)/);
|
||||||
|
expect(pathMatch).toBeTruthy();
|
||||||
|
|
||||||
|
if (pathMatch) {
|
||||||
|
const [, owner, repo] = pathMatch;
|
||||||
|
expect(owner).toBe('yamadashy');
|
||||||
|
expect(repo).toBe('repomix');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should construct correct Repomix URL', () => {
|
||||||
|
const repoUrl = 'https://github.com/yamadashy/repomix';
|
||||||
|
const expectedUrl = `https://repomix.com/?repo=${encodeURIComponent(repoUrl)}`;
|
||||||
|
|
||||||
|
expect(expectedUrl).toBe('https://repomix.com/?repo=https%3A%2F%2Fgithub.com%2Fyamadashy%2Frepomix');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find navigation container', () => {
|
||||||
|
const navContainer = document.querySelector('ul.pagehead-actions');
|
||||||
|
expect(navContainer).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
18
browser/tsconfig.json
Normal file
18
browser/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": false,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
13
browser/vitest.config.ts
Normal file
13
browser/vitest.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
setupFiles: [],
|
||||||
|
watch: false,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user