github-mobile-reader 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md CHANGED
@@ -44,7 +44,7 @@ data
44
44
  - **GitHub Action** — 레포에 YAML 파일 하나만 추가하면 PR마다 Reader 문서가 자동 생성됩니다
45
45
  - **파일별 분리 출력** — 변경된 JS/TS 파일마다 독립적인 섹션으로 출력
46
46
  - **JSX/Tailwind 인식** — `.jsx`/`.tsx` 파일은 컴포넌트 트리(`🎨 JSX Structure`)와 Tailwind 클래스 diff(`💅 Style Changes`)를 별도 섹션으로 분리 출력
47
- - **양방향 diff 추적**추가된 코드와 삭제된 코드를 각각 별도 섹션으로 표시
47
+ - **심볼 단위 요약**전체 코드 블록을 덤프하는 대신, 어떤 함수/컴포넌트가 추가·삭제·수정됐는지 목록으로 표시
48
48
  - **보수적 설계** — 패턴이 애매할 때는 잘못된 정보를 보여주는 대신 덜 보여줍니다
49
49
  - **보안 기본값** — 토큰은 `$GITHUB_TOKEN` 환경변수로만 읽음 — 셸 히스토리나 `ps` 목록에 노출되는 `--token` 플래그 없음
50
50
 
@@ -108,18 +108,20 @@ npx github-mobile-reader --repo owner/repo --all
108
108
 
109
109
  PR마다 `reader-output/pr-<번호>.md` 파일 하나가 생성됩니다.
110
110
 
111
- JSX/TSX 파일은 추가 섹션이 생성됩니다:
111
+ 변경 요약이 없는 파일은 자동으로 생략됩니다. JSX/TSX 파일은 추가 섹션이 생성됩니다:
112
112
 
113
113
  ```
114
114
  # 📖 PR #42 — My Feature
115
115
 
116
116
  ## 📄 `src/App.tsx`
117
117
 
118
- ### 🧠 Logical Flow ← JS 로직 트리
118
+ ### 변경된 함수 / 컴포넌트
119
+ - ✅ `parseArgs()` — added
120
+ - ✏️ `processPR()` — modified
121
+ - ❌ `oldHelper()` — removed
122
+
119
123
  ### 🎨 JSX Structure ← 컴포넌트 계층 구조 (JSX/TSX 전용)
120
124
  ### 💅 Style Changes ← 추가/제거된 Tailwind 클래스 (JSX/TSX 전용)
121
- ### ✅ Added Code
122
- ### ❌ Removed Code
123
125
  ```
124
126
 
125
127
  > **참고:** `reader-output/`는 기본적으로 `.gitignore`에 포함되어 있습니다 — 생성된 파일은 로컬에만 저장되며 레포지토리에 커밋되지 않습니다.
@@ -220,8 +222,9 @@ src/languages/
220
222
  이 라이브러리를 사용하는 가장 쉬운 방법입니다. 매 PR마다 자동으로:
221
223
 
222
224
  1. 변경된 `.js` / `.jsx` / `.ts` / `.tsx` / `.mjs` / `.cjs` 파일의 diff를 파싱
223
- 2. `docs/reader/pr-<번호>.md` 파일을 레포에 저장
224
- 3. PR에 요약 코멘트를 자동으로 달아줍니다
225
+ 2. Reader Markdown 요약을 **PR 코멘트**로 게시 (파일 커밋 불필요)
226
+
227
+ > **브랜치 보호 규칙과 호환** — 워크플로우는 `pull-requests: write` 권한만 필요합니다. 커밋을 push하지 않으므로 엄격한 브랜치 보호 규칙이 설정된 레포에서도 동작합니다.
225
228
 
226
229
  ### Step 1 — 워크플로우 파일 추가
227
230
 
@@ -235,7 +238,6 @@ on:
235
238
  types: [opened, synchronize, reopened]
236
239
 
237
240
  permissions:
238
- contents: write # .md 파일 커밋
239
241
  pull-requests: write # PR 코멘트 작성
240
242
 
241
243
  jobs:
@@ -247,44 +249,45 @@ jobs:
247
249
  - name: Checkout
248
250
  uses: actions/checkout@v4
249
251
  with:
250
- fetch-depth: 0 # git diff에 전체 히스토리 필요
252
+ fetch-depth: 0
251
253
 
252
- - name: Generate Reader Markdown
253
- uses: 3rdflr/github-mobile-reader@v1
254
+ - name: Setup Node
255
+ uses: actions/setup-node@v4
254
256
  with:
255
- github_token: ${{ secrets.GITHUB_TOKEN }}
256
- base_branch: ${{ github.base_ref }}
257
- output_dir: docs/reader
257
+ node-version: '20'
258
+
259
+ - name: Generate Reader Markdown
260
+ run: npx github-mobile-reader@latest --repo ${{ github.repository }} --pr ${{ github.event.pull_request.number }} --out ./reader-output
258
261
  env:
259
- PR_NUMBER: ${{ github.event.pull_request.number }}
260
-
261
- - name: Commit Reader Markdown
262
- run: |
263
- git config user.name "github-actions[bot]"
264
- git config user.email "github-actions[bot]@users.noreply.github.com"
265
- git add docs/reader/
266
- if git diff --cached --quiet; then
267
- echo "변경사항 없음"
268
- else
269
- git commit -m "docs(reader): PR #${{ github.event.pull_request.number }} 모바일 리더 업데이트 [skip ci]"
270
- git push
271
- fi
262
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
263
+
264
+ - name: Post PR Comment
265
+ uses: actions/github-script@v7
266
+ with:
267
+ script: |
268
+ const fs = require('fs');
269
+ const path = './reader-output/pr-${{ github.event.pull_request.number }}.md';
270
+ if (!fs.existsSync(path)) { console.log('생성된 파일 없음.'); return; }
271
+ const body = fs.readFileSync(path, 'utf8');
272
+ const comments = await github.rest.issues.listComments({
273
+ owner: context.repo.owner, repo: context.repo.repo,
274
+ issue_number: ${{ github.event.pull_request.number }},
275
+ });
276
+ const prev = comments.data.find(c =>
277
+ c.user.login === 'github-actions[bot]' && c.body.startsWith('# 📖 PR #')
278
+ );
279
+ if (prev) await github.rest.issues.deleteComment({
280
+ owner: context.repo.owner, repo: context.repo.repo, comment_id: prev.id,
281
+ });
282
+ await github.rest.issues.createComment({
283
+ owner: context.repo.owner, repo: context.repo.repo,
284
+ issue_number: ${{ github.event.pull_request.number }}, body,
285
+ });
272
286
  ```
273
287
 
274
288
  ### Step 2 — PR 열기
275
289
 
276
- 이게 전부입니다. 이후 모든 PR에 자동으로:
277
-
278
- - `docs/reader/pr-<번호>.md` 파일 생성
279
- - 생성된 파일 링크가 담긴 PR 코멘트 자동 게시
280
-
281
- ### Action 입력값
282
-
283
- | 입력값 | 필수 | 기본값 | 설명 |
284
- | -------------- | ---- | ------------- | ---------------------------------- |
285
- | `github_token` | ✅ | — | `${{ secrets.GITHUB_TOKEN }}` 사용 |
286
- | `base_branch` | ❌ | `main` | PR이 머지되는 대상 브랜치 |
287
- | `output_dir` | ❌ | `docs/reader` | 생성된 `.md` 파일 저장 경로 |
290
+ 이게 전부입니다. 이후 모든 PR에 `github-actions[bot]`이 Reader Markdown 코멘트를 자동으로 달아줍니다. 새 커밋을 push할 때마다 코멘트가 업데이트됩니다 (중복 게시 없음).
288
291
 
289
292
  ---
290
293
 
@@ -363,47 +366,53 @@ console.log(treeLines.join("\n"));
363
366
 
364
367
  ## 출력 형식
365
368
 
366
- 생성된 Reader Markdown 문서는 개의 섹션으로 구성됩니다:
369
+ 생성된 Reader Markdown 코멘트는 다음 구조로 작성됩니다:
367
370
 
368
- ````markdown
369
- # 📖 GitHub Reader View
371
+ ```markdown
372
+ # 📖 PR #42 — My Feature
370
373
 
371
- > Generated by **github-mobile-reader**
372
374
  > Repository: my-org/my-repo
373
- > Pull Request: #42
374
375
  > Commit: `a1b2c3d`
375
- > File: `src/api/users.ts`
376
+ > 변경된 JS/TS 파일: 2개
376
377
 
377
378
  ---
378
379
 
379
- ## 🧠 Logical Flow
380
+ ## 📄 `src/api/users.ts`
380
381
 
381
- ```
382
- getData()
383
- └─ filter(callback)
384
- └─ map(item → value)
385
- └─ reduce(callback)
386
- ```
382
+ ### 변경된 함수 / 컴포넌트
387
383
 
388
- ##Added Code
384
+ -`getUser()` — added
385
+ - ✏️ `updateUser()` — modified
386
+ - ❌ `legacyFetch()` — removed
389
387
 
390
- ```typescript
391
- const result = getData()
392
- .filter((item) => item.active)
393
- .map((item) => item.value)
394
- .reduce((a, b) => a + b, 0);
395
- ```
388
+ ---
389
+
390
+ ## 📄 `src/components/UserCard.tsx`
391
+
392
+ ### 변경된 함수 / 컴포넌트
396
393
 
397
- ## Removed Code
394
+ - ✏️ `UserCard()` — modified
398
395
 
399
- ```typescript
400
- const result = getData().map((item) => item.value);
396
+ ### 🎨 JSX Structure
397
+
398
+ ```
399
+ div
400
+ header
401
+ section
402
+ UserAvatar
403
+ UserName
401
404
  ```
402
405
 
406
+ ### 💅 Style Changes
407
+
408
+ **div**
409
+ + dark:bg-gray-900 rounded-xl
410
+ - bg-white
411
+
403
412
  ---
404
413
 
405
- 🛠 Auto-generated by github-mobile-reader. Do not edit manually.
406
- ````
414
+ 🛠 Auto-generated by github-mobile-reader.
415
+ ```
407
416
 
408
417
  ---
409
418
 
package/README.md CHANGED
@@ -42,7 +42,7 @@ data
42
42
  - **GitHub Action** — drop one YAML block into any repo and get auto-generated Reader docs on every PR
43
43
  - **File-by-file output** — each changed JS/TS file gets its own independent section in the output
44
44
  - **JSX/Tailwind aware** — `.jsx`/`.tsx` files get a component tree (`🎨 JSX Structure`) and a Tailwind class diff (`💅 Style Changes`) instead of one unreadable blob
45
- - **Tracks both sides of a diff** shows added _and_ removed code in separate sections
45
+ - **Symbol-level summary** instead of dumping entire code blocks, the output lists which functions/components were added, removed, or modified
46
46
  - **Conservative by design** — when a pattern is ambiguous, the library shows less rather than showing something wrong
47
47
  - **Secure by default** — token is read from `$GITHUB_TOKEN` only; no flag that leaks to shell history or `ps`
48
48
 
@@ -177,18 +177,20 @@ Token: read from `$GITHUB_TOKEN` environment variable (60 req/hr unauthenticated
177
177
 
178
178
  Each PR produces one file: `reader-output/pr-<number>.md`.
179
179
 
180
- Inside that file, every changed JS/TS file gets its own section. JSX/TSX files get two extra sections:
180
+ Inside that file, every changed JS/TS file gets its own section. Files with no detectable symbol changes are automatically skipped.
181
181
 
182
182
  ```
183
183
  # 📖 PR #42 — My Feature
184
184
 
185
185
  ## 📄 `src/App.tsx`
186
186
 
187
- ### 🧠 Logical Flow ← JS logic tree
187
+ ### 변경된 함수 / 컴포넌트
188
+ - ✅ `parseArgs()` — added
189
+ - ✏️ `processPR()` — modified
190
+ - ❌ `oldHelper()` — removed
191
+
188
192
  ### 🎨 JSX Structure ← component hierarchy (JSX/TSX only)
189
193
  ### 💅 Style Changes ← added/removed Tailwind classes (JSX/TSX only)
190
- ### ✅ Added Code
191
- ### ❌ Removed Code
192
194
  ```
193
195
 
194
196
  > **Note:** `reader-output/` is gitignored by default — the generated files are local only and not committed to your repository.
@@ -218,8 +220,9 @@ console.log(markdown);
218
220
  The easiest way to use this library is as a GitHub Action. On every pull request it will:
219
221
 
220
222
  1. Parse the diff of all changed `.js` / `.jsx` / `.ts` / `.tsx` / `.mjs` / `.cjs` files
221
- 2. Write a Reader Markdown file to `docs/reader/pr-<number>.md` inside your repo
222
- 3. Post a summary comment directly on the PR
223
+ 2. Post a Reader Markdown summary as a **PR comment** (no file commits required)
224
+
225
+ > **Branch protection compatible** — the workflow only needs `pull-requests: write` permission. It does not push any commits, so it works even with strict branch protection rules.
223
226
 
224
227
  ### Step 1 — Add the workflow file
225
228
 
@@ -233,8 +236,7 @@ on:
233
236
  types: [opened, synchronize, reopened]
234
237
 
235
238
  permissions:
236
- contents: write # commit the generated .md file
237
- pull-requests: write # post the PR comment
239
+ pull-requests: write # post PR comment
238
240
 
239
241
  jobs:
240
242
  generate-reader:
@@ -245,44 +247,45 @@ jobs:
245
247
  - name: Checkout
246
248
  uses: actions/checkout@v4
247
249
  with:
248
- fetch-depth: 0 # full history required for git diff
250
+ fetch-depth: 0
249
251
 
250
- - name: Generate Reader Markdown
251
- uses: 3rdflr/github-mobile-reader@v1
252
+ - name: Setup Node
253
+ uses: actions/setup-node@v4
252
254
  with:
253
- github_token: ${{ secrets.GITHUB_TOKEN }}
254
- base_branch: ${{ github.base_ref }}
255
- output_dir: docs/reader
255
+ node-version: '20'
256
+
257
+ - name: Generate Reader Markdown
258
+ run: npx github-mobile-reader@latest --repo ${{ github.repository }} --pr ${{ github.event.pull_request.number }} --out ./reader-output
256
259
  env:
257
- PR_NUMBER: ${{ github.event.pull_request.number }}
258
-
259
- - name: Commit Reader Markdown
260
- run: |
261
- git config user.name "github-actions[bot]"
262
- git config user.email "github-actions[bot]@users.noreply.github.com"
263
- git add docs/reader/
264
- if git diff --cached --quiet; then
265
- echo "No changes to commit"
266
- else
267
- git commit -m "docs(reader): update mobile reader for PR #${{ github.event.pull_request.number }} [skip ci]"
268
- git push
269
- fi
260
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
261
+
262
+ - name: Post PR Comment
263
+ uses: actions/github-script@v7
264
+ with:
265
+ script: |
266
+ const fs = require('fs');
267
+ const path = './reader-output/pr-${{ github.event.pull_request.number }}.md';
268
+ if (!fs.existsSync(path)) { console.log('No reader file generated.'); return; }
269
+ const body = fs.readFileSync(path, 'utf8');
270
+ const comments = await github.rest.issues.listComments({
271
+ owner: context.repo.owner, repo: context.repo.repo,
272
+ issue_number: ${{ github.event.pull_request.number }},
273
+ });
274
+ const prev = comments.data.find(c =>
275
+ c.user.login === 'github-actions[bot]' && c.body.startsWith('# 📖 PR #')
276
+ );
277
+ if (prev) await github.rest.issues.deleteComment({
278
+ owner: context.repo.owner, repo: context.repo.repo, comment_id: prev.id,
279
+ });
280
+ await github.rest.issues.createComment({
281
+ owner: context.repo.owner, repo: context.repo.repo,
282
+ issue_number: ${{ github.event.pull_request.number }}, body,
283
+ });
270
284
  ```
271
285
 
272
286
  ### Step 2 — Open a PR
273
287
 
274
- That's it. Every subsequent PR will automatically get:
275
-
276
- - A Reader Markdown file at `docs/reader/pr-<number>.md`
277
- - A comment on the PR linking to the generated file
278
-
279
- ### Action Inputs
280
-
281
- | Input | Required | Default | Description |
282
- | -------------- | -------- | ------------- | ----------------------------------- |
283
- | `github_token` | ✅ | — | Use `${{ secrets.GITHUB_TOKEN }}` |
284
- | `base_branch` | ❌ | `main` | The branch the PR is merging into |
285
- | `output_dir` | ❌ | `docs/reader` | Directory for generated `.md` files |
288
+ That's it. Every subsequent PR will automatically get a Reader Markdown comment posted by `github-actions[bot]`. The comment is updated (not duplicated) on every new push.
286
289
 
287
290
  ---
288
291
 
@@ -361,49 +364,53 @@ console.log(treeLines.join("\n"));
361
364
 
362
365
  ## Output Format
363
366
 
364
- A generated Reader Markdown document has four sections:
367
+ A generated Reader Markdown comment has the following structure:
365
368
 
366
369
  ```markdown
367
- # 📖 GitHub Reader View
370
+ # 📖 PR #42 — My Feature
368
371
 
369
- > Generated by **github-mobile-reader**
370
372
  > Repository: my-org/my-repo
371
- > Pull Request: #42
372
373
  > Commit: `a1b2c3d`
373
- > File: `src/api/users.ts`
374
+ > 변경된 JS/TS 파일: 2개
374
375
 
375
376
  ---
376
377
 
377
- ## 🧠 Logical Flow
378
- ```
378
+ ## 📄 `src/api/users.ts`
379
379
 
380
- getData()
381
- └─ filter(callback)
382
- └─ map(item → value)
383
- └─ reduce(callback)
380
+ ### 변경된 함수 / 컴포넌트
384
381
 
385
- ````
382
+ - ✅ `getUser()` — added
383
+ - ✏️ `updateUser()` — modified
384
+ - ❌ `legacyFetch()` — removed
386
385
 
387
- ## ✅ Added Code
386
+ ---
388
387
 
389
- ```typescript
390
- const result = getData()
391
- .filter(item => item.active)
392
- .map(item => item.value)
393
- .reduce((a, b) => a + b, 0)
394
- ````
388
+ ## 📄 `src/components/UserCard.tsx`
395
389
 
396
- ## Removed Code
390
+ ### 변경된 함수 / 컴포넌트
397
391
 
398
- ```typescript
399
- const result = getData().map((item) => item.value);
392
+ - ✏️ `UserCard()` — modified
393
+
394
+ ### 🎨 JSX Structure
395
+
396
+ ```
397
+ div
398
+ header
399
+ section
400
+ UserAvatar
401
+ UserName
400
402
  ```
401
403
 
402
- ---
404
+ ### 💅 Style Changes
403
405
 
404
- 🛠 Auto-generated by github-mobile-reader. Do not edit manually.
406
+ **div**
407
+ + dark:bg-gray-900 rounded-xl
408
+ - bg-white
405
409
 
406
- ````
410
+ ---
411
+
412
+ 🛠 Auto-generated by github-mobile-reader.
413
+ ```
407
414
 
408
415
  ---
409
416
 
package/dist/action.js CHANGED
@@ -32,7 +32,9 @@ function isJSXFile(filename) {
32
32
  return /\.(jsx|tsx)$/.test(filename);
33
33
  }
34
34
  function hasJSXContent(lines) {
35
- return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
35
+ return lines.some(
36
+ (l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l)
37
+ );
36
38
  }
37
39
  function isClassNameOnlyLine(line) {
38
40
  return /^className=/.test(line.trim());
@@ -40,7 +42,9 @@ function isClassNameOnlyLine(line) {
40
42
  function extractClassName(line) {
41
43
  const staticMatch = line.match(/className="([^"]*)"/);
42
44
  if (staticMatch) return staticMatch[1];
43
- const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
45
+ const ternaryMatch = line.match(
46
+ /className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/
47
+ );
44
48
  if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
45
49
  const templateMatch = line.match(/className=\{`([^`]*)`\}/);
46
50
  if (templateMatch) {
@@ -62,14 +66,16 @@ function parseClassNameChanges(addedLines, removedLines) {
62
66
  const cls = extractClassName(line);
63
67
  const comp = extractComponentFromLine(line);
64
68
  if (!cls) continue;
65
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
69
+ if (!componentMap.has(comp))
70
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
66
71
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
67
72
  }
68
73
  for (const line of removedLines.filter((l) => /className=/.test(l))) {
69
74
  const cls = extractClassName(line);
70
75
  const comp = extractComponentFromLine(line);
71
76
  if (!cls) continue;
72
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
77
+ if (!componentMap.has(comp))
78
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
73
79
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
74
80
  }
75
81
  const changes = [];
@@ -87,7 +93,8 @@ function renderStyleChanges(changes) {
87
93
  for (const change of changes) {
88
94
  lines.push(`**${change.component}**`);
89
95
  if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
90
- if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
96
+ if (change.removed.length > 0)
97
+ lines.push(` - ${change.removed.join(" ")}`);
91
98
  }
92
99
  return lines;
93
100
  }
@@ -156,8 +163,12 @@ function parseJSXToFlowTree(lines) {
156
163
  }
157
164
  function filterDiffLines(diffText) {
158
165
  const lines = diffText.split("\n");
159
- const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
160
- const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
166
+ const added = lines.filter(
167
+ (l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+"
168
+ ).map((l) => l.substring(1));
169
+ const removed = lines.filter(
170
+ (l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-"
171
+ ).map((l) => l.substring(1));
161
172
  return { added, removed };
162
173
  }
163
174
  function getIndentDepth(line) {
@@ -166,14 +177,14 @@ function getIndentDepth(line) {
166
177
  return Math.floor(match[1].length / 2);
167
178
  }
168
179
  function extractChangedSymbols(addedLines, removedLines) {
169
- const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s*)?\(/;
180
+ const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s+)?\(?|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*[a-z]\w+\s*[<(]/;
170
181
  const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
171
182
  const extract = (lines) => {
172
183
  const names = /* @__PURE__ */ new Set();
173
184
  for (const line of lines) {
174
185
  const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
175
186
  if (cm) {
176
- const name = cm[1] || cm[2];
187
+ const name = cm[1] || cm[2] || cm[3];
177
188
  if (name) names.add(name);
178
189
  }
179
190
  }
@@ -185,7 +196,10 @@ function extractChangedSymbols(addedLines, removedLines) {
185
196
  const seen = /* @__PURE__ */ new Set();
186
197
  for (const name of addedNames) {
187
198
  seen.add(name);
188
- results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
199
+ results.push({
200
+ name,
201
+ status: removedNames.has(name) ? "modified" : "added"
202
+ });
189
203
  }
190
204
  for (const name of removedNames) {
191
205
  if (!seen.has(name)) {
@@ -246,7 +260,9 @@ function generateReaderMarkdown(diffText, meta = {}) {
246
260
  sections.push("");
247
261
  }
248
262
  sections.push("---");
249
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
263
+ sections.push(
264
+ "\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually."
265
+ );
250
266
  return sections.join("\n");
251
267
  }
252
268
 
package/dist/cli.js CHANGED
@@ -32,7 +32,9 @@ function isJSXFile(filename) {
32
32
  return /\.(jsx|tsx)$/.test(filename);
33
33
  }
34
34
  function hasJSXContent(lines) {
35
- return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
35
+ return lines.some(
36
+ (l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l)
37
+ );
36
38
  }
37
39
  function isClassNameOnlyLine(line) {
38
40
  return /^className=/.test(line.trim());
@@ -40,7 +42,9 @@ function isClassNameOnlyLine(line) {
40
42
  function extractClassName(line) {
41
43
  const staticMatch = line.match(/className="([^"]*)"/);
42
44
  if (staticMatch) return staticMatch[1];
43
- const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
45
+ const ternaryMatch = line.match(
46
+ /className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/
47
+ );
44
48
  if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
45
49
  const templateMatch = line.match(/className=\{`([^`]*)`\}/);
46
50
  if (templateMatch) {
@@ -62,14 +66,16 @@ function parseClassNameChanges(addedLines, removedLines) {
62
66
  const cls = extractClassName(line);
63
67
  const comp = extractComponentFromLine(line);
64
68
  if (!cls) continue;
65
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
69
+ if (!componentMap.has(comp))
70
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
66
71
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
67
72
  }
68
73
  for (const line of removedLines.filter((l) => /className=/.test(l))) {
69
74
  const cls = extractClassName(line);
70
75
  const comp = extractComponentFromLine(line);
71
76
  if (!cls) continue;
72
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
77
+ if (!componentMap.has(comp))
78
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
73
79
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
74
80
  }
75
81
  const changes = [];
@@ -87,7 +93,8 @@ function renderStyleChanges(changes) {
87
93
  for (const change of changes) {
88
94
  lines.push(`**${change.component}**`);
89
95
  if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
90
- if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
96
+ if (change.removed.length > 0)
97
+ lines.push(` - ${change.removed.join(" ")}`);
91
98
  }
92
99
  return lines;
93
100
  }
@@ -156,8 +163,12 @@ function parseJSXToFlowTree(lines) {
156
163
  }
157
164
  function filterDiffLines(diffText) {
158
165
  const lines = diffText.split("\n");
159
- const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
160
- const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
166
+ const added = lines.filter(
167
+ (l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+"
168
+ ).map((l) => l.substring(1));
169
+ const removed = lines.filter(
170
+ (l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-"
171
+ ).map((l) => l.substring(1));
161
172
  return { added, removed };
162
173
  }
163
174
  function getIndentDepth(line) {
@@ -166,14 +177,14 @@ function getIndentDepth(line) {
166
177
  return Math.floor(match[1].length / 2);
167
178
  }
168
179
  function extractChangedSymbols(addedLines, removedLines) {
169
- const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s*)?\(/;
180
+ const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s+)?\(?|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*[a-z]\w+\s*[<(]/;
170
181
  const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
171
182
  const extract = (lines) => {
172
183
  const names = /* @__PURE__ */ new Set();
173
184
  for (const line of lines) {
174
185
  const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
175
186
  if (cm) {
176
- const name = cm[1] || cm[2];
187
+ const name = cm[1] || cm[2] || cm[3];
177
188
  if (name) names.add(name);
178
189
  }
179
190
  }
@@ -185,7 +196,10 @@ function extractChangedSymbols(addedLines, removedLines) {
185
196
  const seen = /* @__PURE__ */ new Set();
186
197
  for (const name of addedNames) {
187
198
  seen.add(name);
188
- results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
199
+ results.push({
200
+ name,
201
+ status: removedNames.has(name) ? "modified" : "added"
202
+ });
189
203
  }
190
204
  for (const name of removedNames) {
191
205
  if (!seen.has(name)) {
@@ -246,7 +260,9 @@ function generateReaderMarkdown(diffText, meta = {}) {
246
260
  sections.push("");
247
261
  }
248
262
  sections.push("---");
249
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
263
+ sections.push(
264
+ "\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually."
265
+ );
250
266
  return sections.join("\n");
251
267
  }
252
268
 
package/dist/index.d.mts CHANGED
@@ -14,7 +14,7 @@ declare enum Priority {
14
14
  OTHER = 5
15
15
  }
16
16
  interface FlowNode {
17
- type: 'root' | 'chain' | 'condition' | 'loop' | 'function' | 'call';
17
+ type: "root" | "chain" | "condition" | "loop" | "function" | "call";
18
18
  name: string;
19
19
  children: FlowNode[];
20
20
  depth: number;
@@ -74,7 +74,7 @@ declare function parseDiffToLogicalFlow(diffText: string): ParseResult;
74
74
  */
75
75
  declare function extractChangedSymbols(addedLines: string[], removedLines: string[]): {
76
76
  name: string;
77
- status: 'added' | 'removed' | 'modified';
77
+ status: "added" | "removed" | "modified";
78
78
  }[];
79
79
  /**
80
80
  * Render JSX tree as a single compact line: div > header > button(onClick)
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ declare enum Priority {
14
14
  OTHER = 5
15
15
  }
16
16
  interface FlowNode {
17
- type: 'root' | 'chain' | 'condition' | 'loop' | 'function' | 'call';
17
+ type: "root" | "chain" | "condition" | "loop" | "function" | "call";
18
18
  name: string;
19
19
  children: FlowNode[];
20
20
  depth: number;
@@ -74,7 +74,7 @@ declare function parseDiffToLogicalFlow(diffText: string): ParseResult;
74
74
  */
75
75
  declare function extractChangedSymbols(addedLines: string[], removedLines: string[]): {
76
76
  name: string;
77
- status: 'added' | 'removed' | 'modified';
77
+ status: "added" | "removed" | "modified";
78
78
  }[];
79
79
  /**
80
80
  * Render JSX tree as a single compact line: div > header > button(onClick)
package/dist/index.js CHANGED
@@ -54,7 +54,9 @@ function isJSXFile(filename) {
54
54
  return /\.(jsx|tsx)$/.test(filename);
55
55
  }
56
56
  function hasJSXContent(lines) {
57
- return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
57
+ return lines.some(
58
+ (l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l)
59
+ );
58
60
  }
59
61
  function isClassNameOnlyLine(line) {
60
62
  return /^className=/.test(line.trim());
@@ -62,7 +64,9 @@ function isClassNameOnlyLine(line) {
62
64
  function extractClassName(line) {
63
65
  const staticMatch = line.match(/className="([^"]*)"/);
64
66
  if (staticMatch) return staticMatch[1];
65
- const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
67
+ const ternaryMatch = line.match(
68
+ /className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/
69
+ );
66
70
  if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
67
71
  const templateMatch = line.match(/className=\{`([^`]*)`\}/);
68
72
  if (templateMatch) {
@@ -84,14 +88,16 @@ function parseClassNameChanges(addedLines, removedLines) {
84
88
  const cls = extractClassName(line);
85
89
  const comp = extractComponentFromLine(line);
86
90
  if (!cls) continue;
87
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
91
+ if (!componentMap.has(comp))
92
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
88
93
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
89
94
  }
90
95
  for (const line of removedLines.filter((l) => /className=/.test(l))) {
91
96
  const cls = extractClassName(line);
92
97
  const comp = extractComponentFromLine(line);
93
98
  if (!cls) continue;
94
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
99
+ if (!componentMap.has(comp))
100
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
95
101
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
96
102
  }
97
103
  const changes = [];
@@ -109,7 +115,8 @@ function renderStyleChanges(changes) {
109
115
  for (const change of changes) {
110
116
  lines.push(`**${change.component}**`);
111
117
  if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
112
- if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
118
+ if (change.removed.length > 0)
119
+ lines.push(` - ${change.removed.join(" ")}`);
113
120
  }
114
121
  return lines;
115
122
  }
@@ -178,8 +185,12 @@ function parseJSXToFlowTree(lines) {
178
185
  }
179
186
  function filterDiffLines(diffText) {
180
187
  const lines = diffText.split("\n");
181
- const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
182
- const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
188
+ const added = lines.filter(
189
+ (l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+"
190
+ ).map((l) => l.substring(1));
191
+ const removed = lines.filter(
192
+ (l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-"
193
+ ).map((l) => l.substring(1));
183
194
  return { added, removed };
184
195
  }
185
196
  function normalizeCode(lines) {
@@ -360,14 +371,14 @@ function parseDiffToLogicalFlow(diffText) {
360
371
  };
361
372
  }
362
373
  function extractChangedSymbols(addedLines, removedLines) {
363
- const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s*)?\(/;
374
+ const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s+)?\(?|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*[a-z]\w+\s*[<(]/;
364
375
  const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
365
376
  const extract = (lines) => {
366
377
  const names = /* @__PURE__ */ new Set();
367
378
  for (const line of lines) {
368
379
  const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
369
380
  if (cm) {
370
- const name = cm[1] || cm[2];
381
+ const name = cm[1] || cm[2] || cm[3];
371
382
  if (name) names.add(name);
372
383
  }
373
384
  }
@@ -379,7 +390,10 @@ function extractChangedSymbols(addedLines, removedLines) {
379
390
  const seen = /* @__PURE__ */ new Set();
380
391
  for (const name of addedNames) {
381
392
  seen.add(name);
382
- results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
393
+ results.push({
394
+ name,
395
+ status: removedNames.has(name) ? "modified" : "added"
396
+ });
383
397
  }
384
398
  for (const name of removedNames) {
385
399
  if (!seen.has(name)) {
@@ -440,7 +454,9 @@ function generateReaderMarkdown(diffText, meta = {}) {
440
454
  sections.push("");
441
455
  }
442
456
  sections.push("---");
443
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
457
+ sections.push(
458
+ "\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually."
459
+ );
444
460
  return sections.join("\n");
445
461
  }
446
462
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -11,7 +11,9 @@ function isJSXFile(filename) {
11
11
  return /\.(jsx|tsx)$/.test(filename);
12
12
  }
13
13
  function hasJSXContent(lines) {
14
- return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
14
+ return lines.some(
15
+ (l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l)
16
+ );
15
17
  }
16
18
  function isClassNameOnlyLine(line) {
17
19
  return /^className=/.test(line.trim());
@@ -19,7 +21,9 @@ function isClassNameOnlyLine(line) {
19
21
  function extractClassName(line) {
20
22
  const staticMatch = line.match(/className="([^"]*)"/);
21
23
  if (staticMatch) return staticMatch[1];
22
- const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
24
+ const ternaryMatch = line.match(
25
+ /className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/
26
+ );
23
27
  if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
24
28
  const templateMatch = line.match(/className=\{`([^`]*)`\}/);
25
29
  if (templateMatch) {
@@ -41,14 +45,16 @@ function parseClassNameChanges(addedLines, removedLines) {
41
45
  const cls = extractClassName(line);
42
46
  const comp = extractComponentFromLine(line);
43
47
  if (!cls) continue;
44
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
48
+ if (!componentMap.has(comp))
49
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
45
50
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
46
51
  }
47
52
  for (const line of removedLines.filter((l) => /className=/.test(l))) {
48
53
  const cls = extractClassName(line);
49
54
  const comp = extractComponentFromLine(line);
50
55
  if (!cls) continue;
51
- if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
56
+ if (!componentMap.has(comp))
57
+ componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
52
58
  cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
53
59
  }
54
60
  const changes = [];
@@ -66,7 +72,8 @@ function renderStyleChanges(changes) {
66
72
  for (const change of changes) {
67
73
  lines.push(`**${change.component}**`);
68
74
  if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
69
- if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
75
+ if (change.removed.length > 0)
76
+ lines.push(` - ${change.removed.join(" ")}`);
70
77
  }
71
78
  return lines;
72
79
  }
@@ -135,8 +142,12 @@ function parseJSXToFlowTree(lines) {
135
142
  }
136
143
  function filterDiffLines(diffText) {
137
144
  const lines = diffText.split("\n");
138
- const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
139
- const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
145
+ const added = lines.filter(
146
+ (l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+"
147
+ ).map((l) => l.substring(1));
148
+ const removed = lines.filter(
149
+ (l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-"
150
+ ).map((l) => l.substring(1));
140
151
  return { added, removed };
141
152
  }
142
153
  function normalizeCode(lines) {
@@ -317,14 +328,14 @@ function parseDiffToLogicalFlow(diffText) {
317
328
  };
318
329
  }
319
330
  function extractChangedSymbols(addedLines, removedLines) {
320
- const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s*)?\(/;
331
+ const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s+)?\(?|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*[a-z]\w+\s*[<(]/;
321
332
  const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
322
333
  const extract = (lines) => {
323
334
  const names = /* @__PURE__ */ new Set();
324
335
  for (const line of lines) {
325
336
  const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
326
337
  if (cm) {
327
- const name = cm[1] || cm[2];
338
+ const name = cm[1] || cm[2] || cm[3];
328
339
  if (name) names.add(name);
329
340
  }
330
341
  }
@@ -336,7 +347,10 @@ function extractChangedSymbols(addedLines, removedLines) {
336
347
  const seen = /* @__PURE__ */ new Set();
337
348
  for (const name of addedNames) {
338
349
  seen.add(name);
339
- results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
350
+ results.push({
351
+ name,
352
+ status: removedNames.has(name) ? "modified" : "added"
353
+ });
340
354
  }
341
355
  for (const name of removedNames) {
342
356
  if (!seen.has(name)) {
@@ -397,7 +411,9 @@ function generateReaderMarkdown(diffText, meta = {}) {
397
411
  sections.push("");
398
412
  }
399
413
  sections.push("---");
400
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
414
+ sections.push(
415
+ "\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually."
416
+ );
401
417
  return sections.join("\n");
402
418
  }
403
419
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-mobile-reader",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Transform git diffs into mobile-friendly Markdown — no more horizontal scrolling when reviewing code on your phone.",
5
5
  "keywords": [
6
6
  "github",