github-mobile-reader 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +74 -65
- package/README.md +73 -66
- package/dist/action.js +72 -188
- package/dist/cli.js +74 -189
- package/dist/index.d.mts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +85 -35
- package/dist/index.mjs +83 -35
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32,7 +32,9 @@ function isJSXFile(filename) {
|
|
|
32
32
|
return /\.(jsx|tsx)$/.test(filename);
|
|
33
33
|
}
|
|
34
34
|
function hasJSXContent(lines) {
|
|
35
|
-
return lines.some(
|
|
35
|
+
return lines.some(
|
|
36
|
+
(l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l)
|
|
37
|
+
);
|
|
36
38
|
}
|
|
37
39
|
function isClassNameOnlyLine(line) {
|
|
38
40
|
return /^className=/.test(line.trim());
|
|
@@ -40,7 +42,9 @@ function isClassNameOnlyLine(line) {
|
|
|
40
42
|
function extractClassName(line) {
|
|
41
43
|
const staticMatch = line.match(/className="([^"]*)"/);
|
|
42
44
|
if (staticMatch) return staticMatch[1];
|
|
43
|
-
const ternaryMatch = line.match(
|
|
45
|
+
const ternaryMatch = line.match(
|
|
46
|
+
/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/
|
|
47
|
+
);
|
|
44
48
|
if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
|
|
45
49
|
const templateMatch = line.match(/className=\{`([^`]*)`\}/);
|
|
46
50
|
if (templateMatch) {
|
|
@@ -62,18 +66,21 @@ function parseClassNameChanges(addedLines, removedLines) {
|
|
|
62
66
|
const cls = extractClassName(line);
|
|
63
67
|
const comp = extractComponentFromLine(line);
|
|
64
68
|
if (!cls) continue;
|
|
65
|
-
if (!componentMap.has(comp))
|
|
69
|
+
if (!componentMap.has(comp))
|
|
70
|
+
componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
66
71
|
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
|
|
67
72
|
}
|
|
68
73
|
for (const line of removedLines.filter((l) => /className=/.test(l))) {
|
|
69
74
|
const cls = extractClassName(line);
|
|
70
75
|
const comp = extractComponentFromLine(line);
|
|
71
76
|
if (!cls) continue;
|
|
72
|
-
if (!componentMap.has(comp))
|
|
77
|
+
if (!componentMap.has(comp))
|
|
78
|
+
componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
73
79
|
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
|
|
74
80
|
}
|
|
75
81
|
const changes = [];
|
|
76
82
|
for (const [comp, { added, removed }] of componentMap) {
|
|
83
|
+
if (comp === "unknown") continue;
|
|
77
84
|
const pureAdded = [...added].filter((c) => !removed.has(c));
|
|
78
85
|
const pureRemoved = [...removed].filter((c) => !added.has(c));
|
|
79
86
|
if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
|
|
@@ -86,7 +93,8 @@ function renderStyleChanges(changes) {
|
|
|
86
93
|
for (const change of changes) {
|
|
87
94
|
lines.push(`**${change.component}**`);
|
|
88
95
|
if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
|
|
89
|
-
if (change.removed.length > 0)
|
|
96
|
+
if (change.removed.length > 0)
|
|
97
|
+
lines.push(` - ${change.removed.join(" ")}`);
|
|
90
98
|
}
|
|
91
99
|
return lines;
|
|
92
100
|
}
|
|
@@ -155,192 +163,76 @@ function parseJSXToFlowTree(lines) {
|
|
|
155
163
|
}
|
|
156
164
|
function filterDiffLines(diffText) {
|
|
157
165
|
const lines = diffText.split("\n");
|
|
158
|
-
const added = lines.filter(
|
|
159
|
-
|
|
166
|
+
const added = lines.filter(
|
|
167
|
+
(l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+"
|
|
168
|
+
).map((l) => l.substring(1));
|
|
169
|
+
const removed = lines.filter(
|
|
170
|
+
(l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-"
|
|
171
|
+
).map((l) => l.substring(1));
|
|
160
172
|
return { added, removed };
|
|
161
173
|
}
|
|
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
174
|
function getIndentDepth(line) {
|
|
173
175
|
const match = line.match(/^(\s*)/);
|
|
174
176
|
if (!match) return 0;
|
|
175
177
|
return Math.floor(match[1].length / 2);
|
|
176
178
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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);
|
|
179
|
+
function extractChangedSymbols(addedLines, removedLines) {
|
|
180
|
+
const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s+)?\(?|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*[a-z]\w+\s*[<(]/;
|
|
181
|
+
const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
|
|
182
|
+
const extract = (lines) => {
|
|
183
|
+
const names = /* @__PURE__ */ new Set();
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
|
|
186
|
+
if (cm) {
|
|
187
|
+
const name = cm[1] || cm[2] || cm[3];
|
|
188
|
+
if (name) names.add(name);
|
|
266
189
|
}
|
|
267
|
-
prevLine = line;
|
|
268
|
-
continue;
|
|
269
190
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (
|
|
285
|
-
|
|
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;
|
|
191
|
+
return names;
|
|
192
|
+
};
|
|
193
|
+
const addedNames = extract(addedLines);
|
|
194
|
+
const removedNames = extract(removedLines);
|
|
195
|
+
const results = [];
|
|
196
|
+
const seen = /* @__PURE__ */ new Set();
|
|
197
|
+
for (const name of addedNames) {
|
|
198
|
+
seen.add(name);
|
|
199
|
+
results.push({
|
|
200
|
+
name,
|
|
201
|
+
status: removedNames.has(name) ? "modified" : "added"
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
for (const name of removedNames) {
|
|
205
|
+
if (!seen.has(name)) {
|
|
206
|
+
results.push({ name, status: "removed" });
|
|
313
207
|
}
|
|
314
|
-
prevLine = line;
|
|
315
208
|
}
|
|
316
|
-
return
|
|
209
|
+
return results;
|
|
317
210
|
}
|
|
318
|
-
function
|
|
211
|
+
function renderJSXTreeCompact(nodes, maxDepth = 3) {
|
|
319
212
|
const lines = [];
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
213
|
+
function walk(node, depth) {
|
|
214
|
+
if (depth > maxDepth) return;
|
|
215
|
+
const indent = " ".repeat(depth);
|
|
216
|
+
const hasChildren = node.children.length > 0;
|
|
217
|
+
lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
|
|
218
|
+
for (const child of node.children) {
|
|
219
|
+
walk(child, depth + 1);
|
|
325
220
|
}
|
|
326
221
|
}
|
|
327
|
-
|
|
222
|
+
for (const root of nodes) {
|
|
223
|
+
walk(root, 0);
|
|
224
|
+
}
|
|
225
|
+
return lines.join("\n");
|
|
328
226
|
}
|
|
329
227
|
function generateReaderMarkdown(diffText, meta = {}) {
|
|
330
228
|
const { added, removed } = filterDiffLines(diffText);
|
|
331
229
|
const isJSX = Boolean(
|
|
332
230
|
meta.file && isJSXFile(meta.file) || hasJSXContent(added)
|
|
333
231
|
);
|
|
334
|
-
const
|
|
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");
|
|
232
|
+
const changedSymbols = extractChangedSymbols(added, removed);
|
|
340
233
|
const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
|
|
341
234
|
const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
|
|
342
235
|
const sections = [];
|
|
343
|
-
const lang = isJSX ? "tsx" : "typescript";
|
|
344
236
|
sections.push("# \u{1F4D6} GitHub Reader View\n");
|
|
345
237
|
sections.push("> Generated by **github-mobile-reader**");
|
|
346
238
|
if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
|
|
@@ -348,37 +240,29 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
348
240
|
if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
|
|
349
241
|
if (meta.file) sections.push(`> File: \`${meta.file}\``);
|
|
350
242
|
sections.push("\n");
|
|
351
|
-
if (
|
|
352
|
-
sections.push("
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
243
|
+
if (changedSymbols.length > 0) {
|
|
244
|
+
sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
|
|
245
|
+
const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
|
|
246
|
+
for (const { name, status } of changedSymbols) {
|
|
247
|
+
sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
|
|
248
|
+
}
|
|
249
|
+
sections.push("");
|
|
356
250
|
}
|
|
357
251
|
if (isJSX && jsxTree.length > 0) {
|
|
358
|
-
sections.push("
|
|
252
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
359
253
|
sections.push("```");
|
|
360
|
-
sections.push(
|
|
254
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
361
255
|
sections.push("```\n");
|
|
362
256
|
}
|
|
363
257
|
if (isJSX && classNameChanges.length > 0) {
|
|
364
|
-
sections.push("
|
|
258
|
+
sections.push("### \u{1F485} Style Changes\n");
|
|
365
259
|
sections.push(...renderStyleChanges(classNameChanges));
|
|
366
260
|
sections.push("");
|
|
367
261
|
}
|
|
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
262
|
sections.push("---");
|
|
381
|
-
sections.push(
|
|
263
|
+
sections.push(
|
|
264
|
+
"\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually."
|
|
265
|
+
);
|
|
382
266
|
return sections.join("\n");
|
|
383
267
|
}
|
|
384
268
|
|
|
@@ -482,7 +366,8 @@ async function processPR(repo, prNumber, outDir, token) {
|
|
|
482
366
|
file: filename,
|
|
483
367
|
repo
|
|
484
368
|
});
|
|
485
|
-
const withoutHeader = section.replace(/^# 📖.*\n
|
|
369
|
+
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();
|
|
370
|
+
if (!withoutHeader) continue;
|
|
486
371
|
sections.push(`## \u{1F4C4} \`${filename}\`
|
|
487
372
|
`);
|
|
488
373
|
sections.push(withoutHeader);
|
package/dist/index.d.mts
CHANGED
|
@@ -14,7 +14,7 @@ declare enum Priority {
|
|
|
14
14
|
OTHER = 5
|
|
15
15
|
}
|
|
16
16
|
interface FlowNode {
|
|
17
|
-
type:
|
|
17
|
+
type: "root" | "chain" | "condition" | "loop" | "function" | "call";
|
|
18
18
|
name: string;
|
|
19
19
|
children: FlowNode[];
|
|
20
20
|
depth: number;
|
|
@@ -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
|
@@ -14,7 +14,7 @@ declare enum Priority {
|
|
|
14
14
|
OTHER = 5
|
|
15
15
|
}
|
|
16
16
|
interface FlowNode {
|
|
17
|
-
type:
|
|
17
|
+
type: "root" | "chain" | "condition" | "loop" | "function" | "call";
|
|
18
18
|
name: string;
|
|
19
19
|
children: FlowNode[];
|
|
20
20
|
depth: number;
|
|
@@ -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);
|
|
@@ -52,7 +54,9 @@ function isJSXFile(filename) {
|
|
|
52
54
|
return /\.(jsx|tsx)$/.test(filename);
|
|
53
55
|
}
|
|
54
56
|
function hasJSXContent(lines) {
|
|
55
|
-
return lines.some(
|
|
57
|
+
return lines.some(
|
|
58
|
+
(l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l)
|
|
59
|
+
);
|
|
56
60
|
}
|
|
57
61
|
function isClassNameOnlyLine(line) {
|
|
58
62
|
return /^className=/.test(line.trim());
|
|
@@ -60,7 +64,9 @@ function isClassNameOnlyLine(line) {
|
|
|
60
64
|
function extractClassName(line) {
|
|
61
65
|
const staticMatch = line.match(/className="([^"]*)"/);
|
|
62
66
|
if (staticMatch) return staticMatch[1];
|
|
63
|
-
const ternaryMatch = line.match(
|
|
67
|
+
const ternaryMatch = line.match(
|
|
68
|
+
/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/
|
|
69
|
+
);
|
|
64
70
|
if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
|
|
65
71
|
const templateMatch = line.match(/className=\{`([^`]*)`\}/);
|
|
66
72
|
if (templateMatch) {
|
|
@@ -82,18 +88,21 @@ function parseClassNameChanges(addedLines, removedLines) {
|
|
|
82
88
|
const cls = extractClassName(line);
|
|
83
89
|
const comp = extractComponentFromLine(line);
|
|
84
90
|
if (!cls) continue;
|
|
85
|
-
if (!componentMap.has(comp))
|
|
91
|
+
if (!componentMap.has(comp))
|
|
92
|
+
componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
86
93
|
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
|
|
87
94
|
}
|
|
88
95
|
for (const line of removedLines.filter((l) => /className=/.test(l))) {
|
|
89
96
|
const cls = extractClassName(line);
|
|
90
97
|
const comp = extractComponentFromLine(line);
|
|
91
98
|
if (!cls) continue;
|
|
92
|
-
if (!componentMap.has(comp))
|
|
99
|
+
if (!componentMap.has(comp))
|
|
100
|
+
componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
93
101
|
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
|
|
94
102
|
}
|
|
95
103
|
const changes = [];
|
|
96
104
|
for (const [comp, { added, removed }] of componentMap) {
|
|
105
|
+
if (comp === "unknown") continue;
|
|
97
106
|
const pureAdded = [...added].filter((c) => !removed.has(c));
|
|
98
107
|
const pureRemoved = [...removed].filter((c) => !added.has(c));
|
|
99
108
|
if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
|
|
@@ -106,7 +115,8 @@ function renderStyleChanges(changes) {
|
|
|
106
115
|
for (const change of changes) {
|
|
107
116
|
lines.push(`**${change.component}**`);
|
|
108
117
|
if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
|
|
109
|
-
if (change.removed.length > 0)
|
|
118
|
+
if (change.removed.length > 0)
|
|
119
|
+
lines.push(` - ${change.removed.join(" ")}`);
|
|
110
120
|
}
|
|
111
121
|
return lines;
|
|
112
122
|
}
|
|
@@ -175,8 +185,12 @@ function parseJSXToFlowTree(lines) {
|
|
|
175
185
|
}
|
|
176
186
|
function filterDiffLines(diffText) {
|
|
177
187
|
const lines = diffText.split("\n");
|
|
178
|
-
const added = lines.filter(
|
|
179
|
-
|
|
188
|
+
const added = lines.filter(
|
|
189
|
+
(l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+"
|
|
190
|
+
).map((l) => l.substring(1));
|
|
191
|
+
const removed = lines.filter(
|
|
192
|
+
(l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-"
|
|
193
|
+
).map((l) => l.substring(1));
|
|
180
194
|
return { added, removed };
|
|
181
195
|
}
|
|
182
196
|
function normalizeCode(lines) {
|
|
@@ -356,21 +370,63 @@ function parseDiffToLogicalFlow(diffText) {
|
|
|
356
370
|
removedCode: removed.join("\n")
|
|
357
371
|
};
|
|
358
372
|
}
|
|
373
|
+
function extractChangedSymbols(addedLines, removedLines) {
|
|
374
|
+
const FUNC_RE = /^(?:export\s+)?(?:async\s+)?function\s+([a-z]\w+)|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*(?:async\s+)?\(?|^(?:export\s+)?(?:const|let|var)\s+([a-z]\w+)\s*=\s*[a-z]\w+\s*[<(]/;
|
|
375
|
+
const COMPONENT_RE = /^(?:export\s+)?(?:default\s+)?(?:function|const)\s+([A-Z][a-z][A-Za-z0-9]*)/;
|
|
376
|
+
const extract = (lines) => {
|
|
377
|
+
const names = /* @__PURE__ */ new Set();
|
|
378
|
+
for (const line of lines) {
|
|
379
|
+
const cm = line.match(COMPONENT_RE) || line.match(FUNC_RE);
|
|
380
|
+
if (cm) {
|
|
381
|
+
const name = cm[1] || cm[2] || cm[3];
|
|
382
|
+
if (name) names.add(name);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return names;
|
|
386
|
+
};
|
|
387
|
+
const addedNames = extract(addedLines);
|
|
388
|
+
const removedNames = extract(removedLines);
|
|
389
|
+
const results = [];
|
|
390
|
+
const seen = /* @__PURE__ */ new Set();
|
|
391
|
+
for (const name of addedNames) {
|
|
392
|
+
seen.add(name);
|
|
393
|
+
results.push({
|
|
394
|
+
name,
|
|
395
|
+
status: removedNames.has(name) ? "modified" : "added"
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
for (const name of removedNames) {
|
|
399
|
+
if (!seen.has(name)) {
|
|
400
|
+
results.push({ name, status: "removed" });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return results;
|
|
404
|
+
}
|
|
405
|
+
function renderJSXTreeCompact(nodes, maxDepth = 3) {
|
|
406
|
+
const lines = [];
|
|
407
|
+
function walk(node, depth) {
|
|
408
|
+
if (depth > maxDepth) return;
|
|
409
|
+
const indent = " ".repeat(depth);
|
|
410
|
+
const hasChildren = node.children.length > 0;
|
|
411
|
+
lines.push(`${indent}${node.name}${hasChildren ? "" : ""}`);
|
|
412
|
+
for (const child of node.children) {
|
|
413
|
+
walk(child, depth + 1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
for (const root of nodes) {
|
|
417
|
+
walk(root, 0);
|
|
418
|
+
}
|
|
419
|
+
return lines.join("\n");
|
|
420
|
+
}
|
|
359
421
|
function generateReaderMarkdown(diffText, meta = {}) {
|
|
360
422
|
const { added, removed } = filterDiffLines(diffText);
|
|
361
423
|
const isJSX = Boolean(
|
|
362
424
|
meta.file && isJSXFile(meta.file) || hasJSXContent(added)
|
|
363
425
|
);
|
|
364
|
-
const
|
|
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");
|
|
426
|
+
const changedSymbols = extractChangedSymbols(added, removed);
|
|
370
427
|
const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
|
|
371
428
|
const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
|
|
372
429
|
const sections = [];
|
|
373
|
-
const lang = isJSX ? "tsx" : "typescript";
|
|
374
430
|
sections.push("# \u{1F4D6} GitHub Reader View\n");
|
|
375
431
|
sections.push("> Generated by **github-mobile-reader**");
|
|
376
432
|
if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
|
|
@@ -378,42 +434,35 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
378
434
|
if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
|
|
379
435
|
if (meta.file) sections.push(`> File: \`${meta.file}\``);
|
|
380
436
|
sections.push("\n");
|
|
381
|
-
if (
|
|
382
|
-
sections.push("
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
437
|
+
if (changedSymbols.length > 0) {
|
|
438
|
+
sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
|
|
439
|
+
const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
|
|
440
|
+
for (const { name, status } of changedSymbols) {
|
|
441
|
+
sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
|
|
442
|
+
}
|
|
443
|
+
sections.push("");
|
|
386
444
|
}
|
|
387
445
|
if (isJSX && jsxTree.length > 0) {
|
|
388
|
-
sections.push("
|
|
446
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
389
447
|
sections.push("```");
|
|
390
|
-
sections.push(
|
|
448
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
391
449
|
sections.push("```\n");
|
|
392
450
|
}
|
|
393
451
|
if (isJSX && classNameChanges.length > 0) {
|
|
394
|
-
sections.push("
|
|
452
|
+
sections.push("### \u{1F485} Style Changes\n");
|
|
395
453
|
sections.push(...renderStyleChanges(classNameChanges));
|
|
396
454
|
sections.push("");
|
|
397
455
|
}
|
|
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
456
|
sections.push("---");
|
|
411
|
-
sections.push(
|
|
457
|
+
sections.push(
|
|
458
|
+
"\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually."
|
|
459
|
+
);
|
|
412
460
|
return sections.join("\n");
|
|
413
461
|
}
|
|
414
462
|
// Annotate the CommonJS export names for ESM import in node:
|
|
415
463
|
0 && (module.exports = {
|
|
416
464
|
Priority,
|
|
465
|
+
extractChangedSymbols,
|
|
417
466
|
extractClassName,
|
|
418
467
|
extractJSXComponentName,
|
|
419
468
|
filterDiffLines,
|
|
@@ -428,5 +477,6 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
428
477
|
parseJSXToFlowTree,
|
|
429
478
|
parseToFlowTree,
|
|
430
479
|
renderFlowTree,
|
|
480
|
+
renderJSXTreeCompact,
|
|
431
481
|
renderStyleChanges
|
|
432
482
|
});
|