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/**",
|
||||
"./tests/**",
|
||||
"./website/**",
|
||||
"./browser/**",
|
||||
"./.devcontainer/**",
|
||||
"./.github/**",
|
||||
"package.json",
|
||||
@@ -20,7 +21,9 @@
|
||||
"website/client/.vitepress/.temp",
|
||||
"website/client/.vitepress/dist",
|
||||
"website/client/.vitepress/cache",
|
||||
"website/server/dist"
|
||||
"website/server/dist",
|
||||
"browser/dist",
|
||||
"browser/packages"
|
||||
]
|
||||
},
|
||||
"organizeImports": {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Repomix
|
||||
# Repomix Extension
|
||||
|
||||
A browser extension that adds a Repomix button to GitHub repository pages.
|
||||
|
||||

|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Adds a "Repomix" button to GitHub repository pages
|
||||
@@ -79,12 +77,3 @@ This extension:
|
||||
- Does not track user behavior
|
||||
- Only accesses github.com
|
||||
- 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,
|
||||
"name": "repomix",
|
||||
"version": "1.0.0",
|
||||
"description": "A browser extension that adds a Repomix button to GitHub repositories",
|
||||
"scripts": {
|
||||
"dev": "webextension-toolbox dev",
|
||||
"build": "webextension-toolbox build",
|
||||
"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": [
|
||||
"chrome",
|
||||
@@ -21,7 +24,21 @@
|
||||
"author": "yamadashy",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"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