github-mobile-reader 0.1.2 → 0.1.3

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
@@ -219,7 +219,7 @@ src/languages/
219
219
 
220
220
  이 라이브러리를 사용하는 가장 쉬운 방법입니다. 매 PR마다 자동으로:
221
221
 
222
- 1. 변경된 `.js` / `.ts` 파일의 diff를 파싱
222
+ 1. 변경된 `.js` / `.jsx` / `.ts` / `.tsx` / `.mjs` / `.cjs` 파일의 diff를 파싱
223
223
  2. `docs/reader/pr-<번호>.md` 파일을 레포에 저장
224
224
  3. PR에 요약 코멘트를 자동으로 달아줍니다
225
225
 
package/README.md CHANGED
@@ -217,7 +217,7 @@ console.log(markdown);
217
217
 
218
218
  The easiest way to use this library is as a GitHub Action. On every pull request it will:
219
219
 
220
- 1. Parse the diff of all changed `.js` / `.ts` files
220
+ 1. Parse the diff of all changed `.js` / `.jsx` / `.ts` / `.tsx` / `.mjs` / `.cjs` files
221
221
  2. Write a Reader Markdown file to `docs/reader/pr-<number>.md` inside your repo
222
222
  3. Post a summary comment directly on the PR
223
223
 
package/dist/action.js CHANGED
@@ -74,6 +74,7 @@ function parseClassNameChanges(addedLines, removedLines) {
74
74
  }
75
75
  const changes = [];
76
76
  for (const [comp, { added, removed }] of componentMap) {
77
+ if (comp === "unknown") continue;
77
78
  const pureAdded = [...added].filter((c) => !removed.has(c));
78
79
  const pureRemoved = [...removed].filter((c) => !added.has(c));
79
80
  if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
@@ -159,188 +160,65 @@ function filterDiffLines(diffText) {
159
160
  const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
160
161
  return { added, removed };
161
162
  }
162
- function normalizeCode(lines) {
163
- return lines.map((line) => {
164
- let normalized = line;
165
- normalized = normalized.replace(/\/\/.*$/, "");
166
- normalized = normalized.replace(/\/\*.*?\*\//, "");
167
- normalized = normalized.trim();
168
- normalized = normalized.replace(/;$/, "");
169
- return normalized;
170
- }).filter((line) => line.length > 0);
171
- }
172
163
  function getIndentDepth(line) {
173
164
  const match = line.match(/^(\s*)/);
174
165
  if (!match) return 0;
175
166
  return Math.floor(match[1].length / 2);
176
167
  }
177
- function isChaining(line, prevLine) {
178
- if (!prevLine) return false;
179
- if (!line.trim().startsWith(".")) return false;
180
- if (!prevLine.match(/[)\}]$/)) return false;
181
- return true;
182
- }
183
- function extractChainMethod(line) {
184
- const match = line.match(/\.(\w+)\(/);
185
- if (match) return `${match[1]}()`;
186
- return line.trim();
187
- }
188
- function simplifyCallback(methodCall) {
189
- const arrowMatch = methodCall.match(/(\w+)\((\w+)\s*=>\s*(\w+)\.(\w+)\)/);
190
- if (arrowMatch) {
191
- const [, method, param, , prop] = arrowMatch;
192
- return `${method}(${param} \u2192 ${prop})`;
193
- }
194
- const callbackMatch = methodCall.match(/(\w+)\([^)]+\)/);
195
- if (callbackMatch) return `${callbackMatch[1]}(callback)`;
196
- return methodCall;
197
- }
198
- function isConditional(line) {
199
- return /^(if|else|switch)\s*[\(\{]/.test(line.trim());
200
- }
201
- function isLoop(line) {
202
- return /^(for|while)\s*\(/.test(line.trim());
203
- }
204
- function isFunctionDeclaration(line) {
205
- const t = line.trim();
206
- return (
207
- // function foo() / async function foo()
208
- /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
209
- /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
210
- /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
211
- );
212
- }
213
- function shouldIgnore(line) {
214
- const ignorePatterns = [
215
- /^import\s+/,
216
- /^export\s+/,
217
- /^type\s+/,
218
- /^interface\s+/,
219
- /^console\./,
220
- /^return$/,
221
- /^throw\s+/
222
- ];
223
- return ignorePatterns.some((p) => p.test(line.trim()));
224
- }
225
- function extractRoot(line) {
226
- const assignMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(\w+)/);
227
- if (assignMatch) return assignMatch[2];
228
- const callMatch = line.match(/^(\w+)\(/);
229
- if (callMatch) return `${callMatch[1]}()`;
230
- const methodMatch = line.match(/^(\w+)\./);
231
- if (methodMatch) return methodMatch[1];
232
- return null;
233
- }
234
- function parseToFlowTree(lines) {
235
- const roots = [];
236
- let currentChain = null;
237
- let prevLine = null;
238
- let baseDepth = -1;
239
- for (let i = 0; i < lines.length; i++) {
240
- const line = lines[i];
241
- if (shouldIgnore(line)) {
242
- prevLine = line;
243
- continue;
244
- }
245
- const depth = getIndentDepth(lines[i]);
246
- if (baseDepth === -1) baseDepth = depth;
247
- const relativeDepth = depth - baseDepth;
248
- if (isChaining(line, prevLine)) {
249
- const method = extractChainMethod(line);
250
- const simplified = simplifyCallback(method);
251
- if (currentChain) {
252
- const chainNode = {
253
- type: "chain",
254
- name: simplified,
255
- children: [],
256
- depth: relativeDepth,
257
- priority: 1 /* CHAINING */
258
- };
259
- let parent = currentChain;
260
- while (parent.children.length > 0 && parent.children[parent.children.length - 1].depth >= relativeDepth) {
261
- const last = parent.children[parent.children.length - 1];
262
- if (last.children.length > 0) parent = last;
263
- else break;
264
- }
265
- parent.children.push(chainNode);
168
+ 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*)?\(/;
170
+ const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
171
+ const extract = (lines) => {
172
+ const names = /* @__PURE__ */ new Set();
173
+ for (const line of lines) {
174
+ const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
175
+ if (cm) {
176
+ const name = cm[1] || cm[2];
177
+ if (name) names.add(name);
266
178
  }
267
- prevLine = line;
268
- continue;
269
- }
270
- if (isFunctionDeclaration(line)) {
271
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
272
- roots.push({
273
- type: "function",
274
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
275
- children: [],
276
- depth: relativeDepth,
277
- priority: 4 /* FUNCTION */
278
- });
279
- currentChain = null;
280
- prevLine = line;
281
- continue;
282
179
  }
283
- const root = extractRoot(line);
284
- if (root) {
285
- currentChain = {
286
- type: "root",
287
- name: root,
288
- children: [],
289
- depth: relativeDepth,
290
- priority: 1 /* CHAINING */
291
- };
292
- roots.push(currentChain);
293
- } else if (isConditional(line)) {
294
- const condMatch = line.match(/(if|else|switch)\s*\(([^)]+)\)/);
295
- const condName = condMatch ? `${condMatch[1]} (${condMatch[2]})` : line.trim();
296
- roots.push({
297
- type: "condition",
298
- name: condName,
299
- children: [],
300
- depth: relativeDepth,
301
- priority: 2 /* CONDITIONAL */
302
- });
303
- currentChain = null;
304
- } else if (isLoop(line)) {
305
- roots.push({
306
- type: "loop",
307
- name: "loop",
308
- children: [],
309
- depth: relativeDepth,
310
- priority: 3 /* LOOP */
311
- });
312
- currentChain = null;
180
+ return names;
181
+ };
182
+ const addedNames = extract(addedLines);
183
+ const removedNames = extract(removedLines);
184
+ const results = [];
185
+ const seen = /* @__PURE__ */ new Set();
186
+ for (const name of addedNames) {
187
+ seen.add(name);
188
+ results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
189
+ }
190
+ for (const name of removedNames) {
191
+ if (!seen.has(name)) {
192
+ results.push({ name, status: "removed" });
313
193
  }
314
- prevLine = line;
315
194
  }
316
- return roots;
195
+ return results;
317
196
  }
318
- function renderFlowTree(nodes, indent = 0) {
197
+ function renderJSXTreeCompact(nodes, maxDepth = 3) {
319
198
  const lines = [];
320
- const prefix = indent === 0 ? "" : " ".repeat((indent - 1) * 4) + " \u2514\u2500 ";
321
- for (const node of nodes) {
322
- lines.push(prefix + node.name);
323
- if (node.children.length > 0) {
324
- lines.push(...renderFlowTree(node.children, indent + 1));
199
+ function walk(node, depth) {
200
+ if (depth > maxDepth) return;
201
+ const indent = " ".repeat(depth);
202
+ const hasChildren = node.children.length > 0;
203
+ lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
204
+ for (const child of node.children) {
205
+ walk(child, depth + 1);
325
206
  }
326
207
  }
327
- return lines;
208
+ for (const root of nodes) {
209
+ walk(root, 0);
210
+ }
211
+ return lines.join("\n");
328
212
  }
329
213
  function generateReaderMarkdown(diffText, meta = {}) {
330
214
  const { added, removed } = filterDiffLines(diffText);
331
215
  const isJSX = Boolean(
332
216
  meta.file && isJSXFile(meta.file) || hasJSXContent(added)
333
217
  );
334
- const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
335
- const normalizedAdded = normalizeCode(addedForFlow);
336
- const flowTree = parseToFlowTree(normalizedAdded);
337
- const rawCode = addedForFlow.join("\n");
338
- const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
339
- const removedCode = removedForCode.join("\n");
218
+ const changedSymbols = extractChangedSymbols(added, removed);
340
219
  const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
341
220
  const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
342
221
  const sections = [];
343
- const lang = isJSX ? "tsx" : "typescript";
344
222
  sections.push("# \u{1F4D6} GitHub Reader View\n");
345
223
  sections.push("> Generated by **github-mobile-reader**");
346
224
  if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
@@ -348,37 +226,27 @@ function generateReaderMarkdown(diffText, meta = {}) {
348
226
  if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
349
227
  if (meta.file) sections.push(`> File: \`${meta.file}\``);
350
228
  sections.push("\n");
351
- if (flowTree.length > 0) {
352
- sections.push("## \u{1F9E0} Logical Flow\n");
353
- sections.push("```");
354
- sections.push(...renderFlowTree(flowTree));
355
- sections.push("```\n");
229
+ if (changedSymbols.length > 0) {
230
+ sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
231
+ const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
232
+ for (const { name, status } of changedSymbols) {
233
+ sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
234
+ }
235
+ sections.push("");
356
236
  }
357
237
  if (isJSX && jsxTree.length > 0) {
358
- sections.push("## \u{1F3A8} JSX Structure\n");
238
+ sections.push("### \u{1F3A8} JSX Structure\n");
359
239
  sections.push("```");
360
- sections.push(...renderFlowTree(jsxTree));
240
+ sections.push(renderJSXTreeCompact(jsxTree));
361
241
  sections.push("```\n");
362
242
  }
363
243
  if (isJSX && classNameChanges.length > 0) {
364
- sections.push("## \u{1F485} Style Changes\n");
244
+ sections.push("### \u{1F485} Style Changes\n");
365
245
  sections.push(...renderStyleChanges(classNameChanges));
366
246
  sections.push("");
367
247
  }
368
- if (rawCode.trim()) {
369
- sections.push("## \u2705 Added Code\n");
370
- sections.push(`\`\`\`${lang}`);
371
- sections.push(rawCode);
372
- sections.push("```\n");
373
- }
374
- if (removedCode.trim()) {
375
- sections.push("## \u274C Removed Code\n");
376
- sections.push(`\`\`\`${lang}`);
377
- sections.push(removedCode);
378
- sections.push("```\n");
379
- }
380
248
  sections.push("---");
381
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/your-org/github-mobile-reader). Do not edit manually.");
249
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
382
250
  return sections.join("\n");
383
251
  }
384
252
 
package/dist/cli.js CHANGED
@@ -74,6 +74,7 @@ function parseClassNameChanges(addedLines, removedLines) {
74
74
  }
75
75
  const changes = [];
76
76
  for (const [comp, { added, removed }] of componentMap) {
77
+ if (comp === "unknown") continue;
77
78
  const pureAdded = [...added].filter((c) => !removed.has(c));
78
79
  const pureRemoved = [...removed].filter((c) => !added.has(c));
79
80
  if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
@@ -159,188 +160,65 @@ function filterDiffLines(diffText) {
159
160
  const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
160
161
  return { added, removed };
161
162
  }
162
- function normalizeCode(lines) {
163
- return lines.map((line) => {
164
- let normalized = line;
165
- normalized = normalized.replace(/\/\/.*$/, "");
166
- normalized = normalized.replace(/\/\*.*?\*\//, "");
167
- normalized = normalized.trim();
168
- normalized = normalized.replace(/;$/, "");
169
- return normalized;
170
- }).filter((line) => line.length > 0);
171
- }
172
163
  function getIndentDepth(line) {
173
164
  const match = line.match(/^(\s*)/);
174
165
  if (!match) return 0;
175
166
  return Math.floor(match[1].length / 2);
176
167
  }
177
- function isChaining(line, prevLine) {
178
- if (!prevLine) return false;
179
- if (!line.trim().startsWith(".")) return false;
180
- if (!prevLine.match(/[)\}]$/)) return false;
181
- return true;
182
- }
183
- function extractChainMethod(line) {
184
- const match = line.match(/\.(\w+)\(/);
185
- if (match) return `${match[1]}()`;
186
- return line.trim();
187
- }
188
- function simplifyCallback(methodCall) {
189
- const arrowMatch = methodCall.match(/(\w+)\((\w+)\s*=>\s*(\w+)\.(\w+)\)/);
190
- if (arrowMatch) {
191
- const [, method, param, , prop] = arrowMatch;
192
- return `${method}(${param} \u2192 ${prop})`;
193
- }
194
- const callbackMatch = methodCall.match(/(\w+)\([^)]+\)/);
195
- if (callbackMatch) return `${callbackMatch[1]}(callback)`;
196
- return methodCall;
197
- }
198
- function isConditional(line) {
199
- return /^(if|else|switch)\s*[\(\{]/.test(line.trim());
200
- }
201
- function isLoop(line) {
202
- return /^(for|while)\s*\(/.test(line.trim());
203
- }
204
- function isFunctionDeclaration(line) {
205
- const t = line.trim();
206
- return (
207
- // function foo() / async function foo()
208
- /^(async\s+)?function\s+\w+/.test(t) || // const foo = () => / const foo = async () => / const foo = async (x: T) =>
209
- /^(const|let|var)\s+\w+\s*=\s*(async\s*)?\(/.test(t) || // const foo = function / const foo = async function
210
- /^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(t)
211
- );
212
- }
213
- function shouldIgnore(line) {
214
- const ignorePatterns = [
215
- /^import\s+/,
216
- /^export\s+/,
217
- /^type\s+/,
218
- /^interface\s+/,
219
- /^console\./,
220
- /^return$/,
221
- /^throw\s+/
222
- ];
223
- return ignorePatterns.some((p) => p.test(line.trim()));
224
- }
225
- function extractRoot(line) {
226
- const assignMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(\w+)/);
227
- if (assignMatch) return assignMatch[2];
228
- const callMatch = line.match(/^(\w+)\(/);
229
- if (callMatch) return `${callMatch[1]}()`;
230
- const methodMatch = line.match(/^(\w+)\./);
231
- if (methodMatch) return methodMatch[1];
232
- return null;
233
- }
234
- function parseToFlowTree(lines) {
235
- const roots = [];
236
- let currentChain = null;
237
- let prevLine = null;
238
- let baseDepth = -1;
239
- for (let i = 0; i < lines.length; i++) {
240
- const line = lines[i];
241
- if (shouldIgnore(line)) {
242
- prevLine = line;
243
- continue;
244
- }
245
- const depth = getIndentDepth(lines[i]);
246
- if (baseDepth === -1) baseDepth = depth;
247
- const relativeDepth = depth - baseDepth;
248
- if (isChaining(line, prevLine)) {
249
- const method = extractChainMethod(line);
250
- const simplified = simplifyCallback(method);
251
- if (currentChain) {
252
- const chainNode = {
253
- type: "chain",
254
- name: simplified,
255
- children: [],
256
- depth: relativeDepth,
257
- priority: 1 /* CHAINING */
258
- };
259
- let parent = currentChain;
260
- while (parent.children.length > 0 && parent.children[parent.children.length - 1].depth >= relativeDepth) {
261
- const last = parent.children[parent.children.length - 1];
262
- if (last.children.length > 0) parent = last;
263
- else break;
264
- }
265
- parent.children.push(chainNode);
168
+ 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*)?\(/;
170
+ const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
171
+ const extract = (lines) => {
172
+ const names = /* @__PURE__ */ new Set();
173
+ for (const line of lines) {
174
+ const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
175
+ if (cm) {
176
+ const name = cm[1] || cm[2];
177
+ if (name) names.add(name);
266
178
  }
267
- prevLine = line;
268
- continue;
269
179
  }
270
- if (isFunctionDeclaration(line)) {
271
- const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
272
- roots.push({
273
- type: "function",
274
- name: funcMatch ? `${funcMatch[1]}()` : "function()",
275
- children: [],
276
- depth: relativeDepth,
277
- priority: 4 /* FUNCTION */
278
- });
279
- currentChain = null;
280
- prevLine = line;
281
- continue;
282
- }
283
- const root = extractRoot(line);
284
- if (root) {
285
- currentChain = {
286
- type: "root",
287
- name: root,
288
- children: [],
289
- depth: relativeDepth,
290
- priority: 1 /* CHAINING */
291
- };
292
- roots.push(currentChain);
293
- } else if (isConditional(line)) {
294
- const condMatch = line.match(/(if|else|switch)\s*\(([^)]+)\)/);
295
- const condName = condMatch ? `${condMatch[1]} (${condMatch[2]})` : line.trim();
296
- roots.push({
297
- type: "condition",
298
- name: condName,
299
- children: [],
300
- depth: relativeDepth,
301
- priority: 2 /* CONDITIONAL */
302
- });
303
- currentChain = null;
304
- } else if (isLoop(line)) {
305
- roots.push({
306
- type: "loop",
307
- name: "loop",
308
- children: [],
309
- depth: relativeDepth,
310
- priority: 3 /* LOOP */
311
- });
312
- currentChain = null;
180
+ return names;
181
+ };
182
+ const addedNames = extract(addedLines);
183
+ const removedNames = extract(removedLines);
184
+ const results = [];
185
+ const seen = /* @__PURE__ */ new Set();
186
+ for (const name of addedNames) {
187
+ seen.add(name);
188
+ results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
189
+ }
190
+ for (const name of removedNames) {
191
+ if (!seen.has(name)) {
192
+ results.push({ name, status: "removed" });
313
193
  }
314
- prevLine = line;
315
194
  }
316
- return roots;
195
+ return results;
317
196
  }
318
- function renderFlowTree(nodes, indent = 0) {
197
+ function renderJSXTreeCompact(nodes, maxDepth = 3) {
319
198
  const lines = [];
320
- const prefix = indent === 0 ? "" : " ".repeat((indent - 1) * 4) + " \u2514\u2500 ";
321
- for (const node of nodes) {
322
- lines.push(prefix + node.name);
323
- if (node.children.length > 0) {
324
- lines.push(...renderFlowTree(node.children, indent + 1));
199
+ function walk(node, depth) {
200
+ if (depth > maxDepth) return;
201
+ const indent = " ".repeat(depth);
202
+ const hasChildren = node.children.length > 0;
203
+ lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
204
+ for (const child of node.children) {
205
+ walk(child, depth + 1);
325
206
  }
326
207
  }
327
- return lines;
208
+ for (const root of nodes) {
209
+ walk(root, 0);
210
+ }
211
+ return lines.join("\n");
328
212
  }
329
213
  function generateReaderMarkdown(diffText, meta = {}) {
330
214
  const { added, removed } = filterDiffLines(diffText);
331
215
  const isJSX = Boolean(
332
216
  meta.file && isJSXFile(meta.file) || hasJSXContent(added)
333
217
  );
334
- const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
335
- const normalizedAdded = normalizeCode(addedForFlow);
336
- const flowTree = parseToFlowTree(normalizedAdded);
337
- const rawCode = addedForFlow.join("\n");
338
- const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
339
- const removedCode = removedForCode.join("\n");
218
+ const changedSymbols = extractChangedSymbols(added, removed);
340
219
  const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
341
220
  const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
342
221
  const sections = [];
343
- const lang = isJSX ? "tsx" : "typescript";
344
222
  sections.push("# \u{1F4D6} GitHub Reader View\n");
345
223
  sections.push("> Generated by **github-mobile-reader**");
346
224
  if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
@@ -348,37 +226,27 @@ function generateReaderMarkdown(diffText, meta = {}) {
348
226
  if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
349
227
  if (meta.file) sections.push(`> File: \`${meta.file}\``);
350
228
  sections.push("\n");
351
- if (flowTree.length > 0) {
352
- sections.push("## \u{1F9E0} Logical Flow\n");
353
- sections.push("```");
354
- sections.push(...renderFlowTree(flowTree));
355
- sections.push("```\n");
229
+ if (changedSymbols.length > 0) {
230
+ sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
231
+ const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
232
+ for (const { name, status } of changedSymbols) {
233
+ sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
234
+ }
235
+ sections.push("");
356
236
  }
357
237
  if (isJSX && jsxTree.length > 0) {
358
- sections.push("## \u{1F3A8} JSX Structure\n");
238
+ sections.push("### \u{1F3A8} JSX Structure\n");
359
239
  sections.push("```");
360
- sections.push(...renderFlowTree(jsxTree));
240
+ sections.push(renderJSXTreeCompact(jsxTree));
361
241
  sections.push("```\n");
362
242
  }
363
243
  if (isJSX && classNameChanges.length > 0) {
364
- sections.push("## \u{1F485} Style Changes\n");
244
+ sections.push("### \u{1F485} Style Changes\n");
365
245
  sections.push(...renderStyleChanges(classNameChanges));
366
246
  sections.push("");
367
247
  }
368
- if (rawCode.trim()) {
369
- sections.push("## \u2705 Added Code\n");
370
- sections.push(`\`\`\`${lang}`);
371
- sections.push(rawCode);
372
- sections.push("```\n");
373
- }
374
- if (removedCode.trim()) {
375
- sections.push("## \u274C Removed Code\n");
376
- sections.push(`\`\`\`${lang}`);
377
- sections.push(removedCode);
378
- sections.push("```\n");
379
- }
380
248
  sections.push("---");
381
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/your-org/github-mobile-reader). Do not edit manually.");
249
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
382
250
  return sections.join("\n");
383
251
  }
384
252
 
@@ -482,7 +350,8 @@ async function processPR(repo, prNumber, outDir, token) {
482
350
  file: filename,
483
351
  repo
484
352
  });
485
- const withoutHeader = section.replace(/^# 📖.*\n/, "").replace(/^> Generated by.*\n/m, "").replace(/^> Repository:.*\n/m, "").replace(/^> Pull Request:.*\n/m, "").replace(/^> Commit:.*\n/m, "").replace(/^> File:.*\n/m, "").replace(/^\n+/, "");
353
+ const withoutHeader = section.replace(/^# 📖.*\n/m, "").replace(/^> Generated by.*\n/m, "").replace(/^> Repository:.*\n/m, "").replace(/^> Pull Request:.*\n/m, "").replace(/^> Commit:.*\n/m, "").replace(/^> File:.*\n/m, "").replace(/^---\n/m, "").replace(/^🛠 Auto-generated.*\n?/m, "").replace(/^\n+/, "").trimEnd();
354
+ if (!withoutHeader) continue;
486
355
  sections.push(`## \u{1F4C4} \`${filename}\`
487
356
  `);
488
357
  sections.push(withoutHeader);
package/dist/index.d.mts CHANGED
@@ -68,9 +68,22 @@ declare function renderFlowTree(nodes: FlowNode[], indent?: number): string[];
68
68
  * Main entry: parse a raw diff string → ParseResult
69
69
  */
70
70
  declare function parseDiffToLogicalFlow(diffText: string): ParseResult;
71
+ /**
72
+ * Extract function/component names from lines with change status.
73
+ * Returns list of { name, status } where status is 'added' | 'removed' | 'modified'.
74
+ */
75
+ declare function extractChangedSymbols(addedLines: string[], removedLines: string[]): {
76
+ name: string;
77
+ status: 'added' | 'removed' | 'modified';
78
+ }[];
79
+ /**
80
+ * Render JSX tree as a single compact line: div > header > button(onClick)
81
+ * Falls back to multi-line for deep trees.
82
+ */
83
+ declare function renderJSXTreeCompact(nodes: FlowNode[], maxDepth?: number): string;
71
84
  /**
72
85
  * Generate the complete Reader Markdown document
73
86
  */
74
87
  declare function generateReaderMarkdown(diffText: string, meta?: ReaderMarkdownMeta): string;
75
88
 
76
- export { type ClassNameChange, type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, extractClassName, extractJSXComponentName, filterDiffLines, generateReaderMarkdown, hasJSXContent, isClassNameOnlyLine, isJSXElement, isJSXFile, normalizeCode, parseClassNameChanges, parseDiffToLogicalFlow, parseJSXToFlowTree, parseToFlowTree, renderFlowTree, renderStyleChanges };
89
+ export { type ClassNameChange, type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, extractChangedSymbols, extractClassName, extractJSXComponentName, filterDiffLines, generateReaderMarkdown, hasJSXContent, isClassNameOnlyLine, isJSXElement, isJSXFile, normalizeCode, parseClassNameChanges, parseDiffToLogicalFlow, parseJSXToFlowTree, parseToFlowTree, renderFlowTree, renderJSXTreeCompact, renderStyleChanges };
package/dist/index.d.ts CHANGED
@@ -68,9 +68,22 @@ declare function renderFlowTree(nodes: FlowNode[], indent?: number): string[];
68
68
  * Main entry: parse a raw diff string → ParseResult
69
69
  */
70
70
  declare function parseDiffToLogicalFlow(diffText: string): ParseResult;
71
+ /**
72
+ * Extract function/component names from lines with change status.
73
+ * Returns list of { name, status } where status is 'added' | 'removed' | 'modified'.
74
+ */
75
+ declare function extractChangedSymbols(addedLines: string[], removedLines: string[]): {
76
+ name: string;
77
+ status: 'added' | 'removed' | 'modified';
78
+ }[];
79
+ /**
80
+ * Render JSX tree as a single compact line: div > header > button(onClick)
81
+ * Falls back to multi-line for deep trees.
82
+ */
83
+ declare function renderJSXTreeCompact(nodes: FlowNode[], maxDepth?: number): string;
71
84
  /**
72
85
  * Generate the complete Reader Markdown document
73
86
  */
74
87
  declare function generateReaderMarkdown(diffText: string, meta?: ReaderMarkdownMeta): string;
75
88
 
76
- export { type ClassNameChange, type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, extractClassName, extractJSXComponentName, filterDiffLines, generateReaderMarkdown, hasJSXContent, isClassNameOnlyLine, isJSXElement, isJSXFile, normalizeCode, parseClassNameChanges, parseDiffToLogicalFlow, parseJSXToFlowTree, parseToFlowTree, renderFlowTree, renderStyleChanges };
89
+ export { type ClassNameChange, type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, extractChangedSymbols, extractClassName, extractJSXComponentName, filterDiffLines, generateReaderMarkdown, hasJSXContent, isClassNameOnlyLine, isJSXElement, isJSXFile, normalizeCode, parseClassNameChanges, parseDiffToLogicalFlow, parseJSXToFlowTree, parseToFlowTree, renderFlowTree, renderJSXTreeCompact, renderStyleChanges };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Priority: () => Priority,
24
+ extractChangedSymbols: () => extractChangedSymbols,
24
25
  extractClassName: () => extractClassName,
25
26
  extractJSXComponentName: () => extractJSXComponentName,
26
27
  filterDiffLines: () => filterDiffLines,
@@ -35,6 +36,7 @@ __export(index_exports, {
35
36
  parseJSXToFlowTree: () => parseJSXToFlowTree,
36
37
  parseToFlowTree: () => parseToFlowTree,
37
38
  renderFlowTree: () => renderFlowTree,
39
+ renderJSXTreeCompact: () => renderJSXTreeCompact,
38
40
  renderStyleChanges: () => renderStyleChanges
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
@@ -94,6 +96,7 @@ function parseClassNameChanges(addedLines, removedLines) {
94
96
  }
95
97
  const changes = [];
96
98
  for (const [comp, { added, removed }] of componentMap) {
99
+ if (comp === "unknown") continue;
97
100
  const pureAdded = [...added].filter((c) => !removed.has(c));
98
101
  const pureRemoved = [...removed].filter((c) => !added.has(c));
99
102
  if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
@@ -356,21 +359,60 @@ function parseDiffToLogicalFlow(diffText) {
356
359
  removedCode: removed.join("\n")
357
360
  };
358
361
  }
362
+ 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*)?\(/;
364
+ const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
365
+ const extract = (lines) => {
366
+ const names = /* @__PURE__ */ new Set();
367
+ for (const line of lines) {
368
+ const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
369
+ if (cm) {
370
+ const name = cm[1] || cm[2];
371
+ if (name) names.add(name);
372
+ }
373
+ }
374
+ return names;
375
+ };
376
+ const addedNames = extract(addedLines);
377
+ const removedNames = extract(removedLines);
378
+ const results = [];
379
+ const seen = /* @__PURE__ */ new Set();
380
+ for (const name of addedNames) {
381
+ seen.add(name);
382
+ results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
383
+ }
384
+ for (const name of removedNames) {
385
+ if (!seen.has(name)) {
386
+ results.push({ name, status: "removed" });
387
+ }
388
+ }
389
+ return results;
390
+ }
391
+ function renderJSXTreeCompact(nodes, maxDepth = 3) {
392
+ const lines = [];
393
+ function walk(node, depth) {
394
+ if (depth > maxDepth) return;
395
+ const indent = " ".repeat(depth);
396
+ const hasChildren = node.children.length > 0;
397
+ lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
398
+ for (const child of node.children) {
399
+ walk(child, depth + 1);
400
+ }
401
+ }
402
+ for (const root of nodes) {
403
+ walk(root, 0);
404
+ }
405
+ return lines.join("\n");
406
+ }
359
407
  function generateReaderMarkdown(diffText, meta = {}) {
360
408
  const { added, removed } = filterDiffLines(diffText);
361
409
  const isJSX = Boolean(
362
410
  meta.file && isJSXFile(meta.file) || hasJSXContent(added)
363
411
  );
364
- const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
365
- const normalizedAdded = normalizeCode(addedForFlow);
366
- const flowTree = parseToFlowTree(normalizedAdded);
367
- const rawCode = addedForFlow.join("\n");
368
- const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
369
- const removedCode = removedForCode.join("\n");
412
+ const changedSymbols = extractChangedSymbols(added, removed);
370
413
  const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
371
414
  const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
372
415
  const sections = [];
373
- const lang = isJSX ? "tsx" : "typescript";
374
416
  sections.push("# \u{1F4D6} GitHub Reader View\n");
375
417
  sections.push("> Generated by **github-mobile-reader**");
376
418
  if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
@@ -378,42 +420,33 @@ function generateReaderMarkdown(diffText, meta = {}) {
378
420
  if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
379
421
  if (meta.file) sections.push(`> File: \`${meta.file}\``);
380
422
  sections.push("\n");
381
- if (flowTree.length > 0) {
382
- sections.push("## \u{1F9E0} Logical Flow\n");
383
- sections.push("```");
384
- sections.push(...renderFlowTree(flowTree));
385
- sections.push("```\n");
423
+ if (changedSymbols.length > 0) {
424
+ sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
425
+ const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
426
+ for (const { name, status } of changedSymbols) {
427
+ sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
428
+ }
429
+ sections.push("");
386
430
  }
387
431
  if (isJSX && jsxTree.length > 0) {
388
- sections.push("## \u{1F3A8} JSX Structure\n");
432
+ sections.push("### \u{1F3A8} JSX Structure\n");
389
433
  sections.push("```");
390
- sections.push(...renderFlowTree(jsxTree));
434
+ sections.push(renderJSXTreeCompact(jsxTree));
391
435
  sections.push("```\n");
392
436
  }
393
437
  if (isJSX && classNameChanges.length > 0) {
394
- sections.push("## \u{1F485} Style Changes\n");
438
+ sections.push("### \u{1F485} Style Changes\n");
395
439
  sections.push(...renderStyleChanges(classNameChanges));
396
440
  sections.push("");
397
441
  }
398
- if (rawCode.trim()) {
399
- sections.push("## \u2705 Added Code\n");
400
- sections.push(`\`\`\`${lang}`);
401
- sections.push(rawCode);
402
- sections.push("```\n");
403
- }
404
- if (removedCode.trim()) {
405
- sections.push("## \u274C Removed Code\n");
406
- sections.push(`\`\`\`${lang}`);
407
- sections.push(removedCode);
408
- sections.push("```\n");
409
- }
410
442
  sections.push("---");
411
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/your-org/github-mobile-reader). Do not edit manually.");
443
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
412
444
  return sections.join("\n");
413
445
  }
414
446
  // Annotate the CommonJS export names for ESM import in node:
415
447
  0 && (module.exports = {
416
448
  Priority,
449
+ extractChangedSymbols,
417
450
  extractClassName,
418
451
  extractJSXComponentName,
419
452
  filterDiffLines,
@@ -428,5 +461,6 @@ function generateReaderMarkdown(diffText, meta = {}) {
428
461
  parseJSXToFlowTree,
429
462
  parseToFlowTree,
430
463
  renderFlowTree,
464
+ renderJSXTreeCompact,
431
465
  renderStyleChanges
432
466
  });
package/dist/index.mjs CHANGED
@@ -53,6 +53,7 @@ function parseClassNameChanges(addedLines, removedLines) {
53
53
  }
54
54
  const changes = [];
55
55
  for (const [comp, { added, removed }] of componentMap) {
56
+ if (comp === "unknown") continue;
56
57
  const pureAdded = [...added].filter((c) => !removed.has(c));
57
58
  const pureRemoved = [...removed].filter((c) => !added.has(c));
58
59
  if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
@@ -315,21 +316,60 @@ function parseDiffToLogicalFlow(diffText) {
315
316
  removedCode: removed.join("\n")
316
317
  };
317
318
  }
319
+ 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*)?\(/;
321
+ const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
322
+ const extract = (lines) => {
323
+ const names = /* @__PURE__ */ new Set();
324
+ for (const line of lines) {
325
+ const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
326
+ if (cm) {
327
+ const name = cm[1] || cm[2];
328
+ if (name) names.add(name);
329
+ }
330
+ }
331
+ return names;
332
+ };
333
+ const addedNames = extract(addedLines);
334
+ const removedNames = extract(removedLines);
335
+ const results = [];
336
+ const seen = /* @__PURE__ */ new Set();
337
+ for (const name of addedNames) {
338
+ seen.add(name);
339
+ results.push({ name, status: removedNames.has(name) ? "modified" : "added" });
340
+ }
341
+ for (const name of removedNames) {
342
+ if (!seen.has(name)) {
343
+ results.push({ name, status: "removed" });
344
+ }
345
+ }
346
+ return results;
347
+ }
348
+ function renderJSXTreeCompact(nodes, maxDepth = 3) {
349
+ const lines = [];
350
+ function walk(node, depth) {
351
+ if (depth > maxDepth) return;
352
+ const indent = " ".repeat(depth);
353
+ const hasChildren = node.children.length > 0;
354
+ lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
355
+ for (const child of node.children) {
356
+ walk(child, depth + 1);
357
+ }
358
+ }
359
+ for (const root of nodes) {
360
+ walk(root, 0);
361
+ }
362
+ return lines.join("\n");
363
+ }
318
364
  function generateReaderMarkdown(diffText, meta = {}) {
319
365
  const { added, removed } = filterDiffLines(diffText);
320
366
  const isJSX = Boolean(
321
367
  meta.file && isJSXFile(meta.file) || hasJSXContent(added)
322
368
  );
323
- const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
324
- const normalizedAdded = normalizeCode(addedForFlow);
325
- const flowTree = parseToFlowTree(normalizedAdded);
326
- const rawCode = addedForFlow.join("\n");
327
- const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
328
- const removedCode = removedForCode.join("\n");
369
+ const changedSymbols = extractChangedSymbols(added, removed);
329
370
  const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
330
371
  const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
331
372
  const sections = [];
332
- const lang = isJSX ? "tsx" : "typescript";
333
373
  sections.push("# \u{1F4D6} GitHub Reader View\n");
334
374
  sections.push("> Generated by **github-mobile-reader**");
335
375
  if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
@@ -337,41 +377,32 @@ function generateReaderMarkdown(diffText, meta = {}) {
337
377
  if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
338
378
  if (meta.file) sections.push(`> File: \`${meta.file}\``);
339
379
  sections.push("\n");
340
- if (flowTree.length > 0) {
341
- sections.push("## \u{1F9E0} Logical Flow\n");
342
- sections.push("```");
343
- sections.push(...renderFlowTree(flowTree));
344
- sections.push("```\n");
380
+ if (changedSymbols.length > 0) {
381
+ sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
382
+ const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
383
+ for (const { name, status } of changedSymbols) {
384
+ sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
385
+ }
386
+ sections.push("");
345
387
  }
346
388
  if (isJSX && jsxTree.length > 0) {
347
- sections.push("## \u{1F3A8} JSX Structure\n");
389
+ sections.push("### \u{1F3A8} JSX Structure\n");
348
390
  sections.push("```");
349
- sections.push(...renderFlowTree(jsxTree));
391
+ sections.push(renderJSXTreeCompact(jsxTree));
350
392
  sections.push("```\n");
351
393
  }
352
394
  if (isJSX && classNameChanges.length > 0) {
353
- sections.push("## \u{1F485} Style Changes\n");
395
+ sections.push("### \u{1F485} Style Changes\n");
354
396
  sections.push(...renderStyleChanges(classNameChanges));
355
397
  sections.push("");
356
398
  }
357
- if (rawCode.trim()) {
358
- sections.push("## \u2705 Added Code\n");
359
- sections.push(`\`\`\`${lang}`);
360
- sections.push(rawCode);
361
- sections.push("```\n");
362
- }
363
- if (removedCode.trim()) {
364
- sections.push("## \u274C Removed Code\n");
365
- sections.push(`\`\`\`${lang}`);
366
- sections.push(removedCode);
367
- sections.push("```\n");
368
- }
369
399
  sections.push("---");
370
- sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/your-org/github-mobile-reader). Do not edit manually.");
400
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
371
401
  return sections.join("\n");
372
402
  }
373
403
  export {
374
404
  Priority,
405
+ extractChangedSymbols,
375
406
  extractClassName,
376
407
  extractJSXComponentName,
377
408
  filterDiffLines,
@@ -386,5 +417,6 @@ export {
386
417
  parseJSXToFlowTree,
387
418
  parseToFlowTree,
388
419
  renderFlowTree,
420
+ renderJSXTreeCompact,
389
421
  renderStyleChanges
390
422
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-mobile-reader",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",