Skip to content
Merged
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
96 changes: 96 additions & 0 deletions .artifacts/massive-improvement/REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# 大規模コード改善レポート

## Summary

github-pera1-workersの大規模改善を実施。888行の単一ファイルをモジュール分割し、UIをモダン化、開発ツールチェーンを刷新、Chrome拡張を新規作成。

## 構造変更(Before / After)

```mermaid
flowchart LR
subgraph Before["Before: 単一ファイル"]
A1[src/index.ts<br/>888行] --> B1[全ロジック混在<br/>ルーティング+URL解析+<br/>GitHub API+フィルタ+ツリー+UI+MCP]
end
subgraph After["After: モジュール分割"]
A2[src/index.ts<br/>~50行 ルーティング] --> R2[src/resolver.ts<br/>URL/クエリ解決]
A2 --> B2[src/github.ts<br/>GitHub API+ZIP処理]
A2 --> C2[src/mcp.ts<br/>MCPサーバー]
A2 --> D2[src/ui.ts<br/>UI/HTML]
B2 --> E2[src/filters.ts<br/>フィルタ]
B2 --> F2[src/tree.ts<br/>ツリー表示]
G2[src/types.ts<br/>型定義] -.-> R2
H2[src/constants.ts<br/>定数] -.-> B2
end
```

## 変更ファイル一覧

| ファイル | 変更種別 | 説明 |
|---------|---------|------|
| `src/index.ts` | 書き換え | 888行 → ~50行のルーティング薄層 |
| `src/types.ts` | 新規 | FileEntry, GitHubRepositoryParams, ParsedGitHubUrl, ResolvedRequest |
| `src/constants.ts` | 新規 | 定数定義(サイズ制限、拡張子リスト、キャッシュ設定) |
| `src/filters.ts` | 新規 | バイナリ判定、スキップ判定、インクルード判定 |
| `src/tree.ts` | 新規 | ディレクトリツリー表示生成 |
| `src/github.ts` | 新規 | GitHub ZIP取得・展開・フォーマット |
| `src/resolver.ts` | 新規 | URL解析+クエリパラメータ結合ロジック |
| `src/mcp.ts` | 新規 | MCPサーバー定義 |
| `src/ui.ts` | 新規 | モダンランディングページ(ダークモード対応) |
| `vite.config.ts` | 新規 | Cloudflare Vite Plugin設定 |
| `wrangler.toml` | 更新 | compatibility_date更新、nodejs_compat追加 |
| `package.json` | 更新 | v2.0.0、Vite/tsgo追加、scripts更新 |
| `extension/manifest.json` | 新規 | Chrome拡張 Manifest V3 |
| `extension/content.js` | 新規 | GitHub上に「Go to Pera1」ボタン挿入 |

## 改善内容

### 1. モジュール分割
- 888行の巨大index.tsを8つの専門モジュールに分割
- 各モジュールの責務が明確(Single Responsibility)
- テスト可能な純関数として切り出し

### 2. UI/UX大改善
- CSS変数によるテーマ管理
- `prefers-color-scheme`でダークモード自動対応
- レスポンシブデザイン(モバイル対応)
- フェードイン/スライドアップアニメーション
- URLコピーボタン
- ローディング状態表示
- 特徴紹介セクション(MCP、フィルタ、ツリーモード)

### 3. 開発ツールチェーン
- **Vite + @cloudflare/vite-plugin**: `vite dev`で開発、`vite build`でビルド
- **tsgo (TypeScript 7.0 native preview)**: Go製の超高速型チェッカー
- **wrangler**: 引き続き直接デプロイにも対応

### 4. Chrome拡張「Go to Pera1」
- GitHubリポジトリページでリポ名横にボタン表示
- クリックでPera1のURLに直接ジャンプ
- MutationObserverでGitHubのSPA遷移にも対応

### 5. パフォーマンス・品質
- Cache-Controlヘッダー追加(成功: 10分、エラー: 1分)
- compatibility_dateを最新に更新
- nodejs_compatフラグ追加(jszip依存のNode.js API対応)

## スクリーンショット

### ランディングページ(ライトモード)
![Landing Page](./images/after-landing.png)

### ランディングページ(ダークモード)
![Landing Page Dark](./images/after-landing-dark.png)

### リポジトリ取得結果(Tree Mode)
![Result](./images/after-result.png)

## テスト結果

- [x] TypeScript型チェック(tsc --noEmit): パス
- [x] tsgo型チェック: パス
- [x] wrangler dry-run build: パス
- [x] Vite dev server起動: 正常
- [x] ルートページ(/): 200 OK
- [x] リポジトリ取得(?mode=tree): 200 OK
- [x] MCPエンドポイント(/mcp): 406 (想定通り - POST必要)
- [x] エラーページ: 正常表示
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install dependencies
run: npm install

- name: Type check
run: npx tsc --noEmit

- name: Run tests
run: npx vitest run

preview:
if: github.event_name == 'pull_request'
needs: test
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install dependencies
run: npm install

- name: Deploy to preview
id: deploy
run: |
OUTPUT=$(npx wrangler deploy --env preview --minify 2>&1)
echo "$OUTPUT"
PREVIEW_URL=$(echo "$OUTPUT" | grep -oP 'https://[^\s]+\.workers\.dev' | head -1)
echo "preview_url=$PREVIEW_URL" >> "$GITHUB_OUTPUT"
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

- name: Comment preview URL on PR
uses: actions/github-script@v7
with:
script: |
const previewUrl = '${{ steps.deploy.outputs.preview_url }}' || 'https://pera1-preview.<your-subdomain>.workers.dev';
const body = `🚀 **Preview deployed!**\n\n${previewUrl}\n\nThis preview will be updated on every push to this PR.`;

// Find existing bot comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Preview deployed')
);

if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
24 changes: 24 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Deploy

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install dependencies
run: npm install

- name: Deploy to Cloudflare Workers (production)
run: npx wrangler deploy --minify
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dist/

# deps
node_modules/
package-lock.json
.wrangler

# env
Expand All @@ -31,3 +32,4 @@ lerna-debug.log*

# misc
.DS_Store
.tmp-build/
99 changes: 99 additions & 0 deletions extension/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
(function () {
"use strict";

const PERA1_HOST = "pera1.pages.dev";
const BUTTON_ID = "pera1-goto-btn";

function getRepoPath() {
const path = location.pathname;
const segments = path.split("/").filter(Boolean);
// Must have at least owner/repo
if (segments.length < 2) return null;
// Exclude settings, actions, issues, pulls, etc. at the repo level
// We want: /owner/repo, /owner/repo/tree/..., /owner/repo/blob/...
return segments.slice(0, 2).join("/");
}

function createButton(repoPath) {
const btn = document.createElement("a");
btn.id = BUTTON_ID;
btn.href = `https://${PERA1_HOST}/github.com/${repoPath}`;
btn.target = "_blank";
btn.rel = "noopener noreferrer";
btn.textContent = "Go to Pera1";
btn.style.cssText = [
"display: inline-flex",
"align-items: center",
"gap: 4px",
"margin-left: 8px",
"padding: 3px 10px",
"font-size: 12px",
"font-weight: 600",
"color: #fff",
"background: #0969da",
"border-radius: 6px",
"text-decoration: none",
"vertical-align: middle",
"line-height: 1.5",
"cursor: pointer",
"transition: background 0.15s",
].join(";");

btn.addEventListener("mouseenter", () => {
btn.style.background = "#0550ae";
});
btn.addEventListener("mouseleave", () => {
btn.style.background = "#0969da";
});

return btn;
}

function injectButton() {
// Avoid duplicate
if (document.getElementById(BUTTON_ID)) return;

const repoPath = getRepoPath();
if (!repoPath) return;

// Try multiple selectors for GitHub's repo heading area
const selectors = [
"#repository-container-header strong[itemprop='name'] a",
"#repository-container-header .AppHeader-context-item-label",
"[data-pjax='#repo-content-pjax-container'] strong a",
".AppHeader-context-full li:last-child a",
];

let anchor = null;
for (const sel of selectors) {
anchor = document.querySelector(sel);
if (anchor) break;
}

if (!anchor) return;

const btn = createButton(repoPath);
// Insert after the repo name element
const parent = anchor.closest("li") || anchor.parentElement;
if (parent) {
parent.style.display =
parent.style.display === "flex" ? "flex" : parent.style.display;
parent.appendChild(btn);
}
}

// Initial injection
injectButton();

// Re-inject on GitHub's SPA navigation (turbo/pjax)
let lastUrl = location.href;
const observer = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
// Small delay for DOM to update
setTimeout(injectButton, 300);
}
});

observer.observe(document.body, { childList: true, subtree: true });
})();
16 changes: 16 additions & 0 deletions extension/generate-icons.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
# Generate simple placeholder icons for the extension
# Requires ImageMagick (convert command)
# If not available, create the icons manually

if command -v magick &> /dev/null; then
magick -size 48x48 xc:'#0969da' -fill white -font Helvetica -pointsize 24 -gravity center -annotate 0 'P1' extension/icon48.png
magick -size 128x128 xc:'#0969da' -fill white -font Helvetica -pointsize 64 -gravity center -annotate 0 'P1' extension/icon128.png
echo "Icons generated!"
elif command -v convert &> /dev/null; then
convert -size 48x48 xc:'#0969da' -fill white -font Helvetica -pointsize 24 -gravity center -annotate 0 'P1' extension/icon48.png
convert -size 128x128 xc:'#0969da' -fill white -font Helvetica -pointsize 64 -gravity center -annotate 0 'P1' extension/icon128.png
echo "Icons generated!"
else
echo "ImageMagick not found. Please create icon48.png and icon128.png manually."
fi
Binary file added extension/icon128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extension/icon48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions extension/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"manifest_version": 3,
"name": "Go to Pera1",
"version": "1.0.0",
"description": "Add a 'Go to Pera1' button on GitHub repository pages to quickly fetch code as plain text for LLMs.",
"content_scripts": [
{
"matches": ["https://github.com/*/*"],
"js": ["content.js"],
"run_at": "document_idle"
}
],
"icons": {
"48": "icon48.png",
"128": "icon128.png"
}
}
19 changes: 16 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
{
"name": "pera1",
"version": "1.2.0",
"version": "2.0.0",
"type": "module",
"license": "MIT",
"scripts": {
"dev": "wrangler dev",
"dev": "vite dev",
"dev:wrangler": "wrangler dev",
"build": "vite build",
"deploy": "vite build && wrangler deploy",
"deploy:workers": "wrangler deploy --minify",
"tail": "wrangler tail"
"typecheck": "tsc --noEmit",
"typecheck:native": "node node_modules/@typescript/native-preview/bin/tsgo.js --noEmit",
"tail": "wrangler tail",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@hono/mcp": "^0.1.1",
Expand All @@ -15,7 +23,12 @@
"zod": "^3.24.1"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.30.1",
"@cloudflare/workers-types": "^4.20250405.0",
"@typescript/native-preview": "7.0.0-dev.20260326.1",
"typescript": "^6.0.2",
"vite": "^8.0.3",
"vitest": "^4.1.2",
"wrangler": "^4.7.2"
}
}
Loading
Loading