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 +1 -1
- package/README.md +1 -1
- package/dist/action.js +49 -181
- package/dist/cli.js +51 -182
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +62 -28
- package/dist/index.mjs +60 -28
- package/package.json +1 -1
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
|
|
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);
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
195
|
+
return results;
|
|
317
196
|
}
|
|
318
|
-
function
|
|
197
|
+
function renderJSXTreeCompact(nodes, maxDepth = 3) {
|
|
319
198
|
const lines = [];
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
352
|
-
sections.push("
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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("
|
|
238
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
359
239
|
sections.push("```");
|
|
360
|
-
sections.push(
|
|
240
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
361
241
|
sections.push("```\n");
|
|
362
242
|
}
|
|
363
243
|
if (isJSX && classNameChanges.length > 0) {
|
|
364
|
-
sections.push("
|
|
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/
|
|
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
|
|
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);
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
195
|
+
return results;
|
|
317
196
|
}
|
|
318
|
-
function
|
|
197
|
+
function renderJSXTreeCompact(nodes, maxDepth = 3) {
|
|
319
198
|
const lines = [];
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
352
|
-
sections.push("
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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("
|
|
238
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
359
239
|
sections.push("```");
|
|
360
|
-
sections.push(
|
|
240
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
361
241
|
sections.push("```\n");
|
|
362
242
|
}
|
|
363
243
|
if (isJSX && classNameChanges.length > 0) {
|
|
364
|
-
sections.push("
|
|
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/
|
|
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
|
|
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
|
|
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 (
|
|
382
|
-
sections.push("
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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("
|
|
432
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
389
433
|
sections.push("```");
|
|
390
|
-
sections.push(
|
|
434
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
391
435
|
sections.push("```\n");
|
|
392
436
|
}
|
|
393
437
|
if (isJSX && classNameChanges.length > 0) {
|
|
394
|
-
sections.push("
|
|
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/
|
|
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
|
|
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 (
|
|
341
|
-
sections.push("
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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("
|
|
389
|
+
sections.push("### \u{1F3A8} JSX Structure\n");
|
|
348
390
|
sections.push("```");
|
|
349
|
-
sections.push(
|
|
391
|
+
sections.push(renderJSXTreeCompact(jsxTree));
|
|
350
392
|
sections.push("```\n");
|
|
351
393
|
}
|
|
352
394
|
if (isJSX && classNameChanges.length > 0) {
|
|
353
|
-
sections.push("
|
|
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/
|
|
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