github-mobile-reader 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +585 -0
- package/README.md +157 -84
- package/dist/action.js +177 -30
- package/dist/cli.js +552 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +198 -23
- package/dist/index.mjs +188 -22
- package/package.json +7 -3
package/dist/action.js
CHANGED
|
@@ -28,6 +28,131 @@ var path = __toESM(require("path"));
|
|
|
28
28
|
var import_child_process = require("child_process");
|
|
29
29
|
|
|
30
30
|
// src/parser.ts
|
|
31
|
+
function isJSXFile(filename) {
|
|
32
|
+
return /\.(jsx|tsx)$/.test(filename);
|
|
33
|
+
}
|
|
34
|
+
function hasJSXContent(lines) {
|
|
35
|
+
return lines.some((l) => /<[A-Z][A-Za-z]*[\s/>]/.test(l) || /return\s*\(/.test(l));
|
|
36
|
+
}
|
|
37
|
+
function isClassNameOnlyLine(line) {
|
|
38
|
+
return /^className=/.test(line.trim());
|
|
39
|
+
}
|
|
40
|
+
function extractClassName(line) {
|
|
41
|
+
const staticMatch = line.match(/className="([^"]*)"/);
|
|
42
|
+
if (staticMatch) return staticMatch[1];
|
|
43
|
+
const ternaryMatch = line.match(/className=\{[^?]+\?\s*"([^"]*)"\s*:\s*"([^"]*)"\}/);
|
|
44
|
+
if (ternaryMatch) return `${ternaryMatch[1]} ${ternaryMatch[2]}`;
|
|
45
|
+
const templateMatch = line.match(/className=\{`([^`]*)`\}/);
|
|
46
|
+
if (templateMatch) {
|
|
47
|
+
const raw = templateMatch[1];
|
|
48
|
+
const literals = raw.replace(/\$\{[^}]*\}/g, " ").trim();
|
|
49
|
+
const exprStrings = [...raw.matchAll(/"([^"]*)"/g)].map((m) => m[1]);
|
|
50
|
+
return [literals, ...exprStrings].filter(Boolean).join(" ");
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function extractComponentFromLine(line) {
|
|
55
|
+
const tagMatch = line.match(/<([A-Za-z][A-Za-z0-9.]*)/);
|
|
56
|
+
if (tagMatch) return tagMatch[1];
|
|
57
|
+
return "unknown";
|
|
58
|
+
}
|
|
59
|
+
function parseClassNameChanges(addedLines, removedLines) {
|
|
60
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const line of addedLines.filter((l) => /className=/.test(l))) {
|
|
62
|
+
const cls = extractClassName(line);
|
|
63
|
+
const comp = extractComponentFromLine(line);
|
|
64
|
+
if (!cls) continue;
|
|
65
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
66
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).added.add(c));
|
|
67
|
+
}
|
|
68
|
+
for (const line of removedLines.filter((l) => /className=/.test(l))) {
|
|
69
|
+
const cls = extractClassName(line);
|
|
70
|
+
const comp = extractComponentFromLine(line);
|
|
71
|
+
if (!cls) continue;
|
|
72
|
+
if (!componentMap.has(comp)) componentMap.set(comp, { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() });
|
|
73
|
+
cls.split(/\s+/).filter(Boolean).forEach((c) => componentMap.get(comp).removed.add(c));
|
|
74
|
+
}
|
|
75
|
+
const changes = [];
|
|
76
|
+
for (const [comp, { added, removed }] of componentMap) {
|
|
77
|
+
const pureAdded = [...added].filter((c) => !removed.has(c));
|
|
78
|
+
const pureRemoved = [...removed].filter((c) => !added.has(c));
|
|
79
|
+
if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
|
|
80
|
+
changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
|
|
81
|
+
}
|
|
82
|
+
return changes;
|
|
83
|
+
}
|
|
84
|
+
function renderStyleChanges(changes) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
for (const change of changes) {
|
|
87
|
+
lines.push(`**${change.component}**`);
|
|
88
|
+
if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
|
|
89
|
+
if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
|
|
90
|
+
}
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
function isJSXElement(line) {
|
|
94
|
+
const t = line.trim();
|
|
95
|
+
return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
|
|
96
|
+
}
|
|
97
|
+
function isJSXClosing(line) {
|
|
98
|
+
return /^<\/[A-Za-z]/.test(line.trim());
|
|
99
|
+
}
|
|
100
|
+
function isJSXSelfClosing(line) {
|
|
101
|
+
return /\/>[\s]*$/.test(line.trim());
|
|
102
|
+
}
|
|
103
|
+
function extractJSXComponentName(line) {
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
|
|
106
|
+
if (closingMatch) return `/${closingMatch[1]}`;
|
|
107
|
+
const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
|
|
108
|
+
if (!nameMatch) return trimmed;
|
|
109
|
+
const name = nameMatch[1];
|
|
110
|
+
const eventProps = [];
|
|
111
|
+
for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
|
|
112
|
+
eventProps.push(m[1]);
|
|
113
|
+
}
|
|
114
|
+
return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
|
|
115
|
+
}
|
|
116
|
+
function shouldIgnoreJSX(line) {
|
|
117
|
+
const t = line.trim();
|
|
118
|
+
return isClassNameOnlyLine(t) || /^style=/.test(t) || /^aria-/.test(t) || /^data-/.test(t) || /^strokeLinecap=/.test(t) || /^strokeLinejoin=/.test(t) || /^strokeWidth=/.test(t) || /^viewBox=/.test(t) || /^fill=/.test(t) || /^stroke=/.test(t) || /^d="/.test(t) || t === "{" || t === "}" || t === "(" || t === ")" || t === "<>" || t === "</>" || /^\{\/\*/.test(t);
|
|
119
|
+
}
|
|
120
|
+
function parseJSXToFlowTree(lines) {
|
|
121
|
+
const roots = [];
|
|
122
|
+
const stack = [];
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
if (!isJSXElement(line)) continue;
|
|
125
|
+
if (shouldIgnoreJSX(line)) continue;
|
|
126
|
+
const depth = getIndentDepth(line);
|
|
127
|
+
if (isJSXClosing(line)) {
|
|
128
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
129
|
+
stack.pop();
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const name = extractJSXComponentName(line);
|
|
134
|
+
const selfClosing = isJSXSelfClosing(line);
|
|
135
|
+
const node = {
|
|
136
|
+
type: "call",
|
|
137
|
+
name,
|
|
138
|
+
children: [],
|
|
139
|
+
depth,
|
|
140
|
+
priority: 5 /* OTHER */
|
|
141
|
+
};
|
|
142
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
143
|
+
stack.pop();
|
|
144
|
+
}
|
|
145
|
+
if (stack.length === 0) {
|
|
146
|
+
roots.push(node);
|
|
147
|
+
} else {
|
|
148
|
+
stack[stack.length - 1].node.children.push(node);
|
|
149
|
+
}
|
|
150
|
+
if (!selfClosing) {
|
|
151
|
+
stack.push({ node, depth });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return roots;
|
|
155
|
+
}
|
|
31
156
|
function filterDiffLines(diffText) {
|
|
32
157
|
const lines = diffText.split("\n");
|
|
33
158
|
const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
|
|
@@ -37,10 +162,10 @@ function filterDiffLines(diffText) {
|
|
|
37
162
|
function normalizeCode(lines) {
|
|
38
163
|
return lines.map((line) => {
|
|
39
164
|
let normalized = line;
|
|
40
|
-
normalized = normalized.replace(/;$/, "");
|
|
41
165
|
normalized = normalized.replace(/\/\/.*$/, "");
|
|
42
166
|
normalized = normalized.replace(/\/\*.*?\*\//, "");
|
|
43
167
|
normalized = normalized.trim();
|
|
168
|
+
normalized = normalized.replace(/;$/, "");
|
|
44
169
|
return normalized;
|
|
45
170
|
}).filter((line) => line.length > 0);
|
|
46
171
|
}
|
|
@@ -77,7 +202,13 @@ function isLoop(line) {
|
|
|
77
202
|
return /^(for|while)\s*\(/.test(line.trim());
|
|
78
203
|
}
|
|
79
204
|
function isFunctionDeclaration(line) {
|
|
80
|
-
|
|
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
|
+
);
|
|
81
212
|
}
|
|
82
213
|
function shouldIgnore(line) {
|
|
83
214
|
const ignorePatterns = [
|
|
@@ -136,6 +267,19 @@ function parseToFlowTree(lines) {
|
|
|
136
267
|
prevLine = line;
|
|
137
268
|
continue;
|
|
138
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
|
+
}
|
|
139
283
|
const root = extractRoot(line);
|
|
140
284
|
if (root) {
|
|
141
285
|
currentChain = {
|
|
@@ -166,16 +310,6 @@ function parseToFlowTree(lines) {
|
|
|
166
310
|
priority: 3 /* LOOP */
|
|
167
311
|
});
|
|
168
312
|
currentChain = null;
|
|
169
|
-
} else if (isFunctionDeclaration(line)) {
|
|
170
|
-
const funcMatch = line.match(/(?:function|const|let|var)\s+(\w+)/);
|
|
171
|
-
roots.push({
|
|
172
|
-
type: "function",
|
|
173
|
-
name: funcMatch ? `${funcMatch[1]}()` : "function()",
|
|
174
|
-
children: [],
|
|
175
|
-
depth: relativeDepth,
|
|
176
|
-
priority: 4 /* FUNCTION */
|
|
177
|
-
});
|
|
178
|
-
currentChain = null;
|
|
179
313
|
}
|
|
180
314
|
prevLine = line;
|
|
181
315
|
}
|
|
@@ -192,19 +326,21 @@ function renderFlowTree(nodes, indent = 0) {
|
|
|
192
326
|
}
|
|
193
327
|
return lines;
|
|
194
328
|
}
|
|
195
|
-
function
|
|
329
|
+
function generateReaderMarkdown(diffText, meta = {}) {
|
|
196
330
|
const { added, removed } = filterDiffLines(diffText);
|
|
197
|
-
const
|
|
331
|
+
const isJSX = Boolean(
|
|
332
|
+
meta.file && isJSXFile(meta.file) || hasJSXContent(added)
|
|
333
|
+
);
|
|
334
|
+
const addedForFlow = isJSX ? added.filter((l) => !isClassNameOnlyLine(l)) : added;
|
|
335
|
+
const normalizedAdded = normalizeCode(addedForFlow);
|
|
198
336
|
const flowTree = parseToFlowTree(normalizedAdded);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
function generateReaderMarkdown(diffText, meta = {}) {
|
|
206
|
-
const result = parseDiffToLogicalFlow(diffText);
|
|
337
|
+
const rawCode = addedForFlow.join("\n");
|
|
338
|
+
const removedForCode = isJSX ? removed.filter((l) => !isClassNameOnlyLine(l)) : removed;
|
|
339
|
+
const removedCode = removedForCode.join("\n");
|
|
340
|
+
const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
|
|
341
|
+
const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
|
|
207
342
|
const sections = [];
|
|
343
|
+
const lang = isJSX ? "tsx" : "typescript";
|
|
208
344
|
sections.push("# \u{1F4D6} GitHub Reader View\n");
|
|
209
345
|
sections.push("> Generated by **github-mobile-reader**");
|
|
210
346
|
if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
|
|
@@ -212,22 +348,33 @@ function generateReaderMarkdown(diffText, meta = {}) {
|
|
|
212
348
|
if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
|
|
213
349
|
if (meta.file) sections.push(`> File: \`${meta.file}\``);
|
|
214
350
|
sections.push("\n");
|
|
215
|
-
if (
|
|
351
|
+
if (flowTree.length > 0) {
|
|
216
352
|
sections.push("## \u{1F9E0} Logical Flow\n");
|
|
217
353
|
sections.push("```");
|
|
218
|
-
sections.push(...renderFlowTree(
|
|
354
|
+
sections.push(...renderFlowTree(flowTree));
|
|
219
355
|
sections.push("```\n");
|
|
220
356
|
}
|
|
221
|
-
if (
|
|
357
|
+
if (isJSX && jsxTree.length > 0) {
|
|
358
|
+
sections.push("## \u{1F3A8} JSX Structure\n");
|
|
359
|
+
sections.push("```");
|
|
360
|
+
sections.push(...renderFlowTree(jsxTree));
|
|
361
|
+
sections.push("```\n");
|
|
362
|
+
}
|
|
363
|
+
if (isJSX && classNameChanges.length > 0) {
|
|
364
|
+
sections.push("## \u{1F485} Style Changes\n");
|
|
365
|
+
sections.push(...renderStyleChanges(classNameChanges));
|
|
366
|
+
sections.push("");
|
|
367
|
+
}
|
|
368
|
+
if (rawCode.trim()) {
|
|
222
369
|
sections.push("## \u2705 Added Code\n");
|
|
223
|
-
sections.push(
|
|
224
|
-
sections.push(
|
|
370
|
+
sections.push(`\`\`\`${lang}`);
|
|
371
|
+
sections.push(rawCode);
|
|
225
372
|
sections.push("```\n");
|
|
226
373
|
}
|
|
227
|
-
if (
|
|
374
|
+
if (removedCode.trim()) {
|
|
228
375
|
sections.push("## \u274C Removed Code\n");
|
|
229
|
-
sections.push(
|
|
230
|
-
sections.push(
|
|
376
|
+
sections.push(`\`\`\`${lang}`);
|
|
377
|
+
sections.push(removedCode);
|
|
231
378
|
sections.push("```\n");
|
|
232
379
|
}
|
|
233
380
|
sections.push("---");
|