github-mobile-reader 0.1.0 β†’ 0.1.1

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 ADDED
@@ -0,0 +1,517 @@
1
+ # πŸ“– github-mobile-reader
2
+
3
+ > `github-mobile-reader`λŠ” git diffλ₯Ό κΉ”λ”ν•˜κ²Œ μ„Έλ‘œ 슀크둀둜 읽을 수 μžˆλŠ” Markdown λ¬Έμ„œλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€ β€” 더 이상 쒌우 ν•€μΉ˜μ€Œμ΄λ‚˜ κ°€λ‘œ μŠ€μ™€μ΄ν”„λŠ” ν•„μš” μ—†μŠ΅λ‹ˆλ‹€.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/github-mobile-reader.svg)](https://www.npmjs.com/package/github-mobile-reader)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js β‰₯ 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
8
+
9
+ > μ˜μ–΄ λ¬Έμ„œλŠ” [README.md](./README.md)μ—μ„œ ν™•μΈν•˜μ„Έμš”.
10
+
11
+ ---
12
+
13
+ ## 문제 상황
14
+
15
+ GitHub의 λͺ¨λ°”일 μ›Ή λ·°λŠ” μ½”λ“œλ₯Ό κ³ μ • λ„ˆλΉ„μ˜ λͺ¨λ…ΈμŠ€νŽ˜μ΄μŠ€ λΈ”λ‘μœΌλ‘œ λ Œλ”λ§ν•©λ‹ˆλ‹€. κΈ΄ 쀄은 κ°€λ‘œ μŠ€ν¬λ‘€μ„ μš”κ΅¬ν•˜κ³ , 깊게 μ€‘μ²©λœ λ‘œμ§μ€ ν•œλˆˆμ— νŒŒμ•…μ΄ λΆˆκ°€λŠ₯ν•˜λ©°, μΆœν‡΄κ·Ό μ§€ν•˜μ² μ—μ„œ PR 리뷰λ₯Ό ν•˜λŠ” 건 사싀상 λΆˆκ°€λŠ₯에 κ°€κΉμŠ΅λ‹ˆλ‹€.
16
+
17
+ ## ν•΄κ²°μ±…
18
+
19
+ `github-mobile-reader`λŠ” git diffλ₯Ό νŒŒμ‹±ν•΄μ„œ **Logical Flow** β€” λ‹¨μˆœνžˆ μ–΄λ–€ λ¬Έμžκ°€ λ°”λ€Œμ—ˆλŠ”μ§€κ°€ μ•„λ‹ˆλΌ *μ½”λ“œκ°€ 무엇을 ν•˜λŠ”μ§€*λ₯Ό λ³΄μ—¬μ£ΌλŠ” κ°„κ²°ν•œ 트리 β€” λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. 결과물은 μ–΄λ–€ ν™”λ©΄ λ„ˆλΉ„μ—μ„œλ„ μœ„μ—μ„œ μ•„λž˜λ‘œ μ½νžˆλŠ” Markdown λ¬Έμ„œμž…λ‹ˆλ‹€.
20
+
21
+ **Before** (κΈ°μ‘΄ diff, λͺ¨λ°”일 μ›Ή):
22
+
23
+ ```
24
+ ← μŠ€μ™€μ΄ν”„ β†’ μŠ€μ™€μ΄ν”„ β†’ μŠ€μ™€μ΄ν”„ β†’
25
+ + const result = data.map(item => item.value).filter(v => v > 10).reduce((a,b) => a+b, 0)
26
+ ```
27
+
28
+ **After** (Reader Markdown):
29
+
30
+ ```
31
+ data
32
+ └─ map(item β†’ value)
33
+ └─ filter(callback)
34
+ └─ reduce(callback)
35
+ ```
36
+
37
+ ---
38
+
39
+ ## μ£Όμš” κΈ°λŠ₯
40
+
41
+ - **μ˜μ‘΄μ„± 제둜 μ½”μ–΄** β€” νŒŒμ„œλŠ” Node.js β‰₯ 18이 μžˆλŠ” μ–΄λ””μ„œλ‚˜ λ™μž‘ν•©λ‹ˆλ‹€
42
+ - **이쀑 좜λ ₯ 포맷** β€” CJS (`require`)와 ESM (`import`) λͺ¨λ‘ 지원, TypeScript νƒ€μž… 포함
43
+ - **GitHub Action** β€” λ ˆν¬μ— YAML 파일 ν•˜λ‚˜λ§Œ μΆ”κ°€ν•˜λ©΄ PRλ§ˆλ‹€ Reader λ¬Έμ„œκ°€ μžλ™ μƒμ„±λ©λ‹ˆλ‹€
44
+ - **μ–‘λ°©ν–₯ diff 좔적** β€” μΆ”κ°€λœ μ½”λ“œμ™€ μ‚­μ œλœ μ½”λ“œλ₯Ό 각각 별도 μ„Ήμ…˜μœΌλ‘œ ν‘œμ‹œ
45
+ - **보수적 섀계** β€” νŒ¨ν„΄μ΄ μ• λ§€ν•  λ•ŒλŠ” 잘λͺ»λœ 정보λ₯Ό λ³΄μ—¬μ£ΌλŠ” λŒ€μ‹  덜 λ³΄μ—¬μ€λ‹ˆλ‹€
46
+
47
+ ---
48
+
49
+ ## λͺ©μ°¨
50
+
51
+ 1. [λΉ λ₯Έ μ‹œμž‘](#λΉ λ₯Έ-μ‹œμž‘)
52
+ 2. [μ–Έμ–΄ 지원](#μ–Έμ–΄-지원)
53
+ 3. [GitHub Action (ꢌμž₯)](#github-action-ꢌμž₯)
54
+ 4. [npm 라이브러리 μ‚¬μš©λ²•](#npm-라이브러리-μ‚¬μš©λ²•)
55
+ 5. [좜λ ₯ ν˜•μ‹](#좜λ ₯-ν˜•μ‹)
56
+ 6. [API 레퍼런슀](#api-레퍼런슀)
57
+ 7. [νŒŒμ„œ λ™μž‘ 원리](#νŒŒμ„œ-λ™μž‘-원리)
58
+ 8. [κΈ°μ—¬ν•˜κΈ°](#κΈ°μ—¬ν•˜κΈ°)
59
+ 9. [λΌμ΄μ„ μŠ€](#λΌμ΄μ„ μŠ€)
60
+
61
+ ---
62
+
63
+ ## λΉ λ₯Έ μ‹œμž‘
64
+
65
+ ```bash
66
+ npm install github-mobile-reader
67
+ ```
68
+
69
+ ```ts
70
+ import { generateReaderMarkdown } from "github-mobile-reader";
71
+ import { execSync } from "child_process";
72
+
73
+ const diff = execSync("git diff HEAD~1", { encoding: "utf8" });
74
+ const markdown = generateReaderMarkdown(diff, { file: "src/utils.ts" });
75
+
76
+ console.log(markdown);
77
+ ```
78
+
79
+ ---
80
+
81
+ ## μ–Έμ–΄ 지원
82
+
83
+ νŒŒμ„œλŠ” μ •κ·œμ‹ 기반 νŒ¨ν„΄ 맀칭으둜 λ™μž‘ν•˜λ―€λ‘œ κΈ°μˆ μ μœΌλ‘œλŠ” μ–΄λ–€ μ–Έμ–΄μ˜ diff도 μž…λ ₯받을 수 μžˆμŠ΅λ‹ˆλ‹€. λ‹€λ§Œ 감지 νŒ¨ν„΄μ΄ JavaScript/TypeScript 문법에 맞좰 μ„€κ³„λ˜μ–΄ μžˆμ–΄ **Logical Flow 좜λ ₯ ν’ˆμ§ˆμ΄ μ–Έμ–΄λ§ˆλ‹€ λ‹€λ¦…λ‹ˆλ‹€**.
84
+
85
+ ### ν˜„μž¬ 지원 ν˜„ν™© (v0.1)
86
+
87
+ | μ–Έμ–΄ | ν™•μž₯자 | ν’ˆμ§ˆ | λΉ„κ³  |
88
+ | ----------------------- | ------------------------- | :------------: | --------------------------------------------------------------------------- |
89
+ | **JavaScript** | `.js` `.mjs` `.cjs` | βœ… μ™„μ „ | νŒŒμ„œμ˜ κΈ°μ€€ μ–Έμ–΄ |
90
+ | **TypeScript** | `.ts` | βœ… μ™„μ „ | JS μƒμœ„ μ§‘ν•© β€” λͺ¨λ“  νŒ¨ν„΄ 적용 |
91
+ | **React JSX** | `.jsx` | βœ… μ™„μ „ | JS와 λ™μΌν•œ 문법 |
92
+ | **React TSX** | `.tsx` | βœ… μ™„μ „ | TS와 λ™μΌν•œ 문법 |
93
+ | **Next.js** | `.js` `.ts` `.jsx` `.tsx` | βœ… μ™„μ „ | JS/TS μœ„μ—μ„œ λ™μž‘ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬ |
94
+ | **Java** | `.java` | ⚠️ λΆ€λΆ„ (~55%) | `if/for/while`κ³Ό 체이닝은 λ™μž‘; ν•¨μˆ˜ μ„ μ–Έ 감지 μ‹€νŒ¨ (`const/let/var` μ—†μŒ) |
95
+ | **C#** | `.cs` | ⚠️ λΆ€λΆ„ (~35%) | LINQ 체이닝(`.Where().Select()`)은 λ™μž‘; `using`/`namespace`/`class` 미감지 |
96
+ | **C** | `.c` `.h` | ❌ μ΅œμ†Œ (~15%) | λ§€μΉ­ ν‚€μ›Œλ“œ μ—†μŒ; 포인터 문법(`->`, `*`) 미지원 |
97
+ | **Python, Go, Rust λ“±** | β€” | πŸ”œ μ˜ˆμ • | μ•„λž˜ λ‘œλ“œλ§΅ μ°Έκ³  |
98
+
99
+ > **μ°Έκ³ :** Java, C#, C νŒŒμΌμ€ 기본적으둜 GitHub Actionμ—μ„œ μ²˜λ¦¬λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
100
+ > Action은 `.js .jsx .ts .tsx .mjs .cjs` 파일만 μŠ€μΊ”ν•©λ‹ˆλ‹€ ([`src/action.ts` 66번째 쀄](src/action.ts)).
101
+ > λ‹€λ₯Έ μ–Έμ–΄λ₯Ό μ²˜λ¦¬ν•˜λ €λ©΄ μ»€μŠ€ν…€ μ–΄λŒ‘ν„°κ°€ ν•„μš”ν•©λ‹ˆλ‹€ ([κΈ°μ—¬ν•˜κΈ°](#κΈ°μ—¬ν•˜κΈ°) μ°Έκ³ ).
102
+
103
+ ### JS/TS/React/Next.jsκ°€ μ™„μ „ μ§€μ›λ˜λŠ” 이유
104
+
105
+ λ„€ κ°€μ§€ λͺ¨λ‘ λ™μΌν•œ 기반 문법을 κ³΅μœ ν•©λ‹ˆλ‹€. νŒŒμ„œκ°€ μΈμ‹ν•˜λŠ” 것:
106
+
107
+ - **λ©”μ„œλ“œ 체이닝** β€” `)`λ‚˜ `}`둜 λλ‚˜λŠ” 쀄 λ‹€μŒμ— `.`으둜 μ‹œμž‘ν•˜λŠ” 쀄
108
+ ```ts
109
+ data
110
+ .filter((item) => item.active) // P1 μ²΄μ΄λ‹μœΌλ‘œ 감지
111
+ .map((item) => item.value); // P1 μ²΄μ΄λ‹μœΌλ‘œ 감지
112
+ ```
113
+ - **ν•¨μˆ˜ μ„ μ–Έ** β€” `const`, `let`, `var`, `function`, `async`
114
+ - **쑰건문** β€” `if / else / switch`
115
+ - **반볡문** β€” `for / while`
116
+ - **λ…Έμ΄μ¦ˆ 필터링** β€” `import`, `export`, `type`, `interface`, `console.log`λŠ” μžλ™μœΌλ‘œ 제거
117
+
118
+ ### C / C# / Javaκ°€ μ œν•œμ μΈ 이유
119
+
120
+ 이 언어듀은 μœ„ νŒ¨ν„΄μ— λŒ€ν•΄ λ‹€λ₯Έ ν‘œκΈ° 방식을 μ‚¬μš©ν•©λ‹ˆλ‹€:
121
+
122
+ | κ°œλ… | JS/TS (βœ… 감지됨) | Java / C# / C (❌ 미감지) |
123
+ | ------------- | ---------------------- | -------------------------------- |
124
+ | λ³€μˆ˜ μ„ μ–Έ | `const x = …` | `int x = …` / `String x = …` |
125
+ | ν™”μ‚΄ν‘œ 콜백 | `x => x.value` | μ–Έμ–΄λ§ˆλ‹€ λžŒλ‹€ 문법 닀름 |
126
+ | λ…Έμ΄μ¦ˆ import | `import` / `export` | `using` / `#include` / `package` |
127
+ | 비동기 ν•¨μˆ˜ | `async function foo()` | `async Task<T> Foo()` |
128
+
129
+ ### λ‘œλ“œλ§΅ β€” Language Adapter μ‹œμŠ€ν…œ (v0.2)
130
+
131
+ μΆ”κ°€ μ–Έμ–΄ 지원을 μœ„ν•΄ **Language Adapter** μ•„ν‚€ν…μ²˜κ°€ κ³„νšλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:
132
+
133
+ ```
134
+ src/languages/
135
+ β”œβ”€β”€ base.adapter.ts ← 곡톡 μΈν„°νŽ˜μ΄μŠ€
136
+ β”œβ”€β”€ js-ts.adapter.ts ← ν˜„μž¬ 둜직 (parser.tsμ—μ„œ 뢄리)
137
+ β”œβ”€β”€ java.adapter.ts ← public/private/void μ„ μ–Έ, Stream 체이닝
138
+ └── csharp.adapter.ts ← using/namespace, LINQ 체이닝
139
+ ```
140
+
141
+ 각 μ–΄λŒ‘ν„°κ°€ μ œκ³΅ν•˜λŠ” 것:
142
+
143
+ - 지원 파일 ν™•μž₯자 λͺ©λ‘
144
+ - ν•¨μˆ˜ μ„ μ–Έ 감지 νŒ¨ν„΄
145
+ - λ¬΄μ‹œν•  ν‚€μ›Œλ“œ λͺ©λ‘ (λ…Έμ΄μ¦ˆ)
146
+ - 체이닝 ν‘œκΈ° 방식 (점(`.`) vs. ν™”μ‚΄ν‘œ(`->`))
147
+
148
+ μ–Έμ–΄ μ–΄λŒ‘ν„°λ₯Ό κΈ°μ—¬ν•˜κ³  μ‹Άλ‹€λ©΄ [κΈ°μ—¬ν•˜κΈ°](#κΈ°μ—¬ν•˜κΈ°)λ₯Ό ν™•μΈν•˜μ„Έμš”.
149
+
150
+ ---
151
+
152
+ ## GitHub Action (ꢌμž₯)
153
+
154
+ 이 라이브러리λ₯Ό μ‚¬μš©ν•˜λŠ” κ°€μž₯ μ‰¬μš΄ λ°©λ²•μž…λ‹ˆλ‹€. λ§€ PRλ§ˆλ‹€ μžλ™μœΌλ‘œ:
155
+
156
+ 1. λ³€κ²½λœ `.js` / `.ts` 파일의 diffλ₯Ό νŒŒμ‹±
157
+ 2. `docs/reader/pr-<번호>.md` νŒŒμΌμ„ λ ˆν¬μ— μ €μž₯
158
+ 3. PR에 μš”μ•½ μ½”λ©˜νŠΈλ₯Ό μžλ™μœΌλ‘œ λ‹¬μ•„μ€λ‹ˆλ‹€
159
+
160
+ ### Step 1 β€” μ›Œν¬ν”Œλ‘œμš° 파일 μΆ”κ°€
161
+
162
+ λ ˆν¬μ— `.github/workflows/mobile-reader.yml`을 λ§Œλ“€μ–΄ μ£Όμ„Έμš”:
163
+
164
+ ```yaml
165
+ name: πŸ“– Mobile Reader
166
+
167
+ on:
168
+ pull_request:
169
+ types: [opened, synchronize, reopened]
170
+
171
+ permissions:
172
+ contents: write # .md 파일 컀밋
173
+ pull-requests: write # PR μ½”λ©˜νŠΈ μž‘μ„±
174
+
175
+ jobs:
176
+ generate-reader:
177
+ name: Generate Mobile Reader View
178
+ runs-on: ubuntu-latest
179
+
180
+ steps:
181
+ - name: Checkout
182
+ uses: actions/checkout@v4
183
+ with:
184
+ fetch-depth: 0 # git diff에 전체 νžˆμŠ€ν† λ¦¬ ν•„μš”
185
+
186
+ - name: Generate Reader Markdown
187
+ uses: 3rdflr/github-mobile-reader@v1
188
+ with:
189
+ github_token: ${{ secrets.GITHUB_TOKEN }}
190
+ base_branch: ${{ github.base_ref }}
191
+ output_dir: docs/reader
192
+ env:
193
+ PR_NUMBER: ${{ github.event.pull_request.number }}
194
+
195
+ - name: Commit Reader Markdown
196
+ run: |
197
+ git config user.name "github-actions[bot]"
198
+ git config user.email "github-actions[bot]@users.noreply.github.com"
199
+ git add docs/reader/
200
+ if git diff --cached --quiet; then
201
+ echo "변경사항 μ—†μŒ"
202
+ else
203
+ git commit -m "docs(reader): PR #${{ github.event.pull_request.number }} λͺ¨λ°”일 리더 μ—…λ°μ΄νŠΈ [skip ci]"
204
+ git push
205
+ fi
206
+ ```
207
+
208
+ ### Step 2 β€” PR μ—΄κΈ°
209
+
210
+ 이게 μ „λΆ€μž…λ‹ˆλ‹€. 이후 λͺ¨λ“  PR에 μžλ™μœΌλ‘œ:
211
+
212
+ - `docs/reader/pr-<번호>.md` 파일 생성
213
+ - μƒμ„±λœ 파일 링크가 λ‹΄κΈ΄ PR μ½”λ©˜νŠΈ μžλ™ κ²Œμ‹œ
214
+
215
+ ### Action μž…λ ₯κ°’
216
+
217
+ | μž…λ ₯κ°’ | ν•„μˆ˜ | κΈ°λ³Έκ°’ | μ„€λͺ… |
218
+ | -------------- | ---- | ------------- | ---------------------------------- |
219
+ | `github_token` | βœ… | β€” | `${{ secrets.GITHUB_TOKEN }}` μ‚¬μš© |
220
+ | `base_branch` | ❌ | `main` | PR이 λ¨Έμ§€λ˜λŠ” λŒ€μƒ 브랜치 |
221
+ | `output_dir` | ❌ | `docs/reader` | μƒμ„±λœ `.md` 파일 μ €μž₯ 경둜 |
222
+
223
+ ---
224
+
225
+ ## npm 라이브러리 μ‚¬μš©λ²•
226
+
227
+ CI 슀크립트, μ»€μŠ€ν…€ 봇, 둜컬 도ꡬ λ“± λͺ¨λ“  Node.js ν”„λ‘œμ νŠΈμ—μ„œ 라이브러리둜 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
228
+
229
+ ### μ„€μΉ˜
230
+
231
+ ```bash
232
+ # npm
233
+ npm install github-mobile-reader
234
+
235
+ # pnpm
236
+ pnpm add github-mobile-reader
237
+
238
+ # yarn
239
+ yarn add github-mobile-reader
240
+ ```
241
+
242
+ ### CommonJS
243
+
244
+ ```js
245
+ const { generateReaderMarkdown } = require("github-mobile-reader");
246
+ ```
247
+
248
+ ### ESM / TypeScript
249
+
250
+ ```ts
251
+ import {
252
+ generateReaderMarkdown,
253
+ parseDiffToLogicalFlow,
254
+ } from "github-mobile-reader";
255
+ ```
256
+
257
+ ### κΈ°λ³Έ μ‚¬μš© μ˜ˆμ‹œ
258
+
259
+ ```ts
260
+ import { generateReaderMarkdown } from "github-mobile-reader";
261
+ import { execSync } from "child_process";
262
+ import { writeFileSync } from "fs";
263
+
264
+ // λ§ˆμ§€λ§‰ μ»€λ°‹μ˜ diff κ°€μ Έμ˜€κΈ°
265
+ const diff = execSync("git diff HEAD~1 HEAD", { encoding: "utf8" });
266
+
267
+ // 메타데이터와 ν•¨κ»˜ Reader Markdown 생성
268
+ const markdown = generateReaderMarkdown(diff, {
269
+ pr: "42",
270
+ commit: "a1b2c3d",
271
+ file: "src/api/users.ts",
272
+ repo: "my-org/my-repo",
273
+ });
274
+
275
+ // 파일 μ €μž₯ λ˜λŠ” Slack / Discord / GitHub에 κ²Œμ‹œ
276
+ writeFileSync("reader.md", markdown, "utf8");
277
+ ```
278
+
279
+ ### μ €μˆ˜μ€€ API μ˜ˆμ‹œ
280
+
281
+ 트리 ꡬ쑰만 ν•„μš”ν•œ 경우 (예: μ»€μŠ€ν…€ λ Œλ”λŸ¬ μ œμž‘):
282
+
283
+ ```ts
284
+ import { parseDiffToLogicalFlow, renderFlowTree } from "github-mobile-reader";
285
+
286
+ const { root, rawCode, removedCode } = parseDiffToLogicalFlow(diff);
287
+
288
+ // root β†’ FlowNode[] (논리 트리)
289
+ // rawCode β†’ string (μΆ”κ°€λœ 쀄, μ€„λ°”κΏˆμœΌλ‘œ μ—°κ²°)
290
+ // removedCode β†’ string (μ‚­μ œλœ 쀄, μ€„λ°”κΏˆμœΌλ‘œ μ—°κ²°)
291
+
292
+ const treeLines = renderFlowTree(root);
293
+ console.log(treeLines.join("\n"));
294
+ ```
295
+
296
+ ---
297
+
298
+ ## 좜λ ₯ ν˜•μ‹
299
+
300
+ μƒμ„±λœ Reader Markdown λ¬Έμ„œλŠ” λ„€ 개의 μ„Ήμ…˜μœΌλ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€:
301
+
302
+ ````markdown
303
+ # πŸ“– GitHub Reader View
304
+
305
+ > Generated by **github-mobile-reader**
306
+ > Repository: my-org/my-repo
307
+ > Pull Request: #42
308
+ > Commit: `a1b2c3d`
309
+ > File: `src/api/users.ts`
310
+
311
+ ---
312
+
313
+ ## 🧠 Logical Flow
314
+
315
+ ```
316
+ getData()
317
+ └─ filter(callback)
318
+ └─ map(item β†’ value)
319
+ └─ reduce(callback)
320
+ ```
321
+
322
+ ## βœ… Added Code
323
+
324
+ ```typescript
325
+ const result = getData()
326
+ .filter((item) => item.active)
327
+ .map((item) => item.value)
328
+ .reduce((a, b) => a + b, 0);
329
+ ```
330
+
331
+ ## ❌ Removed Code
332
+
333
+ ```typescript
334
+ const result = getData().map((item) => item.value);
335
+ ```
336
+
337
+ ---
338
+
339
+ πŸ›  Auto-generated by github-mobile-reader. Do not edit manually.
340
+ ````
341
+
342
+ ---
343
+
344
+ ## API 레퍼런슀
345
+
346
+ ### `generateReaderMarkdown(diffText, meta?)`
347
+
348
+ 메인 μ§„μž…μ . μ›μ‹œ git diff λ¬Έμžμ—΄μ„ νŒŒμ‹±ν•΄μ„œ μ™„μ„±λœ Reader Markdown λ¬Έμ„œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
349
+
350
+ | νŒŒλΌλ―Έν„° | νƒ€μž… | μ„€λͺ… |
351
+ | ------------- | --------- | ----------------------------- |
352
+ | `diffText` | `string` | `git diff`의 μ›μ‹œ 좜λ ₯ |
353
+ | `meta.pr` | `string?` | PR 번호 |
354
+ | `meta.commit` | `string?` | 컀밋 SHA |
355
+ | `meta.file` | `string?` | 헀더에 ν‘œμ‹œν•  파일λͺ… |
356
+ | `meta.repo` | `string?` | `owner/repo` ν˜•μ‹μ˜ 레포 이름 |
357
+
358
+ **λ°˜ν™˜κ°’:** `string` β€” μ™„μ„±λœ Markdown λ¬Έμ„œ
359
+
360
+ ---
361
+
362
+ ### `parseDiffToLogicalFlow(diffText)`
363
+
364
+ λ Œλ”λ§ 없이 diffλ₯Ό κ΅¬μ‘°ν™”λœ 결과둜 νŒŒμ‹±ν•©λ‹ˆλ‹€.
365
+
366
+ **λ°˜ν™˜κ°’:** `ParseResult`
367
+
368
+ ```ts
369
+ interface ParseResult {
370
+ root: FlowNode[]; // 논리 트리 (μΆ”κ°€λœ 쀄)
371
+ rawCode: string; // μΆ”κ°€λœ 쀄 (\n으둜 μ—°κ²°)
372
+ removedCode: string; // μ‚­μ œλœ 쀄 (\n으둜 μ—°κ²°)
373
+ }
374
+ ```
375
+
376
+ ---
377
+
378
+ ### `renderFlowTree(nodes, indent?)`
379
+
380
+ `FlowNode[]` 트리λ₯Ό Markdown μ•ˆμ „ν•œ ν…μŠ€νŠΈ 쀄 λ°°μ—΄λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
381
+
382
+ ```ts
383
+ const lines = renderFlowTree(root);
384
+ // [ 'getData()', ' └─ filter(callback)', ' └─ map(item β†’ value)' ]
385
+ ```
386
+
387
+ ---
388
+
389
+ ### `FlowNode`
390
+
391
+ ```ts
392
+ interface FlowNode {
393
+ type: "root" | "chain" | "condition" | "loop" | "function" | "call";
394
+ name: string;
395
+ children: FlowNode[];
396
+ depth: number;
397
+ priority: Priority;
398
+ }
399
+ ```
400
+
401
+ ---
402
+
403
+ ### `Priority` (μ—΄κ±°ν˜•)
404
+
405
+ | κ°’ | 의미 |
406
+ | ----------------- | ----------------------------------------------- |
407
+ | `CHAINING = 1` | λ©”μ„œλ“œ 체인 (`.map()`, `.filter()`, …) β€” μ΅œμš°μ„  |
408
+ | `CONDITIONAL = 2` | `if` / `else` / `switch` 블둝 |
409
+ | `LOOP = 3` | `for` / `while` 반볡문 |
410
+ | `FUNCTION = 4` | ν•¨μˆ˜ μ„ μ–Έ |
411
+ | `OTHER = 5` | κ·Έ μ™Έ |
412
+
413
+ ---
414
+
415
+ ## νŒŒμ„œ λ™μž‘ 원리
416
+
417
+ νŒŒμ„œλŠ” 결정둠적 νŒŒμ΄ν”„λΌμΈμœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€ β€” AI μ—†μŒ, μ™ΈλΆ€ μ˜μ‘΄μ„± μ—†μŒ.
418
+
419
+ ```
420
+ git diff ν…μŠ€νŠΈ
421
+ β”‚
422
+ β–Ό
423
+ 1. filterDiffLines() β€” + / - 쀄 뢄리, +++ / --- 헀더 제거
424
+ β”‚
425
+ β–Ό
426
+ 2. normalizeCode() β€” ; 제거, 주석 제거, 곡백 정리
427
+ β”‚
428
+ β–Ό
429
+ 3. getIndentDepth() β€” 쀑첩 레벨 계산 (2 spaces = 1 레벨)
430
+ β”‚
431
+ β–Ό
432
+ 4. parseToFlowTree() β€” μš°μ„ μˆœμœ„ μˆœμ„œλ‘œ νŒ¨ν„΄ λ§€μΉ­:
433
+ β”‚ P1 체이닝 (.map .filter .reduce …)
434
+ β”‚ P2 쑰건문 (if / else / switch)
435
+ β”‚ P3 반볡문 (for / while)
436
+ β”‚ P4 ν•¨μˆ˜ μ„ μ–Έ
437
+ β”‚
438
+ β–Ό
439
+ 5. renderFlowTree() β€” 트리 β†’ λ“€μ—¬μ“°κΈ°λœ ν…μŠ€νŠΈ μ€„λ‘œ λ³€ν™˜
440
+ β”‚
441
+ β–Ό
442
+ generateReaderMarkdown() β€” μ΅œμ’… Markdown λ¬Έμ„œ 쑰립
443
+ ```
444
+
445
+ **μ£Όμš” 섀계 κ²°μ •:**
446
+
447
+ - **보수적** β€” λΆ„λ₯˜λ˜μ§€ μ•ŠλŠ” 쀄은 잘λͺ»λœ 정보 λŒ€μ‹  쑰용히 κ±΄λ„ˆλœλ‹ˆλ‹€
448
+ - **import / export / type / interface / console.log**λŠ” λ¬΄μ‹œλ©λ‹ˆλ‹€. 흐름 이해에 κΈ°μ—¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€
449
+ - **콜백 인자 μΆ•μ•½** β€” 본문이 단일 속성 접근일 λ•Œ `.map(item => item.value)`λ₯Ό `map(item β†’ value)`둜 μΆ•μ•½ν•©λ‹ˆλ‹€. κ·Έ μ™Έμ—λŠ” `map(callback)`으둜 ν‘œμ‹œν•©λ‹ˆλ‹€
450
+ - **ν•¨μˆ˜ 선언은 μ΅œμš°μ„  체크** β€” `const foo = async …`κ°€ `extractRoot`에 잘λͺ» λΆ„λ₯˜λ˜μ§€ μ•Šλ„λ‘, ν•¨μˆ˜ μ„ μ–Έ 감지λ₯Ό 루트 μΆ”μΆœλ³΄λ‹€ λ¨Όμ € μˆ˜ν–‰ν•©λ‹ˆλ‹€
451
+ - **depth**λŠ” λ“€μ—¬μ“°κΈ° 기반 (2-space κΈ°μ€€)으둜 μΆ”μ λ˜λ©°, 체이닝 감지가 μ• λ§€ν•  λ•Œ 보쑰 μ •λ³΄λ‘œλ§Œ μ‚¬μš©λ©λ‹ˆλ‹€
452
+
453
+ ### 지원 μ–Έμ–΄ (v0.1)
454
+
455
+ [μ–Έμ–΄ 지원](#μ–Έμ–΄-지원) μ„Ήμ…˜μ˜ 전체 ν‘œλ₯Ό ν™•μΈν•˜μ„Έμš”.
456
+ μš”μ•½: **JS / TS / React / Next.js μ™„μ „ 지원**, Java와 C#은 λΆ€λΆ„ 지원, C 등은 Language Adapter μ‹œμŠ€ν…œ(v0.2)으둜 κ³„νš 쀑.
457
+
458
+ ---
459
+
460
+ ## κΈ°μ—¬ν•˜κΈ°
461
+
462
+ PR은 μ–Έμ œλ“ μ§€ ν™˜μ˜ν•©λ‹ˆλ‹€! μ‹œμž‘ 방법:
463
+
464
+ ```bash
465
+ # 레포 클둠
466
+ git clone https://github.com/3rdflr/github-mobile-reader.git
467
+ cd github-mobile-reader
468
+
469
+ # μ˜μ‘΄μ„± μ„€μΉ˜
470
+ npm install
471
+
472
+ # λΉŒλ“œ (라이브러리 + Action runner)
473
+ npm run build:all
474
+
475
+ # 개발 쀑 watch λͺ¨λ“œ
476
+ npm run dev
477
+
478
+ # ν…ŒμŠ€νŠΈ μ‹€ν–‰
479
+ npx ts-node src/test.ts
480
+ ```
481
+
482
+ ### ν”„λ‘œμ νŠΈ ꡬ쑰
483
+
484
+ ```
485
+ github-mobile-reader/
486
+ β”œβ”€β”€ src/
487
+ β”‚ β”œβ”€β”€ parser.ts ← 핡심 diff β†’ logical flow νŒŒμ„œ
488
+ β”‚ β”œβ”€β”€ index.ts ← npm 곡개 API
489
+ β”‚ β”œβ”€β”€ action.ts ← GitHub Action μ§„μž…μ 
490
+ β”‚ └── test.ts ← 슀λͺ¨ν¬ ν…ŒμŠ€νŠΈ (33개)
491
+ β”œβ”€β”€ dist/ ← 컴파일 κ²°κ³Όλ¬Ό (μžλ™ 생성, μˆ˜μ • κΈˆμ§€)
492
+ β”œβ”€β”€ .github/
493
+ β”‚ └── workflows/
494
+ β”‚ └── mobile-reader.yml ← μ‚¬μš©μžμš© μ˜ˆμ‹œ μ›Œν¬ν”Œλ‘œμš°
495
+ β”œβ”€β”€ action.yml ← GitHub Action μ •μ˜
496
+ β”œβ”€β”€ README.md ← μ˜μ–΄ λ¬Έμ„œ
497
+ β”œβ”€β”€ README.ko.md ← ν•œκ΅­μ–΄ λ¬Έμ„œ (ν˜„μž¬ 파일)
498
+ β”œβ”€β”€ package.json
499
+ └── tsconfig.json
500
+ ```
501
+
502
+ ### μƒˆ μ–Έμ–΄ μ–΄λŒ‘ν„° μΆ”κ°€ν•˜κΈ°
503
+
504
+ νŒŒμ„œλŠ” ν˜„μž¬ JS/TS 문법 νœ΄λ¦¬μŠ€ν‹±μ— μ˜μ‘΄ν•©λ‹ˆλ‹€ (점 체이닝, `const`/`let`/`var`, `function`, `if`/`for`/`while`). μƒˆ μ–Έμ–΄λ₯Ό μΆ”κ°€ν•˜λ €λ©΄:
505
+
506
+ 1. `src/parser.ts`μ—μ„œ 감지 헬퍼 μΆ”κ°€ (κΈ°μ‘΄ `isChaining`, `isConditional` νŒ¨ν„΄ μ°Έκ³ )
507
+ 2. `src/action.ts`의 `filterDiffLines`μ—μ„œ μƒˆ 파일 ν™•μž₯자 ν—ˆμš©
508
+ 3. `src/test.ts`에 ν•΄λ‹Ή μ–Έμ–΄μ˜ diff μ˜ˆμ‹œλ₯Ό ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ‘œ μΆ”κ°€
509
+ 4. μ˜ˆμ‹œ diffλ₯Ό ν¬ν•¨ν•΄μ„œ PR μ˜€ν”ˆ
510
+
511
+ ---
512
+
513
+ ## λΌμ΄μ„ μŠ€
514
+
515
+ MIT Β© [3rdflr](https://github.com/3rdflr)
516
+
517
+ ---
package/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # πŸ“– github-mobile-reader
2
2
 
3
- > **Stop squinting at code on your phone.**
4
3
  > `github-mobile-reader` transforms raw git diffs into clean, vertically-scrollable Markdown β€” no more pinch-zooming or swiping left and right to read a single line.
5
4
 
6
5
  [![npm version](https://img.shields.io/npm/v/github-mobile-reader.svg)](https://www.npmjs.com/package/github-mobile-reader)
@@ -15,15 +14,17 @@ GitHub's mobile web view renders code in a fixed-width monospace block. Long lin
15
14
 
16
15
  ## The Solution
17
16
 
18
- `github-mobile-reader` parses a git diff and produces a **Logical Flow** β€” a compact tree that shows *what the code does*, not just what characters changed. The result is a Markdown document that reads top-to-bottom on any screen width.
17
+ `github-mobile-reader` parses a git diff and produces a **Logical Flow** β€” a compact tree that shows _what the code does_, not just what characters changed. The result is a Markdown document that reads top-to-bottom on any screen width.
19
18
 
20
19
  **Before** (raw diff, mobile web):
20
+
21
21
  ```
22
22
  ← swipe β†’ swipe β†’ swipe β†’
23
23
  + const result = data.map(item => item.value).filter(v => v > 10).reduce((a,b) => a+b, 0)
24
24
  ```
25
25
 
26
26
  **After** (Reader Markdown):
27
+
27
28
  ```
28
29
  data
29
30
  └─ map(item β†’ value)
@@ -38,7 +39,7 @@ data
38
39
  - **Zero-dependency core** β€” the parser runs anywhere Node.js β‰₯ 18 is available
39
40
  - **Dual output format** β€” CJS (`require`) and ESM (`import`) with full TypeScript types
40
41
  - **GitHub Action** β€” drop one YAML block into any repo and get auto-generated Reader docs on every PR
41
- - **Tracks both sides of a diff** β€” shows added *and* removed code in separate sections
42
+ - **Tracks both sides of a diff** β€” shows added _and_ removed code in separate sections
42
43
  - **Conservative by design** β€” when a pattern is ambiguous, the library shows less rather than showing something wrong
43
44
 
44
45
  ---
@@ -63,17 +64,17 @@ The parser is built on regex-based pattern matching, so it can technically recei
63
64
 
64
65
  ### Current support (v0.1)
65
66
 
66
- | Language | Extensions | Flow Quality | Notes |
67
- |----------|-----------|:------------:|-------|
68
- | **JavaScript** | `.js` `.mjs` `.cjs` | βœ… Full | Baseline target language |
69
- | **TypeScript** | `.ts` | βœ… Full | JS superset β€” all patterns apply |
70
- | **React JSX** | `.jsx` | βœ… Full | Same syntax as JS |
71
- | **React TSX** | `.tsx` | βœ… Full | Same syntax as TS |
72
- | **Next.js** | `.js` `.ts` `.jsx` `.tsx` | βœ… Full | Framework on top of JS/TS |
73
- | **Java** | `.java` | ⚠️ Partial (~55%) | `if/for/while` and dot-chaining work; function declarations missed (no `const/let/var`) |
74
- | **C#** | `.cs` | ⚠️ Partial (~35%) | LINQ chaining (`.Where().Select()`) works; `using`/`namespace`/`class` not detected |
75
- | **C** | `.c` `.h` | ❌ Minimal (~15%) | No matching keywords; pointer syntax (`->`, `*`) not understood |
76
- | **Python, Go, Rust, etc.** | β€” | πŸ”œ Planned | See roadmap below |
67
+ | Language | Extensions | Flow Quality | Notes |
68
+ | -------------------------- | ------------------------- | :---------------: | --------------------------------------------------------------------------------------- |
69
+ | **JavaScript** | `.js` `.mjs` `.cjs` | βœ… Full | Baseline target language |
70
+ | **TypeScript** | `.ts` | βœ… Full | JS superset β€” all patterns apply |
71
+ | **React JSX** | `.jsx` | βœ… Full | Same syntax as JS |
72
+ | **React TSX** | `.tsx` | βœ… Full | Same syntax as TS |
73
+ | **Next.js** | `.js` `.ts` `.jsx` `.tsx` | βœ… Full | Framework on top of JS/TS |
74
+ | **Java** | `.java` | ⚠️ Partial (~55%) | `if/for/while` and dot-chaining work; function declarations missed (no `const/let/var`) |
75
+ | **C#** | `.cs` | ⚠️ Partial (~35%) | LINQ chaining (`.Where().Select()`) works; `using`/`namespace`/`class` not detected |
76
+ | **C** | `.c` `.h` | ❌ Minimal (~15%) | No matching keywords; pointer syntax (`->`, `*`) not understood |
77
+ | **Python, Go, Rust, etc.** | β€” | πŸ”œ Planned | See roadmap below |
77
78
 
78
79
  > **Note:** Java, C#, and C files are not processed by the GitHub Action by default.
79
80
  > The Action only scans `.js .jsx .ts .tsx .mjs .cjs` files ([`src/action.ts` line 66](src/action.ts)).
@@ -86,8 +87,8 @@ All four share the same underlying syntax. The parser recognises:
86
87
  - **Method chaining** β€” line starting with `.` after a line ending with `)` or `}`
87
88
  ```ts
88
89
  data
89
- .filter(item => item.active) // detected as P1 chain
90
- .map(item => item.value) // detected as P1 chain
90
+ .filter((item) => item.active) // detected as P1 chain
91
+ .map((item) => item.value); // detected as P1 chain
91
92
  ```
92
93
  - **Function declarations** β€” `const`, `let`, `var`, `function`, `async`
93
94
  - **Conditionals** β€” `if / else / switch`
@@ -98,12 +99,12 @@ All four share the same underlying syntax. The parser recognises:
98
99
 
99
100
  These languages use different conventions for the patterns above:
100
101
 
101
- | Concept | JS/TS (βœ… detected) | Java / C# / C (❌ missed) |
102
- |---------|---------------------|--------------------------|
103
- | Variable declaration | `const x = …` | `int x = …` / `String x = …` |
104
- | Arrow callbacks | `x => x.value` | Lambdas differ per language |
105
- | Noise imports | `import` / `export` | `using` / `#include` / `package` |
106
- | Async functions | `async function foo()` | `async Task<T> Foo()` |
102
+ | Concept | JS/TS (βœ… detected) | Java / C# / C (❌ missed) |
103
+ | -------------------- | ---------------------- | -------------------------------- |
104
+ | Variable declaration | `const x = …` | `int x = …` / `String x = …` |
105
+ | Arrow callbacks | `x => x.value` | Lambdas differ per language |
106
+ | Noise imports | `import` / `export` | `using` / `#include` / `package` |
107
+ | Async functions | `async function foo()` | `async Task<T> Foo()` |
107
108
 
108
109
  ### Roadmap β€” Language Adapter system (v0.2)
109
110
 
@@ -118,6 +119,7 @@ src/languages/
118
119
  ```
119
120
 
120
121
  Each adapter will declare:
122
+
121
123
  - Supported file extensions
122
124
  - Function-declaration detection pattern
123
125
  - Keywords to ignore (noise list)
@@ -134,13 +136,13 @@ npm install github-mobile-reader
134
136
  ```
135
137
 
136
138
  ```ts
137
- import { generateReaderMarkdown } from 'github-mobile-reader'
138
- import { execSync } from 'child_process'
139
+ import { generateReaderMarkdown } from "github-mobile-reader";
140
+ import { execSync } from "child_process";
139
141
 
140
- const diff = execSync('git diff HEAD~1', { encoding: 'utf8' })
141
- const markdown = generateReaderMarkdown(diff, { file: 'src/utils.ts' })
142
+ const diff = execSync("git diff HEAD~1", { encoding: "utf8" });
143
+ const markdown = generateReaderMarkdown(diff, { file: "src/utils.ts" });
142
144
 
143
- console.log(markdown)
145
+ console.log(markdown);
144
146
  ```
145
147
 
146
148
  ---
@@ -165,8 +167,8 @@ on:
165
167
  types: [opened, synchronize, reopened]
166
168
 
167
169
  permissions:
168
- contents: write # commit the generated .md file
169
- pull-requests: write # post the PR comment
170
+ contents: write # commit the generated .md file
171
+ pull-requests: write # post the PR comment
170
172
 
171
173
  jobs:
172
174
  generate-reader:
@@ -177,7 +179,7 @@ jobs:
177
179
  - name: Checkout
178
180
  uses: actions/checkout@v4
179
181
  with:
180
- fetch-depth: 0 # full history required for git diff
182
+ fetch-depth: 0 # full history required for git diff
181
183
 
182
184
  - name: Generate Reader Markdown
183
185
  uses: 3rdflr/github-mobile-reader@v1
@@ -210,11 +212,11 @@ That's it. Every subsequent PR will automatically get:
210
212
 
211
213
  ### Action Inputs
212
214
 
213
- | Input | Required | Default | Description |
214
- |-------|----------|---------|-------------|
215
- | `github_token` | βœ… | β€” | Use `${{ secrets.GITHUB_TOKEN }}` |
216
- | `base_branch` | ❌ | `main` | The branch the PR is merging into |
217
- | `output_dir` | ❌ | `docs/reader` | Directory for generated `.md` files |
215
+ | Input | Required | Default | Description |
216
+ | -------------- | -------- | ------------- | ----------------------------------- |
217
+ | `github_token` | βœ… | β€” | Use `${{ secrets.GITHUB_TOKEN }}` |
218
+ | `base_branch` | ❌ | `main` | The branch the PR is merging into |
219
+ | `output_dir` | ❌ | `docs/reader` | Directory for generated `.md` files |
218
220
 
219
221
  ---
220
222
 
@@ -238,35 +240,38 @@ yarn add github-mobile-reader
238
240
  ### CommonJS
239
241
 
240
242
  ```js
241
- const { generateReaderMarkdown } = require('github-mobile-reader')
243
+ const { generateReaderMarkdown } = require("github-mobile-reader");
242
244
  ```
243
245
 
244
246
  ### ESM / TypeScript
245
247
 
246
248
  ```ts
247
- import { generateReaderMarkdown, parseDiffToLogicalFlow } from 'github-mobile-reader'
249
+ import {
250
+ generateReaderMarkdown,
251
+ parseDiffToLogicalFlow,
252
+ } from "github-mobile-reader";
248
253
  ```
249
254
 
250
255
  ### Basic Example
251
256
 
252
257
  ```ts
253
- import { generateReaderMarkdown } from 'github-mobile-reader'
254
- import { execSync } from 'child_process'
255
- import { writeFileSync } from 'fs'
258
+ import { generateReaderMarkdown } from "github-mobile-reader";
259
+ import { execSync } from "child_process";
260
+ import { writeFileSync } from "fs";
256
261
 
257
262
  // Get the diff for the last commit
258
- const diff = execSync('git diff HEAD~1 HEAD', { encoding: 'utf8' })
263
+ const diff = execSync("git diff HEAD~1 HEAD", { encoding: "utf8" });
259
264
 
260
265
  // Generate Reader Markdown with metadata
261
266
  const markdown = generateReaderMarkdown(diff, {
262
- pr: '42',
263
- commit: 'a1b2c3d',
264
- file: 'src/api/users.ts',
265
- repo: 'my-org/my-repo',
266
- })
267
+ pr: "42",
268
+ commit: "a1b2c3d",
269
+ file: "src/api/users.ts",
270
+ repo: "my-org/my-repo",
271
+ });
267
272
 
268
273
  // Write to a file or post to Slack / Discord / GitHub
269
- writeFileSync('reader.md', markdown, 'utf8')
274
+ writeFileSync("reader.md", markdown, "utf8");
270
275
  ```
271
276
 
272
277
  ### Low-level API Example
@@ -274,16 +279,16 @@ writeFileSync('reader.md', markdown, 'utf8')
274
279
  If you only need the parsed tree (e.g. to build your own renderer):
275
280
 
276
281
  ```ts
277
- import { parseDiffToLogicalFlow, renderFlowTree } from 'github-mobile-reader'
282
+ import { parseDiffToLogicalFlow, renderFlowTree } from "github-mobile-reader";
278
283
 
279
- const { root, rawCode, removedCode } = parseDiffToLogicalFlow(diff)
284
+ const { root, rawCode, removedCode } = parseDiffToLogicalFlow(diff);
280
285
 
281
286
  // root β†’ FlowNode[] (the logical tree)
282
287
  // rawCode β†’ string (added lines, joined)
283
288
  // removedCode β†’ string (removed lines, joined)
284
289
 
285
- const treeLines = renderFlowTree(root)
286
- console.log(treeLines.join('\n'))
290
+ const treeLines = renderFlowTree(root);
291
+ console.log(treeLines.join("\n"));
287
292
  ```
288
293
 
289
294
  ---
@@ -304,13 +309,14 @@ A generated Reader Markdown document has four sections:
304
309
  ---
305
310
 
306
311
  ## 🧠 Logical Flow
307
-
308
312
  ```
313
+
309
314
  getData()
310
- └─ filter(callback)
311
- └─ map(item β†’ value)
312
- └─ reduce(callback)
313
- ```
315
+ └─ filter(callback)
316
+ └─ map(item β†’ value)
317
+ └─ reduce(callback)
318
+
319
+ ````
314
320
 
315
321
  ## βœ… Added Code
316
322
 
@@ -319,17 +325,19 @@ const result = getData()
319
325
  .filter(item => item.active)
320
326
  .map(item => item.value)
321
327
  .reduce((a, b) => a + b, 0)
322
- ```
328
+ ````
323
329
 
324
330
  ## ❌ Removed Code
325
331
 
326
332
  ```typescript
327
- const result = getData().map(item => item.value)
333
+ const result = getData().map((item) => item.value);
328
334
  ```
329
335
 
330
336
  ---
337
+
331
338
  πŸ›  Auto-generated by github-mobile-reader. Do not edit manually.
332
- ```
339
+
340
+ ````
333
341
 
334
342
  ---
335
343
 
@@ -363,7 +371,7 @@ interface ParseResult {
363
371
  rawCode: string // added lines joined with \n
364
372
  removedCode: string // removed lines joined with \n
365
373
  }
366
- ```
374
+ ````
367
375
 
368
376
  ---
369
377
 
@@ -372,7 +380,7 @@ interface ParseResult {
372
380
  Converts a `FlowNode[]` tree into an array of Markdown-safe text lines.
373
381
 
374
382
  ```ts
375
- const lines = renderFlowTree(root)
383
+ const lines = renderFlowTree(root);
376
384
  // [ 'getData()', ' └─ filter(callback)', ' └─ map(item β†’ value)' ]
377
385
  ```
378
386
 
@@ -382,11 +390,11 @@ const lines = renderFlowTree(root)
382
390
 
383
391
  ```ts
384
392
  interface FlowNode {
385
- type: 'root' | 'chain' | 'condition' | 'loop' | 'function' | 'call'
386
- name: string
387
- children: FlowNode[]
388
- depth: number
389
- priority: Priority
393
+ type: "root" | "chain" | "condition" | "loop" | "function" | "call";
394
+ name: string;
395
+ children: FlowNode[];
396
+ depth: number;
397
+ priority: Priority;
390
398
  }
391
399
  ```
392
400
 
@@ -394,13 +402,13 @@ interface FlowNode {
394
402
 
395
403
  ### `Priority` (enum)
396
404
 
397
- | Value | Meaning |
398
- |-------|---------|
399
- | `CHAINING = 1` | Method chains (`.map()`, `.filter()`, …) β€” highest priority |
400
- | `CONDITIONAL = 2` | `if` / `else` / `switch` blocks |
401
- | `LOOP = 3` | `for` / `while` loops |
402
- | `FUNCTION = 4` | Function declarations |
403
- | `OTHER = 5` | Everything else |
405
+ | Value | Meaning |
406
+ | ----------------- | ----------------------------------------------------------- |
407
+ | `CHAINING = 1` | Method chains (`.map()`, `.filter()`, …) β€” highest priority |
408
+ | `CONDITIONAL = 2` | `if` / `else` / `switch` blocks |
409
+ | `LOOP = 3` | `for` / `while` loops |
410
+ | `FUNCTION = 4` | Function declarations |
411
+ | `OTHER = 5` | Everything else |
404
412
 
405
413
  ---
406
414
 
@@ -499,6 +507,3 @@ The parser currently relies on JS/TS syntax heuristics (dot-chaining, `const`/`l
499
507
  MIT Β© [3rdflr](https://github.com/3rdflr)
500
508
 
501
509
  ---
502
-
503
- > **"The era of per-device number crunching is over.
504
- > One logic. Every screen."**
package/dist/action.js CHANGED
@@ -37,10 +37,10 @@ function filterDiffLines(diffText) {
37
37
  function normalizeCode(lines) {
38
38
  return lines.map((line) => {
39
39
  let normalized = line;
40
- normalized = normalized.replace(/;$/, "");
41
40
  normalized = normalized.replace(/\/\/.*$/, "");
42
41
  normalized = normalized.replace(/\/\*.*?\*\//, "");
43
42
  normalized = normalized.trim();
43
+ normalized = normalized.replace(/;$/, "");
44
44
  return normalized;
45
45
  }).filter((line) => line.length > 0);
46
46
  }
@@ -77,7 +77,13 @@ function isLoop(line) {
77
77
  return /^(for|while)\s*\(/.test(line.trim());
78
78
  }
79
79
  function isFunctionDeclaration(line) {
80
- return /^(function|const|let|var)\s+\w+\s*=?\s*(async\s*)?\(/.test(line.trim()) || /^(async\s+)?function\s+\w+/.test(line.trim());
80
+ const t = line.trim();
81
+ return (
82
+ // function foo() / async function foo()
83
+ /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
84
+ /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
85
+ /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
86
+ );
81
87
  }
82
88
  function shouldIgnore(line) {
83
89
  const ignorePatterns = [
@@ -136,6 +142,19 @@ function parseToFlowTree(lines) {
136
142
  prevLine = line;
137
143
  continue;
138
144
  }
145
+ if (isFunctionDeclaration(line)) {
146
+ const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
147
+ roots.push({
148
+ type: "function",
149
+ name: funcMatch ? `${funcMatch[1]}()` : "function()",
150
+ children: [],
151
+ depth: relativeDepth,
152
+ priority: 4 /* FUNCTION */
153
+ });
154
+ currentChain = null;
155
+ prevLine = line;
156
+ continue;
157
+ }
139
158
  const root = extractRoot(line);
140
159
  if (root) {
141
160
  currentChain = {
@@ -166,16 +185,6 @@ function parseToFlowTree(lines) {
166
185
  priority: 3 /* LOOP */
167
186
  });
168
187
  currentChain = null;
169
- } else if (isFunctionDeclaration(line)) {
170
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
171
- roots.push({
172
- type: "function",
173
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
174
- children: [],
175
- depth: relativeDepth,
176
- priority: 4 /* FUNCTION */
177
- });
178
- currentChain = null;
179
188
  }
180
189
  prevLine = line;
181
190
  }
package/dist/index.js CHANGED
@@ -48,10 +48,10 @@ function filterDiffLines(diffText) {
48
48
  function normalizeCode(lines) {
49
49
  return lines.map((line) => {
50
50
  let normalized = line;
51
- normalized = normalized.replace(/;$/, "");
52
51
  normalized = normalized.replace(/\/\/.*$/, "");
53
52
  normalized = normalized.replace(/\/\*.*?\*\//, "");
54
53
  normalized = normalized.trim();
54
+ normalized = normalized.replace(/;$/, "");
55
55
  return normalized;
56
56
  }).filter((line) => line.length > 0);
57
57
  }
@@ -88,7 +88,13 @@ function isLoop(line) {
88
88
  return /^(for|while)\s*\(/.test(line.trim());
89
89
  }
90
90
  function isFunctionDeclaration(line) {
91
- return /^(function|const|let|var)\s+\w+\s*=?\s*(async\s*)?\(/.test(line.trim()) || /^(async\s+)?function\s+\w+/.test(line.trim());
91
+ const t = line.trim();
92
+ return (
93
+ // function foo() / async function foo()
94
+ /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
95
+ /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
96
+ /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
97
+ );
92
98
  }
93
99
  function shouldIgnore(line) {
94
100
  const ignorePatterns = [
@@ -147,6 +153,19 @@ function parseToFlowTree(lines) {
147
153
  prevLine = line;
148
154
  continue;
149
155
  }
156
+ if (isFunctionDeclaration(line)) {
157
+ const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
158
+ roots.push({
159
+ type: "function",
160
+ name: funcMatch ? `${funcMatch[1]}()` : "function()",
161
+ children: [],
162
+ depth: relativeDepth,
163
+ priority: 4 /* FUNCTION */
164
+ });
165
+ currentChain = null;
166
+ prevLine = line;
167
+ continue;
168
+ }
150
169
  const root = extractRoot(line);
151
170
  if (root) {
152
171
  currentChain = {
@@ -177,16 +196,6 @@ function parseToFlowTree(lines) {
177
196
  priority: 3 /* LOOP */
178
197
  });
179
198
  currentChain = null;
180
- } else if (isFunctionDeclaration(line)) {
181
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
182
- roots.push({
183
- type: "function",
184
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
185
- children: [],
186
- depth: relativeDepth,
187
- priority: 4 /* FUNCTION */
188
- });
189
- currentChain = null;
190
199
  }
191
200
  prevLine = line;
192
201
  }
package/dist/index.mjs CHANGED
@@ -16,10 +16,10 @@ function filterDiffLines(diffText) {
16
16
  function normalizeCode(lines) {
17
17
  return lines.map((line) => {
18
18
  let normalized = line;
19
- normalized = normalized.replace(/;$/, "");
20
19
  normalized = normalized.replace(/\/\/.*$/, "");
21
20
  normalized = normalized.replace(/\/\*.*?\*\//, "");
22
21
  normalized = normalized.trim();
22
+ normalized = normalized.replace(/;$/, "");
23
23
  return normalized;
24
24
  }).filter((line) => line.length > 0);
25
25
  }
@@ -56,7 +56,13 @@ function isLoop(line) {
56
56
  return /^(for|while)\s*\(/.test(line.trim());
57
57
  }
58
58
  function isFunctionDeclaration(line) {
59
- return /^(function|const|let|var)\s+\w+\s*=?\s*(async\s*)?\(/.test(line.trim()) || /^(async\s+)?function\s+\w+/.test(line.trim());
59
+ const t = line.trim();
60
+ return (
61
+ // function foo() / async function foo()
62
+ /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
63
+ /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
64
+ /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
65
+ );
60
66
  }
61
67
  function shouldIgnore(line) {
62
68
  const ignorePatterns = [
@@ -115,6 +121,19 @@ function parseToFlowTree(lines) {
115
121
  prevLine = line;
116
122
  continue;
117
123
  }
124
+ if (isFunctionDeclaration(line)) {
125
+ const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
126
+ roots.push({
127
+ type: "function",
128
+ name: funcMatch ? `${funcMatch[1]}()` : "function()",
129
+ children: [],
130
+ depth: relativeDepth,
131
+ priority: 4 /* FUNCTION */
132
+ });
133
+ currentChain = null;
134
+ prevLine = line;
135
+ continue;
136
+ }
118
137
  const root = extractRoot(line);
119
138
  if (root) {
120
139
  currentChain = {
@@ -145,16 +164,6 @@ function parseToFlowTree(lines) {
145
164
  priority: 3 /* LOOP */
146
165
  });
147
166
  currentChain = null;
148
- } else if (isFunctionDeclaration(line)) {
149
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
150
- roots.push({
151
- type: "function",
152
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
153
- children: [],
154
- depth: relativeDepth,
155
- priority: 4 /* FUNCTION */
156
- });
157
- currentChain = null;
158
167
  }
159
168
  prevLine = line;
160
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-mobile-reader",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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",