github-mobile-reader 0.1.0

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.md ADDED
@@ -0,0 +1,504 @@
1
+ # 📖 github-mobile-reader
2
+
3
+ > **Stop squinting at code on your phone.**
4
+ > `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
+
6
+ [![npm version](https://img.shields.io/npm/v/github-mobile-reader.svg)](https://www.npmjs.com/package/github-mobile-reader)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+ [![Node.js ≥ 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
9
+
10
+ ---
11
+
12
+ ## The Problem
13
+
14
+ GitHub's mobile web view renders code in a fixed-width monospace block. Long lines require horizontal scrolling, deeply nested logic is invisible at a glance, and reviewing a PR on a commute is practically impossible.
15
+
16
+ ## The Solution
17
+
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.
19
+
20
+ **Before** (raw diff, mobile web):
21
+ ```
22
+ ← swipe → swipe → swipe →
23
+ + const result = data.map(item => item.value).filter(v => v > 10).reduce((a,b) => a+b, 0)
24
+ ```
25
+
26
+ **After** (Reader Markdown):
27
+ ```
28
+ data
29
+ └─ map(item → value)
30
+ └─ filter(callback)
31
+ └─ reduce(callback)
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Features
37
+
38
+ - **Zero-dependency core** — the parser runs anywhere Node.js ≥ 18 is available
39
+ - **Dual output format** — CJS (`require`) and ESM (`import`) with full TypeScript types
40
+ - **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
+ - **Conservative by design** — when a pattern is ambiguous, the library shows less rather than showing something wrong
43
+
44
+ ---
45
+
46
+ ## Table of Contents
47
+
48
+ 1. [Quick Start](#quick-start)
49
+ 2. [Language Support](#language-support)
50
+ 3. [GitHub Action (recommended)](#github-action-recommended)
51
+ 4. [npm Library Usage](#npm-library-usage)
52
+ 5. [Output Format](#output-format)
53
+ 6. [API Reference](#api-reference)
54
+ 7. [How the Parser Works](#how-the-parser-works)
55
+ 8. [Contributing](#contributing)
56
+ 9. [License](#license)
57
+
58
+ ---
59
+
60
+ ## Language Support
61
+
62
+ The parser is built on regex-based pattern matching, so it can technically receive a diff from any language. However, the detection patterns are tuned to JavaScript/TypeScript syntax, which means the **quality of the Logical Flow output varies by language**.
63
+
64
+ ### Current support (v0.1)
65
+
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 |
77
+
78
+ > **Note:** Java, C#, and C files are not processed by the GitHub Action by default.
79
+ > The Action only scans `.js .jsx .ts .tsx .mjs .cjs` files ([`src/action.ts` line 66](src/action.ts)).
80
+ > To process other languages you would need a custom adapter (see [Contributing](#contributing)).
81
+
82
+ ### Why JS/TS/React/Next.js work fully
83
+
84
+ All four share the same underlying syntax. The parser recognises:
85
+
86
+ - **Method chaining** — line starting with `.` after a line ending with `)` or `}`
87
+ ```ts
88
+ data
89
+ .filter(item => item.active) // detected as P1 chain
90
+ .map(item => item.value) // detected as P1 chain
91
+ ```
92
+ - **Function declarations** — `const`, `let`, `var`, `function`, `async`
93
+ - **Conditionals** — `if / else / switch`
94
+ - **Loops** — `for / while`
95
+ - **Noise filtering** — `import`, `export`, `type`, `interface`, `console.log` are silently dropped
96
+
97
+ ### Why C / C# / Java are limited
98
+
99
+ These languages use different conventions for the patterns above:
100
+
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()` |
107
+
108
+ ### Roadmap — Language Adapter system (v0.2)
109
+
110
+ To support additional languages, a **Language Adapter** architecture is planned:
111
+
112
+ ```
113
+ src/languages/
114
+ ├── base.adapter.ts ← shared interface
115
+ ├── js-ts.adapter.ts ← current logic (promoted from parser.ts)
116
+ ├── java.adapter.ts ← public/private/void declarations, Stream chaining
117
+ └── csharp.adapter.ts ← using/namespace, LINQ chaining
118
+ ```
119
+
120
+ Each adapter will declare:
121
+ - Supported file extensions
122
+ - Function-declaration detection pattern
123
+ - Keywords to ignore (noise list)
124
+ - Chaining notation (dot vs. arrow `->`)
125
+
126
+ If you'd like to contribute an adapter for your language, see [Contributing](#contributing).
127
+
128
+ ---
129
+
130
+ ## Quick Start
131
+
132
+ ```bash
133
+ npm install github-mobile-reader
134
+ ```
135
+
136
+ ```ts
137
+ import { generateReaderMarkdown } from 'github-mobile-reader'
138
+ import { execSync } from 'child_process'
139
+
140
+ const diff = execSync('git diff HEAD~1', { encoding: 'utf8' })
141
+ const markdown = generateReaderMarkdown(diff, { file: 'src/utils.ts' })
142
+
143
+ console.log(markdown)
144
+ ```
145
+
146
+ ---
147
+
148
+ ## GitHub Action (recommended)
149
+
150
+ The easiest way to use this library is as a GitHub Action. On every pull request it will:
151
+
152
+ 1. Parse the diff of all changed `.js` / `.ts` files
153
+ 2. Write a Reader Markdown file to `docs/reader/pr-<number>.md` inside your repo
154
+ 3. Post a summary comment directly on the PR
155
+
156
+ ### Step 1 — Add the workflow file
157
+
158
+ Create `.github/workflows/mobile-reader.yml` in your repository:
159
+
160
+ ```yaml
161
+ name: 📖 Mobile Reader
162
+
163
+ on:
164
+ pull_request:
165
+ types: [opened, synchronize, reopened]
166
+
167
+ permissions:
168
+ contents: write # commit the generated .md file
169
+ pull-requests: write # post the PR comment
170
+
171
+ jobs:
172
+ generate-reader:
173
+ name: Generate Mobile Reader View
174
+ runs-on: ubuntu-latest
175
+
176
+ steps:
177
+ - name: Checkout
178
+ uses: actions/checkout@v4
179
+ with:
180
+ fetch-depth: 0 # full history required for git diff
181
+
182
+ - name: Generate Reader Markdown
183
+ uses: 3rdflr/github-mobile-reader@v1
184
+ with:
185
+ github_token: ${{ secrets.GITHUB_TOKEN }}
186
+ base_branch: ${{ github.base_ref }}
187
+ output_dir: docs/reader
188
+ env:
189
+ PR_NUMBER: ${{ github.event.pull_request.number }}
190
+
191
+ - name: Commit Reader Markdown
192
+ run: |
193
+ git config user.name "github-actions[bot]"
194
+ git config user.email "github-actions[bot]@users.noreply.github.com"
195
+ git add docs/reader/
196
+ if git diff --cached --quiet; then
197
+ echo "No changes to commit"
198
+ else
199
+ git commit -m "docs(reader): update mobile reader for PR #${{ github.event.pull_request.number }} [skip ci]"
200
+ git push
201
+ fi
202
+ ```
203
+
204
+ ### Step 2 — Open a PR
205
+
206
+ That's it. Every subsequent PR will automatically get:
207
+
208
+ - A Reader Markdown file at `docs/reader/pr-<number>.md`
209
+ - A comment on the PR linking to the generated file
210
+
211
+ ### Action Inputs
212
+
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 |
218
+
219
+ ---
220
+
221
+ ## npm Library Usage
222
+
223
+ Use `github-mobile-reader` as a plain library in any Node.js project — CI scripts, custom bots, local tooling, etc.
224
+
225
+ ### Installation
226
+
227
+ ```bash
228
+ # npm
229
+ npm install github-mobile-reader
230
+
231
+ # pnpm
232
+ pnpm add github-mobile-reader
233
+
234
+ # yarn
235
+ yarn add github-mobile-reader
236
+ ```
237
+
238
+ ### CommonJS
239
+
240
+ ```js
241
+ const { generateReaderMarkdown } = require('github-mobile-reader')
242
+ ```
243
+
244
+ ### ESM / TypeScript
245
+
246
+ ```ts
247
+ import { generateReaderMarkdown, parseDiffToLogicalFlow } from 'github-mobile-reader'
248
+ ```
249
+
250
+ ### Basic Example
251
+
252
+ ```ts
253
+ import { generateReaderMarkdown } from 'github-mobile-reader'
254
+ import { execSync } from 'child_process'
255
+ import { writeFileSync } from 'fs'
256
+
257
+ // Get the diff for the last commit
258
+ const diff = execSync('git diff HEAD~1 HEAD', { encoding: 'utf8' })
259
+
260
+ // Generate Reader Markdown with metadata
261
+ const markdown = generateReaderMarkdown(diff, {
262
+ pr: '42',
263
+ commit: 'a1b2c3d',
264
+ file: 'src/api/users.ts',
265
+ repo: 'my-org/my-repo',
266
+ })
267
+
268
+ // Write to a file or post to Slack / Discord / GitHub
269
+ writeFileSync('reader.md', markdown, 'utf8')
270
+ ```
271
+
272
+ ### Low-level API Example
273
+
274
+ If you only need the parsed tree (e.g. to build your own renderer):
275
+
276
+ ```ts
277
+ import { parseDiffToLogicalFlow, renderFlowTree } from 'github-mobile-reader'
278
+
279
+ const { root, rawCode, removedCode } = parseDiffToLogicalFlow(diff)
280
+
281
+ // root → FlowNode[] (the logical tree)
282
+ // rawCode → string (added lines, joined)
283
+ // removedCode → string (removed lines, joined)
284
+
285
+ const treeLines = renderFlowTree(root)
286
+ console.log(treeLines.join('\n'))
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Output Format
292
+
293
+ A generated Reader Markdown document has four sections:
294
+
295
+ ```markdown
296
+ # 📖 GitHub Reader View
297
+
298
+ > Generated by **github-mobile-reader**
299
+ > Repository: my-org/my-repo
300
+ > Pull Request: #42
301
+ > Commit: `a1b2c3d`
302
+ > File: `src/api/users.ts`
303
+
304
+ ---
305
+
306
+ ## 🧠 Logical Flow
307
+
308
+ ```
309
+ getData()
310
+ └─ filter(callback)
311
+ └─ map(item → value)
312
+ └─ reduce(callback)
313
+ ```
314
+
315
+ ## ✅ Added Code
316
+
317
+ ```typescript
318
+ const result = getData()
319
+ .filter(item => item.active)
320
+ .map(item => item.value)
321
+ .reduce((a, b) => a + b, 0)
322
+ ```
323
+
324
+ ## ❌ Removed Code
325
+
326
+ ```typescript
327
+ const result = getData().map(item => item.value)
328
+ ```
329
+
330
+ ---
331
+ 🛠 Auto-generated by github-mobile-reader. Do not edit manually.
332
+ ```
333
+
334
+ ---
335
+
336
+ ## API Reference
337
+
338
+ ### `generateReaderMarkdown(diffText, meta?)`
339
+
340
+ The main entry point. Parses a raw git diff string and returns a complete Reader Markdown document.
341
+
342
+ | Parameter | Type | Description |
343
+ |-----------|------|-------------|
344
+ | `diffText` | `string` | Raw output of `git diff` |
345
+ | `meta.pr` | `string?` | Pull request number |
346
+ | `meta.commit` | `string?` | Commit SHA |
347
+ | `meta.file` | `string?` | File name shown in the header |
348
+ | `meta.repo` | `string?` | Repository in `owner/repo` format |
349
+
350
+ **Returns:** `string` — the complete Markdown document.
351
+
352
+ ---
353
+
354
+ ### `parseDiffToLogicalFlow(diffText)`
355
+
356
+ Parses a diff into a structured result without rendering.
357
+
358
+ **Returns:** `ParseResult`
359
+
360
+ ```ts
361
+ interface ParseResult {
362
+ root: FlowNode[] // logical tree (added lines)
363
+ rawCode: string // added lines joined with \n
364
+ removedCode: string // removed lines joined with \n
365
+ }
366
+ ```
367
+
368
+ ---
369
+
370
+ ### `renderFlowTree(nodes, indent?)`
371
+
372
+ Converts a `FlowNode[]` tree into an array of Markdown-safe text lines.
373
+
374
+ ```ts
375
+ const lines = renderFlowTree(root)
376
+ // [ 'getData()', ' └─ filter(callback)', ' └─ map(item → value)' ]
377
+ ```
378
+
379
+ ---
380
+
381
+ ### `FlowNode`
382
+
383
+ ```ts
384
+ interface FlowNode {
385
+ type: 'root' | 'chain' | 'condition' | 'loop' | 'function' | 'call'
386
+ name: string
387
+ children: FlowNode[]
388
+ depth: number
389
+ priority: Priority
390
+ }
391
+ ```
392
+
393
+ ---
394
+
395
+ ### `Priority` (enum)
396
+
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 |
404
+
405
+ ---
406
+
407
+ ## How the Parser Works
408
+
409
+ The parser runs a deterministic pipeline — no AI, no external dependencies.
410
+
411
+ ```
412
+ git diff text
413
+
414
+
415
+ 1. filterDiffLines() — split + and - lines, strip +++ / --- headers
416
+
417
+
418
+ 2. normalizeCode() — remove ; comments, trim whitespace
419
+
420
+
421
+ 3. getIndentDepth() — calculate nesting level (2 spaces = 1 level)
422
+
423
+
424
+ 4. parseToFlowTree() — match patterns in priority order:
425
+ │ P1 chaining (.map .filter .reduce …)
426
+ │ P2 conditional (if / else / switch)
427
+ │ P3 loop (for / while)
428
+ │ P4 function declaration
429
+
430
+
431
+ 5. renderFlowTree() — convert tree → indented text lines
432
+
433
+
434
+ generateReaderMarkdown() — assemble the final Markdown document
435
+ ```
436
+
437
+ **Key design decisions:**
438
+
439
+ - **Conservative** — lines that cannot be classified are silently skipped rather than misrepresented.
440
+ - **Imports / exports / types / interfaces / console.log** are ignored; they do not contribute to understanding flow.
441
+ - **Callback arguments** are simplified: `.map(item => item.value)` becomes `map(item → value)` when the body is a single property access; otherwise it becomes `map(callback)`.
442
+ - **Depth** is tracked via indentation (2-space baseline) and used only as a fallback when chaining detection is ambiguous.
443
+
444
+ ### Supported Languages (v0.1)
445
+
446
+ See the full breakdown in the [Language Support](#language-support) section.
447
+ In short: **JS / TS / React / Next.js are fully supported**; Java and C# are partial; C and others are planned via the Language Adapter system (v0.2).
448
+
449
+ ---
450
+
451
+ ## Contributing
452
+
453
+ Pull requests are welcome! Here's how to get started:
454
+
455
+ ```bash
456
+ # Clone the repo
457
+ git clone https://github.com/3rdflr/github-mobile-reader.git
458
+ cd github-mobile-reader
459
+
460
+ # Install dependencies
461
+ npm install
462
+
463
+ # Build (library + action runner)
464
+ npm run build:all
465
+
466
+ # Watch mode during development
467
+ npm run dev
468
+ ```
469
+
470
+ ### Project Structure
471
+
472
+ ```
473
+ github-mobile-reader/
474
+ ├── src/
475
+ │ ├── parser.ts ← core diff → logical flow parser
476
+ │ ├── index.ts ← public npm API surface
477
+ │ └── action.ts ← GitHub Action entry point
478
+ ├── dist/ ← compiled output (auto-generated, do not edit)
479
+ ├── .github/
480
+ │ └── workflows/
481
+ │ └── mobile-reader.yml ← example workflow for consumers
482
+ ├── action.yml ← GitHub Action definition
483
+ ├── package.json
484
+ └── tsconfig.json
485
+ ```
486
+
487
+ ### Adding Support for a New Language
488
+
489
+ The parser currently relies on JS/TS syntax heuristics (dot-chaining, `const`/`let`/`var`, `function`, `if`/`for`/`while`). To add a new language:
490
+
491
+ 1. Add detection helpers in `src/parser.ts` (follow the existing `isChaining`, `isConditional` pattern)
492
+ 2. Update `filterDiffLines` to handle the new file extension
493
+ 3. Open a PR with a test diff as an example
494
+
495
+ ---
496
+
497
+ ## License
498
+
499
+ MIT © [3rdflr](https://github.com/3rdflr)
500
+
501
+ ---
502
+
503
+ > **"The era of per-device number crunching is over.
504
+ > One logic. Every screen."**
package/action.yml ADDED
@@ -0,0 +1,24 @@
1
+ name: 'GitHub Mobile Reader'
2
+ description: 'Generate mobile-friendly Markdown summaries of PR diffs and post them as PR comments.'
3
+ author: 'your-org'
4
+
5
+ branding:
6
+ icon: 'book-open'
7
+ color: 'blue'
8
+
9
+ inputs:
10
+ github_token:
11
+ description: 'GitHub token with write access to issues/pull-requests (use ${{ secrets.GITHUB_TOKEN }})'
12
+ required: true
13
+ base_branch:
14
+ description: 'Base branch to diff against (default: main)'
15
+ required: false
16
+ default: 'main'
17
+ output_dir:
18
+ description: 'Directory where the generated .md file will be saved inside the repo'
19
+ required: false
20
+ default: 'docs/reader'
21
+
22
+ runs:
23
+ using: 'node20'
24
+ main: 'dist/action.js'