github-mobile-reader 0.1.1 → 0.1.2
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 +75 -7
- package/README.md +76 -8
- package/dist/action.js +156 -18
- package/dist/cli.js +552 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +177 -11
- package/dist/index.mjs +167 -10
- package/package.json +7 -3
package/README.ko.md
CHANGED
|
@@ -40,9 +40,13 @@ data
|
|
|
40
40
|
|
|
41
41
|
- **의존성 제로 코어** — 파서는 Node.js ≥ 18이 있는 어디서나 동작합니다
|
|
42
42
|
- **이중 출력 포맷** — CJS (`require`)와 ESM (`import`) 모두 지원, TypeScript 타입 포함
|
|
43
|
+
- **CLI** — `npx github-mobile-reader --repo owner/repo --pr 42` 로 어떤 PR이든 즉시 변환
|
|
43
44
|
- **GitHub Action** — 레포에 YAML 파일 하나만 추가하면 PR마다 Reader 문서가 자동 생성됩니다
|
|
45
|
+
- **파일별 분리 출력** — 변경된 JS/TS 파일마다 독립적인 섹션으로 출력
|
|
46
|
+
- **JSX/Tailwind 인식** — `.jsx`/`.tsx` 파일은 컴포넌트 트리(`🎨 JSX Structure`)와 Tailwind 클래스 diff(`💅 Style Changes`)를 별도 섹션으로 분리 출력
|
|
44
47
|
- **양방향 diff 추적** — 추가된 코드와 삭제된 코드를 각각 별도 섹션으로 표시
|
|
45
48
|
- **보수적 설계** — 패턴이 애매할 때는 잘못된 정보를 보여주는 대신 덜 보여줍니다
|
|
49
|
+
- **보안 기본값** — 토큰은 `$GITHUB_TOKEN` 환경변수로만 읽음 — 셸 히스토리나 `ps` 목록에 노출되는 `--token` 플래그 없음
|
|
46
50
|
|
|
47
51
|
---
|
|
48
52
|
|
|
@@ -50,13 +54,75 @@ data
|
|
|
50
54
|
|
|
51
55
|
1. [빠른 시작](#빠른-시작)
|
|
52
56
|
2. [언어 지원](#언어-지원)
|
|
53
|
-
3. [
|
|
54
|
-
4. [
|
|
55
|
-
5. [
|
|
56
|
-
6. [
|
|
57
|
-
7. [
|
|
58
|
-
8. [
|
|
59
|
-
9. [
|
|
57
|
+
3. [CLI 사용법](#cli-사용법)
|
|
58
|
+
4. [GitHub Action (권장)](#github-action-권장)
|
|
59
|
+
5. [npm 라이브러리 사용법](#npm-라이브러리-사용법)
|
|
60
|
+
6. [출력 형식](#출력-형식)
|
|
61
|
+
7. [API 레퍼런스](#api-레퍼런스)
|
|
62
|
+
8. [파서 동작 원리](#파서-동작-원리)
|
|
63
|
+
9. [기여하기](#기여하기)
|
|
64
|
+
10. [라이선스](#라이선스)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## CLI 사용법
|
|
69
|
+
|
|
70
|
+
터미널에서 `github-mobile-reader`를 바로 실행할 수 있습니다 — 별도 설정 파일 불필요. GitHub에서 PR diff를 받아 모바일 친화적인 Markdown으로 변환하고 `./reader-output/`에 PR별로 파일을 저장합니다.
|
|
71
|
+
|
|
72
|
+
### 인증 (토큰 설정)
|
|
73
|
+
|
|
74
|
+
CLI 실행 **전에** 환경변수로 GitHub 토큰을 설정하세요:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
export GITHUB_TOKEN=ghp_xxxx
|
|
78
|
+
npx github-mobile-reader --repo owner/repo --pr 42
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **보안 안내:** CLI는 `--token` 플래그를 지원하지 않습니다. 커맨드라인 인자로 시크릿을 전달하면 셸 히스토리와 `ps` 출력에 토큰이 노출됩니다. 반드시 환경변수를 사용하세요.
|
|
82
|
+
|
|
83
|
+
### 단일 PR
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx github-mobile-reader --repo owner/repo --pr 42
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 최근 PR 전체
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx github-mobile-reader --repo owner/repo --all
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 옵션
|
|
96
|
+
|
|
97
|
+
| 플래그 | 기본값 | 설명 |
|
|
98
|
+
| ----------- | ------------------ | ------------------------------------------------------- |
|
|
99
|
+
| `--repo` | *(필수)* | `owner/repo` 형식의 레포지토리 |
|
|
100
|
+
| `--pr` | — | 특정 PR 번호 하나 처리 |
|
|
101
|
+
| `--all` | — | 최근 PR 전체 처리 (`--limit`와 함께 사용) |
|
|
102
|
+
| `--out` | `./reader-output` | 생성된 `.md` 파일 저장 경로 — 상대 경로만 허용, `..` 불가 |
|
|
103
|
+
| `--limit` | `10` | `--all` 사용 시 가져올 PR 최대 개수 |
|
|
104
|
+
|
|
105
|
+
토큰: `$GITHUB_TOKEN` 환경변수에서 읽음 (미인증 시 60 req/hr, 인증 시 5 000 req/hr).
|
|
106
|
+
|
|
107
|
+
### 출력 결과
|
|
108
|
+
|
|
109
|
+
PR마다 `reader-output/pr-<번호>.md` 파일 하나가 생성됩니다.
|
|
110
|
+
|
|
111
|
+
JSX/TSX 파일은 추가 섹션이 생성됩니다:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
# 📖 PR #42 — My Feature
|
|
115
|
+
|
|
116
|
+
## 📄 `src/App.tsx`
|
|
117
|
+
|
|
118
|
+
### 🧠 Logical Flow ← JS 로직 트리
|
|
119
|
+
### 🎨 JSX Structure ← 컴포넌트 계층 구조 (JSX/TSX 전용)
|
|
120
|
+
### 💅 Style Changes ← 추가/제거된 Tailwind 클래스 (JSX/TSX 전용)
|
|
121
|
+
### ✅ Added Code
|
|
122
|
+
### ❌ Removed Code
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> **참고:** `reader-output/`는 기본적으로 `.gitignore`에 포함되어 있습니다 — 생성된 파일은 로컬에만 저장되며 레포지토리에 커밋되지 않습니다.
|
|
60
126
|
|
|
61
127
|
---
|
|
62
128
|
|
|
@@ -487,8 +553,10 @@ github-mobile-reader/
|
|
|
487
553
|
│ ├── parser.ts ← 핵심 diff → logical flow 파서
|
|
488
554
|
│ ├── index.ts ← npm 공개 API
|
|
489
555
|
│ ├── action.ts ← GitHub Action 진입점
|
|
556
|
+
│ ├── cli.ts ← CLI 진입점 (npx github-mobile-reader)
|
|
490
557
|
│ └── test.ts ← 스모크 테스트 (33개)
|
|
491
558
|
├── dist/ ← 컴파일 결과물 (자동 생성, 수정 금지)
|
|
559
|
+
├── reader-output/ ← CLI 출력 디렉토리 (gitignore됨)
|
|
492
560
|
├── .github/
|
|
493
561
|
│ └── workflows/
|
|
494
562
|
│ └── mobile-reader.yml ← 사용자용 예시 워크플로우
|
package/README.md
CHANGED
|
@@ -38,9 +38,13 @@ data
|
|
|
38
38
|
|
|
39
39
|
- **Zero-dependency core** — the parser runs anywhere Node.js ≥ 18 is available
|
|
40
40
|
- **Dual output format** — CJS (`require`) and ESM (`import`) with full TypeScript types
|
|
41
|
+
- **CLI** — `npx github-mobile-reader --repo owner/repo --pr 42` fetches and converts any PR instantly
|
|
41
42
|
- **GitHub Action** — drop one YAML block into any repo and get auto-generated Reader docs on every PR
|
|
43
|
+
- **File-by-file output** — each changed JS/TS file gets its own independent section in the output
|
|
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
|
|
42
45
|
- **Tracks both sides of a diff** — shows added _and_ removed code in separate sections
|
|
43
46
|
- **Conservative by design** — when a pattern is ambiguous, the library shows less rather than showing something wrong
|
|
47
|
+
- **Secure by default** — token is read from `$GITHUB_TOKEN` only; no flag that leaks to shell history or `ps`
|
|
44
48
|
|
|
45
49
|
---
|
|
46
50
|
|
|
@@ -48,13 +52,14 @@ data
|
|
|
48
52
|
|
|
49
53
|
1. [Quick Start](#quick-start)
|
|
50
54
|
2. [Language Support](#language-support)
|
|
51
|
-
3. [
|
|
52
|
-
4. [
|
|
53
|
-
5. [
|
|
54
|
-
6. [
|
|
55
|
-
7. [
|
|
56
|
-
8. [
|
|
57
|
-
9. [
|
|
55
|
+
3. [CLI Usage](#cli-usage)
|
|
56
|
+
4. [GitHub Action (recommended)](#github-action-recommended)
|
|
57
|
+
5. [npm Library Usage](#npm-library-usage)
|
|
58
|
+
6. [Output Format](#output-format)
|
|
59
|
+
7. [API Reference](#api-reference)
|
|
60
|
+
8. [How the Parser Works](#how-the-parser-works)
|
|
61
|
+
9. [Contributing](#contributing)
|
|
62
|
+
10. [License](#license)
|
|
58
63
|
|
|
59
64
|
---
|
|
60
65
|
|
|
@@ -129,6 +134,67 @@ If you'd like to contribute an adapter for your language, see [Contributing](#co
|
|
|
129
134
|
|
|
130
135
|
---
|
|
131
136
|
|
|
137
|
+
## CLI Usage
|
|
138
|
+
|
|
139
|
+
Run `github-mobile-reader` directly from your terminal — no setup, no config file. It fetches a PR diff from GitHub, converts it to mobile-friendly Markdown, and saves one file per PR to `./reader-output/`.
|
|
140
|
+
|
|
141
|
+
### Authentication
|
|
142
|
+
|
|
143
|
+
Set your GitHub token as an environment variable **before** running the CLI:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
export GITHUB_TOKEN=ghp_xxxx
|
|
147
|
+
npx github-mobile-reader --repo owner/repo --pr 42
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> **Security note:** The CLI does not accept a `--token` flag. Passing secrets as command-line arguments exposes them in shell history and `ps` output. Always use the environment variable.
|
|
151
|
+
|
|
152
|
+
### Single PR
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx github-mobile-reader --repo owner/repo --pr 42
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### All recent PRs
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx github-mobile-reader --repo owner/repo --all
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Options
|
|
165
|
+
|
|
166
|
+
| Flag | Default | Description |
|
|
167
|
+
| --------- | ----------------- | ------------------------------------------------- |
|
|
168
|
+
| `--repo` | *(required)* | Repository in `owner/repo` format |
|
|
169
|
+
| `--pr` | — | Process a single PR by number |
|
|
170
|
+
| `--all` | — | Process all recent PRs (use with `--limit`) |
|
|
171
|
+
| `--out` | `./reader-output` | Output directory — relative paths only, no `..` |
|
|
172
|
+
| `--limit` | `10` | Max number of PRs to fetch when using `--all` |
|
|
173
|
+
|
|
174
|
+
Token: read from `$GITHUB_TOKEN` environment variable (60 req/hr unauthenticated, 5 000 req/hr authenticated).
|
|
175
|
+
|
|
176
|
+
### Output
|
|
177
|
+
|
|
178
|
+
Each PR produces one file: `reader-output/pr-<number>.md`.
|
|
179
|
+
|
|
180
|
+
Inside that file, every changed JS/TS file gets its own section. JSX/TSX files get two extra sections:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
# 📖 PR #42 — My Feature
|
|
184
|
+
|
|
185
|
+
## 📄 `src/App.tsx`
|
|
186
|
+
|
|
187
|
+
### 🧠 Logical Flow ← JS logic tree
|
|
188
|
+
### 🎨 JSX Structure ← component hierarchy (JSX/TSX only)
|
|
189
|
+
### 💅 Style Changes ← added/removed Tailwind classes (JSX/TSX only)
|
|
190
|
+
### ✅ Added Code
|
|
191
|
+
### ❌ Removed Code
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> **Note:** `reader-output/` is gitignored by default — the generated files are local only and not committed to your repository.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
132
198
|
## Quick Start
|
|
133
199
|
|
|
134
200
|
```bash
|
|
@@ -482,8 +548,10 @@ github-mobile-reader/
|
|
|
482
548
|
├── src/
|
|
483
549
|
│ ├── parser.ts ← core diff → logical flow parser
|
|
484
550
|
│ ├── index.ts ← public npm API surface
|
|
485
|
-
│
|
|
551
|
+
│ ├── action.ts ← GitHub Action entry point
|
|
552
|
+
│ └── cli.ts ← CLI entry point (npx github-mobile-reader)
|
|
486
553
|
├── dist/ ← compiled output (auto-generated, do not edit)
|
|
554
|
+
├── reader-output/ ← CLI output directory (gitignored)
|
|
487
555
|
├── .github/
|
|
488
556
|
│ └── workflows/
|
|
489
557
|
│ └── mobile-reader.yml ← example workflow for consumers
|
package/dist/action.js
CHANGED
|
@@ -28,6 +28,131 @@ var path = __toESM(require("path"));
|
|
|
28
28
|
var import_child_process = require("child_process");
|
|
29
29
|
|
|
30
30
|
// src/parser.ts
|
|
31
|
+
function isJSXFile(filename) {
|
|
32
|
+
return /\.(jsx|tsx)$/.test(filename);
|
|
33
|
+
}
|
|
34
|
+
function hasJSXContent(lines) {
|
|
35
|
+
return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
|
|
36
|
+
}
|
|
37
|
+
function isClassNameOnlyLine(line) {
|
|
38
|
+
return /^className=/.test(line.trim());
|
|
39
|
+
}
|
|
40
|
+
function extractClassName(line) {
|
|
41
|
+
const staticMatch = line.match(/className="([^"]*)"/);
|
|
42
|
+
if (staticMatch) return staticMatch[1];
|
|
43
|
+
const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
|
|
44
|
+
if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
|
|
45
|
+
const templateMatch = line.match(/className=\{`([^`]*)`\}/);
|
|
46
|
+
if (templateMatch) {
|
|
47
|
+
const raw = templateMatch[1];
|
|
48
|
+
const literals = raw.replace(/\$\{[^}]*\}/g, " ").trim();
|
|
49
|
+
const exprStrings = [...raw.matchAll(/"([^"]*)"/g)].map((m) => m[1]);
|
|
50
|
+
return [literals, ...exprStrings].filter(Boolean).join(" ");
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function extractComponentFromLine(line) {
|
|
55
|
+
const tagMatch = line.match(/<([A-Za-z][A-Za-z0-9.]*)/);
|
|
56
|
+
if (tagMatch) return tagMatch[1];
|
|
57
|
+
return "unknown";
|
|
58
|
+
}
|
|
59
|
+
function parseClassNameChanges(addedLines, removedLines) {
|
|
60
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const line of addedLines.filter((l) => /className=/.test(l))) {
|
|
62
|
+
const cls = extractClassName(line);
|
|
63
|
+
const comp = extractComponentFromLine(line);
|
|
64
|
+
if (!cls) continue;
|
|
65
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
66
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
|
|
67
|
+
}
|
|
68
|
+
for (const line of removedLines.filter((l) => /className=/.test(l))) {
|
|
69
|
+
const cls = extractClassName(line);
|
|
70
|
+
const comp = extractComponentFromLine(line);
|
|
71
|
+
if (!cls) continue;
|
|
72
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
73
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
|
|
74
|
+
}
|
|
75
|
+
const changes = [];
|
|
76
|
+
for (const [comp, { added, removed }] of componentMap) {
|
|
77
|
+
const pureAdded = [...added].filter((c) => !removed.has(c));
|
|
78
|
+
const pureRemoved = [...removed].filter((c) => !added.has(c));
|
|
79
|
+
if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
|
|
80
|
+
changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
|
|
81
|
+
}
|
|
82
|
+
return changes;
|
|
83
|
+
}
|
|
84
|
+
function renderStyleChanges(changes) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
for (const change of changes) {
|
|
87
|
+
lines.push(`**${change.component}**`);
|
|
88
|
+
if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
|
|
89
|
+
if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
|
|
90
|
+
}
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
function isJSXElement(line) {
|
|
94
|
+
const t = line.trim();
|
|
95
|
+
return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
|
|
96
|
+
}
|
|
97
|
+
function isJSXClosing(line) {
|
|
98
|
+
return /^<\/[A-Za-z]/.test(line.trim());
|
|
99
|
+
}
|
|
100
|
+
function isJSXSelfClosing(line) {
|
|
101
|
+
return /\/>[\s]*$/.test(line.trim());
|
|
102
|
+
}
|
|
103
|
+
function extractJSXComponentName(line) {
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
|
|
106
|
+
if (closingMatch) return `/${closingMatch[1]}`;
|
|
107
|
+
const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
|
|
108
|
+
if (!nameMatch) return trimmed;
|
|
109
|
+
const name = nameMatch[1];
|
|
110
|
+
const eventProps = [];
|
|
111
|
+
for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
|
|
112
|
+
eventProps.push(m[1]);
|
|
113
|
+
}
|
|
114
|
+
return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
|
|
115
|
+
}
|
|
116
|
+
function shouldIgnoreJSX(line) {
|
|
117
|
+
const t = line.trim();
|
|
118
|
+
return isClassNameOnlyLine(t) || /^style=/.test(t) || /^aria-/.test(t) || /^data-/.test(t) || /^strokeLinecap=/.test(t) || /^strokeLinejoin=/.test(t) || /^strokeWidth=/.test(t) || /^viewBox=/.test(t) || /^fill=/.test(t) || /^stroke=/.test(t) || /^d="/.test(t) || t === "{" || t === "}" || t === "(" || t === ")" || t === "<>" || t === "</>" || /^\{\/\*/.test(t);
|
|
119
|
+
}
|
|
120
|
+
function parseJSXToFlowTree(lines) {
|
|
121
|
+
const roots = [];
|
|
122
|
+
const stack = [];
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
if (!isJSXElement(line)) continue;
|
|
125
|
+
if (shouldIgnoreJSX(line)) continue;
|
|
126
|
+
const depth = getIndentDepth(line);
|
|
127
|
+
if (isJSXClosing(line)) {
|
|
128
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
129
|
+
stack.pop();
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const name = extractJSXComponentName(line);
|
|
134
|
+
const selfClosing = isJSXSelfClosing(line);
|
|
135
|
+
const node = {
|
|
136
|
+
type: "call",
|
|
137
|
+
name,
|
|
138
|
+
children: [],
|
|
139
|
+
depth,
|
|
140
|
+
priority: 5 /* OTHER */
|
|
141
|
+
};
|
|
142
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
143
|
+
stack.pop();
|
|
144
|
+
}
|
|
145
|
+
if (stack.length === 0) {
|
|
146
|
+
roots.push(node);
|
|
147
|
+
} else {
|
|
148
|
+
stack[stack.length - 1].node.children.push(node);
|
|
149
|
+
}
|
|
150
|
+
if (!selfClosing) {
|
|
151
|
+
stack.push({ node, depth });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return roots;
|
|
155
|
+
}
|
|
31
156
|
function filterDiffLines(diffText) {
|
|
32
157
|
const lines = diffText.split("\n");
|
|
33
158
|
const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
|
|
@@ -201,19 +326,21 @@ function renderFlowTree(nodes, indent = 0) {
|
|
|
201
326
|
}
|
|
202
327
|
return lines;
|
|
203
328
|
}
|
|
204
|
-
function
|
|
329
|
+
function generateReaderMarkdown(diffText, meta = {}) {
|
|
205
330
|
const { added, removed } = filterDiffLines(diffText);
|
|
206
|
-
const
|
|
331
|
+
const isJSX = Boolean(
|
|
332
|
+
meta.file && isJSXFile(meta.file) || hasJSXContent(added)
|
|
333
|
+
);
|
|
334
|
+
const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
|
|
335
|
+
const normalizedAdded = normalizeCode(addedForFlow);
|
|
207
336
|
const flowTree = parseToFlowTree(normalizedAdded);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
function generateReaderMarkdown(diffText, meta = {}) {
|
|
215
|
-
const result = parseDiffToLogicalFlow(diffText);
|
|
337
|
+
const rawCode = addedForFlow.join("\n");
|
|
338
|
+
const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
|
|
339
|
+
const removedCode = removedForCode.join("\n");
|
|
340
|
+
const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
|
|
341
|
+
const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
|
|
216
342
|
const sections = [];
|
|
343
|
+
const lang = isJSX ? "tsx" : "typescript";
|
|
217
344
|
sections.push("# \u{1F4D6} GitHub Reader View\n");
|
|
218
345
|
sections.push("> Generated by **github-mobile-reader**");
|
|
219
346
|
if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
|
|
@@ -221,22 +348,33 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
221
348
|
if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
|
|
222
349
|
if (meta.file) sections.push(`> File: \`${meta.file}\``);
|
|
223
350
|
sections.push("\n");
|
|
224
|
-
if (
|
|
351
|
+
if (flowTree.length > 0) {
|
|
225
352
|
sections.push("## \u{1F9E0} Logical Flow\n");
|
|
226
353
|
sections.push("```");
|
|
227
|
-
sections.push(...renderFlowTree(
|
|
354
|
+
sections.push(...renderFlowTree(flowTree));
|
|
228
355
|
sections.push("```\n");
|
|
229
356
|
}
|
|
230
|
-
if (
|
|
357
|
+
if (isJSX && jsxTree.length > 0) {
|
|
358
|
+
sections.push("## \u{1F3A8} JSX Structure\n");
|
|
359
|
+
sections.push("```");
|
|
360
|
+
sections.push(...renderFlowTree(jsxTree));
|
|
361
|
+
sections.push("```\n");
|
|
362
|
+
}
|
|
363
|
+
if (isJSX && classNameChanges.length > 0) {
|
|
364
|
+
sections.push("## \u{1F485} Style Changes\n");
|
|
365
|
+
sections.push(...renderStyleChanges(classNameChanges));
|
|
366
|
+
sections.push("");
|
|
367
|
+
}
|
|
368
|
+
if (rawCode.trim()) {
|
|
231
369
|
sections.push("## \u2705 Added Code\n");
|
|
232
|
-
sections.push(
|
|
233
|
-
sections.push(
|
|
370
|
+
sections.push(`\`\`\`${lang}`);
|
|
371
|
+
sections.push(rawCode);
|
|
234
372
|
sections.push("```\n");
|
|
235
373
|
}
|
|
236
|
-
if (
|
|
374
|
+
if (removedCode.trim()) {
|
|
237
375
|
sections.push("## \u274C Removed Code\n");
|
|
238
|
-
sections.push(
|
|
239
|
-
sections.push(
|
|
376
|
+
sections.push(`\`\`\`${lang}`);
|
|
377
|
+
sections.push(removedCode);
|
|
240
378
|
sections.push("```\n");
|
|
241
379
|
}
|
|
242
380
|
sections.push("---");
|