github-mobile-reader 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/action.js ADDED
@@ -0,0 +1,348 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/action.ts
26
+ var fs = __toESM(require("fs"));
27
+ var path = __toESM(require("path"));
28
+ var import_child_process = require("child_process");
29
+
30
+ // src/parser.ts
31
+ function filterDiffLines(diffText) {
32
+ const lines = diffText.split("\n");
33
+ const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
34
+ const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
35
+ return { added, removed };
36
+ }
37
+ function normalizeCode(lines) {
38
+ return lines.map((line) => {
39
+ let normalized = line;
40
+ normalized = normalized.replace(/;$/, "");
41
+ normalized = normalized.replace(/\/\/.*$/, "");
42
+ normalized = normalized.replace(/\/\*.*?\*\//, "");
43
+ normalized = normalized.trim();
44
+ return normalized;
45
+ }).filter((line) => line.length > 0);
46
+ }
47
+ function getIndentDepth(line) {
48
+ const match = line.match(/^(\s*)/);
49
+ if (!match) return 0;
50
+ return Math.floor(match[1].length / 2);
51
+ }
52
+ function isChaining(line, prevLine) {
53
+ if (!prevLine) return false;
54
+ if (!line.trim().startsWith(".")) return false;
55
+ if (!prevLine.match(/[)\}]$/)) return false;
56
+ return true;
57
+ }
58
+ function extractChainMethod(line) {
59
+ const match = line.match(/\.(\w+)\(/);
60
+ if (match) return `${match[1]}()`;
61
+ return line.trim();
62
+ }
63
+ function simplifyCallback(methodCall) {
64
+ const arrowMatch = methodCall.match(/(\w+)\((\w+)\s*=>\s*(\w+)\.(\w+)\)/);
65
+ if (arrowMatch) {
66
+ const [, method, param, , prop] = arrowMatch;
67
+ return `${method}(${param} \u2192 ${prop})`;
68
+ }
69
+ const callbackMatch = methodCall.match(/(\w+)\([^)]+\)/);
70
+ if (callbackMatch) return `${callbackMatch[1]}(callback)`;
71
+ return methodCall;
72
+ }
73
+ function isConditional(line) {
74
+ return /^(if|else|switch)\s*[\(\{]/.test(line.trim());
75
+ }
76
+ function isLoop(line) {
77
+ return /^(for|while)\s*\(/.test(line.trim());
78
+ }
79
+ function isFunctionDeclaration(line) {
80
+ return /^(function|const|let|var)\s+\w+\s*=?\s*(async\s*)?\(/.test(line.trim()) || /^(async\s+)?function\s+\w+/.test(line.trim());
81
+ }
82
+ function shouldIgnore(line) {
83
+ const ignorePatterns = [
84
+ /^import\s+/,
85
+ /^export\s+/,
86
+ /^type\s+/,
87
+ /^interface\s+/,
88
+ /^console\./,
89
+ /^return$/,
90
+ /^throw\s+/
91
+ ];
92
+ return ignorePatterns.some((p) => p.test(line.trim()));
93
+ }
94
+ function extractRoot(line) {
95
+ const assignMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(\w+)/);
96
+ if (assignMatch) return assignMatch[2];
97
+ const callMatch = line.match(/^(\w+)\(/);
98
+ if (callMatch) return `${callMatch[1]}()`;
99
+ const methodMatch = line.match(/^(\w+)\./);
100
+ if (methodMatch) return methodMatch[1];
101
+ return null;
102
+ }
103
+ function parseToFlowTree(lines) {
104
+ const roots = [];
105
+ let currentChain = null;
106
+ let prevLine = null;
107
+ let baseDepth = -1;
108
+ for (let i = 0; i < lines.length; i++) {
109
+ const line = lines[i];
110
+ if (shouldIgnore(line)) {
111
+ prevLine = line;
112
+ continue;
113
+ }
114
+ const depth = getIndentDepth(lines[i]);
115
+ if (baseDepth === -1) baseDepth = depth;
116
+ const relativeDepth = depth - baseDepth;
117
+ if (isChaining(line, prevLine)) {
118
+ const method = extractChainMethod(line);
119
+ const simplified = simplifyCallback(method);
120
+ if (currentChain) {
121
+ const chainNode = {
122
+ type: "chain",
123
+ name: simplified,
124
+ children: [],
125
+ depth: relativeDepth,
126
+ priority: 1 /* CHAINING */
127
+ };
128
+ let parent = currentChain;
129
+ while (parent.children.length > 0 && parent.children[parent.children.length - 1].depth >= relativeDepth) {
130
+ const last = parent.children[parent.children.length - 1];
131
+ if (last.children.length > 0) parent = last;
132
+ else break;
133
+ }
134
+ parent.children.push(chainNode);
135
+ }
136
+ prevLine = line;
137
+ continue;
138
+ }
139
+ const root = extractRoot(line);
140
+ if (root) {
141
+ currentChain = {
142
+ type: "root",
143
+ name: root,
144
+ children: [],
145
+ depth: relativeDepth,
146
+ priority: 1 /* CHAINING */
147
+ };
148
+ roots.push(currentChain);
149
+ } else if (isConditional(line)) {
150
+ const condMatch = line.match(/(if|else|switch)\s*\(([^)]+)\)/);
151
+ const condName = condMatch ? `${condMatch[1]} (${condMatch[2]})` : line.trim();
152
+ roots.push({
153
+ type: "condition",
154
+ name: condName,
155
+ children: [],
156
+ depth: relativeDepth,
157
+ priority: 2 /* CONDITIONAL */
158
+ });
159
+ currentChain = null;
160
+ } else if (isLoop(line)) {
161
+ roots.push({
162
+ type: "loop",
163
+ name: "loop",
164
+ children: [],
165
+ depth: relativeDepth,
166
+ priority: 3 /* LOOP */
167
+ });
168
+ 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
+ }
180
+ prevLine = line;
181
+ }
182
+ return roots;
183
+ }
184
+ function renderFlowTree(nodes, indent = 0) {
185
+ const lines = [];
186
+ const prefix = indent === 0 ? "" : " ".repeat((indent - 1) * 4) + " \u2514\u2500 ";
187
+ for (const node of nodes) {
188
+ lines.push(prefix + node.name);
189
+ if (node.children.length > 0) {
190
+ lines.push(...renderFlowTree(node.children, indent + 1));
191
+ }
192
+ }
193
+ return lines;
194
+ }
195
+ function parseDiffToLogicalFlow(diffText) {
196
+ const { added, removed } = filterDiffLines(diffText);
197
+ const normalizedAdded = normalizeCode(added);
198
+ const flowTree = parseToFlowTree(normalizedAdded);
199
+ return {
200
+ root: flowTree,
201
+ rawCode: added.join("\n"),
202
+ removedCode: removed.join("\n")
203
+ };
204
+ }
205
+ function generateReaderMarkdown(diffText, meta = {}) {
206
+ const result = parseDiffToLogicalFlow(diffText);
207
+ const sections = [];
208
+ sections.push("# \u{1F4D6} GitHub Reader View\n");
209
+ sections.push("> Generated by **github-mobile-reader**");
210
+ if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
211
+ if (meta.pr) sections.push(`> Pull Request: #${meta.pr}`);
212
+ if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
213
+ if (meta.file) sections.push(`> File: \`${meta.file}\``);
214
+ sections.push("\n");
215
+ if (result.root.length > 0) {
216
+ sections.push("## \u{1F9E0} Logical Flow\n");
217
+ sections.push("```");
218
+ sections.push(...renderFlowTree(result.root));
219
+ sections.push("```\n");
220
+ }
221
+ if (result.rawCode.trim()) {
222
+ sections.push("## \u2705 Added Code\n");
223
+ sections.push("```typescript");
224
+ sections.push(result.rawCode);
225
+ sections.push("```\n");
226
+ }
227
+ if (result.removedCode.trim()) {
228
+ sections.push("## \u274C Removed Code\n");
229
+ sections.push("```typescript");
230
+ sections.push(result.removedCode);
231
+ sections.push("```\n");
232
+ }
233
+ sections.push("---");
234
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/your-org/github-mobile-reader). Do not edit manually.");
235
+ return sections.join("\n");
236
+ }
237
+
238
+ // src/action.ts
239
+ function env(name, fallback) {
240
+ const val = process.env[name] ?? fallback;
241
+ if (val === void 0) {
242
+ throw new Error(`Required environment variable "${name}" is not set.`);
243
+ }
244
+ return val;
245
+ }
246
+ function run(cmd, cwd) {
247
+ return (0, import_child_process.execSync)(cmd, {
248
+ cwd: cwd ?? process.cwd(),
249
+ encoding: "utf8",
250
+ stdio: ["pipe", "pipe", "pipe"]
251
+ }).trim();
252
+ }
253
+ function getFileDiffs(baseBranch) {
254
+ let rawDiff;
255
+ try {
256
+ rawDiff = run(`git diff origin/${baseBranch}...HEAD`);
257
+ } catch {
258
+ rawDiff = run("git diff HEAD");
259
+ }
260
+ if (!rawDiff) return [];
261
+ const fileDiffs = [];
262
+ const chunks = rawDiff.split(/(?=^diff --git )/m).filter(Boolean);
263
+ for (const chunk of chunks) {
264
+ const fileMatch = chunk.match(/^diff --git a\/(.+?) b\//m);
265
+ if (!fileMatch) continue;
266
+ const filename = fileMatch[1];
267
+ if (!/\.(js|jsx|ts|tsx|mjs|cjs)$/.test(filename)) continue;
268
+ fileDiffs.push({ filename, diff: chunk });
269
+ }
270
+ return fileDiffs;
271
+ }
272
+ function writeMarkdown(outputDir, prNumber, fileDiffs, meta) {
273
+ const parts = [
274
+ `# \u{1F4D6} PR #${prNumber} \u2014 Mobile Reader View
275
+ `,
276
+ `> Repository: ${meta.repo} `,
277
+ `> Commit: \`${meta.commit}\` `,
278
+ `> Generated by **github-mobile-reader**
279
+ `,
280
+ "---\n"
281
+ ];
282
+ for (const { filename, diff } of fileDiffs) {
283
+ const section = generateReaderMarkdown(diff, {
284
+ pr: prNumber,
285
+ commit: meta.commit,
286
+ file: filename,
287
+ repo: meta.repo
288
+ });
289
+ parts.push(`## \u{1F4C4} \`${filename}\`
290
+ `);
291
+ parts.push(section);
292
+ parts.push("\n---\n");
293
+ }
294
+ if (parts.length === 5) {
295
+ parts.push("_No JavaScript / TypeScript changes detected in this PR._\n");
296
+ }
297
+ const outPath = path.join(outputDir, `pr-${prNumber}.md`);
298
+ fs.mkdirSync(outputDir, { recursive: true });
299
+ fs.writeFileSync(outPath, parts.join("\n"), "utf8");
300
+ console.log(`[github-mobile-reader] Written \u2192 ${outPath}`);
301
+ return outPath;
302
+ }
303
+ async function postComment(token, repo, prNumber, body) {
304
+ const [owner, repoName] = repo.split("/");
305
+ const url = `https://api.github.com/repos/${owner}/${repoName}/issues/${prNumber}/comments`;
306
+ const resp = await fetch(url, {
307
+ method: "POST",
308
+ headers: {
309
+ Authorization: `token ${token}`,
310
+ "Content-Type": "application/json",
311
+ Accept: "application/vnd.github+json"
312
+ },
313
+ body: JSON.stringify({ body })
314
+ });
315
+ if (!resp.ok) {
316
+ const text = await resp.text();
317
+ throw new Error(`GitHub API error ${resp.status}: ${text}`);
318
+ }
319
+ console.log("[github-mobile-reader] PR comment posted.");
320
+ }
321
+ async function main() {
322
+ const token = env("GITHUB_TOKEN");
323
+ const repo = env("GITHUB_REPOSITORY");
324
+ const prNumber = env("PR_NUMBER");
325
+ const baseBranch = env("BASE_BRANCH", "main");
326
+ const outputDir = env("OUTPUT_DIR", "docs/reader");
327
+ const commit = run("git rev-parse --short HEAD");
328
+ console.log(`[github-mobile-reader] Processing PR #${prNumber} (${repo})`);
329
+ const fileDiffs = getFileDiffs(baseBranch);
330
+ console.log(`[github-mobile-reader] Found ${fileDiffs.length} JS/TS file(s) with changes`);
331
+ const outPath = writeMarkdown(outputDir, prNumber, fileDiffs, { repo, commit });
332
+ const fileList = fileDiffs.map((f) => `- \`${f.filename}\``).join("\n") || "- (none)";
333
+ const commentBody = [
334
+ "## \u{1F4D6} Mobile Reader View Generated",
335
+ "",
336
+ `A mobile-friendly summary has been written to \`${outPath}\`.`,
337
+ "",
338
+ "**Changed files processed:**",
339
+ fileList,
340
+ "",
341
+ `> Powered by [github-mobile-reader](https://www.npmjs.com/package/github-mobile-reader)`
342
+ ].join("\n");
343
+ await postComment(token, repo, prNumber, commentBody);
344
+ }
345
+ main().catch((err) => {
346
+ console.error("[github-mobile-reader] Fatal:", err.message);
347
+ process.exit(1);
348
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * github-mobile-reader - Diff to Logical Flow Parser v0.2
3
+ *
4
+ * Philosophy:
5
+ * - Summarize, don't explain
6
+ * - Be conservative when ambiguous
7
+ * - Show less rather than show wrong
8
+ */
9
+ declare enum Priority {
10
+ CHAINING = 1,
11
+ CONDITIONAL = 2,
12
+ LOOP = 3,
13
+ FUNCTION = 4,
14
+ OTHER = 5
15
+ }
16
+ interface FlowNode {
17
+ type: 'root' | 'chain' | 'condition' | 'loop' | 'function' | 'call';
18
+ name: string;
19
+ children: FlowNode[];
20
+ depth: number;
21
+ priority: Priority;
22
+ }
23
+ interface ParseResult {
24
+ root: FlowNode[];
25
+ rawCode: string;
26
+ removedCode: string;
27
+ }
28
+ interface ReaderMarkdownMeta {
29
+ pr?: string;
30
+ commit?: string;
31
+ file?: string;
32
+ repo?: string;
33
+ }
34
+ /**
35
+ * Step 1: Filter diff lines — added (+) and removed (-) separately
36
+ */
37
+ declare function filterDiffLines(diffText: string): {
38
+ added: string[];
39
+ removed: string[];
40
+ };
41
+ /**
42
+ * Step 2: Normalize code — remove noise, preserve structure
43
+ */
44
+ declare function normalizeCode(lines: string[]): string[];
45
+ /**
46
+ * Main parser: convert normalized lines → FlowNode tree
47
+ */
48
+ declare function parseToFlowTree(lines: string[]): FlowNode[];
49
+ /**
50
+ * Render flow tree as markdown lines
51
+ */
52
+ declare function renderFlowTree(nodes: FlowNode[], indent?: number): string[];
53
+ /**
54
+ * Main entry: parse a raw diff string → ParseResult
55
+ */
56
+ declare function parseDiffToLogicalFlow(diffText: string): ParseResult;
57
+ /**
58
+ * Generate the complete Reader Markdown document
59
+ */
60
+ declare function generateReaderMarkdown(diffText: string, meta?: ReaderMarkdownMeta): string;
61
+
62
+ export { type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, filterDiffLines, generateReaderMarkdown, normalizeCode, parseDiffToLogicalFlow, parseToFlowTree, renderFlowTree };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * github-mobile-reader - Diff to Logical Flow Parser v0.2
3
+ *
4
+ * Philosophy:
5
+ * - Summarize, don't explain
6
+ * - Be conservative when ambiguous
7
+ * - Show less rather than show wrong
8
+ */
9
+ declare enum Priority {
10
+ CHAINING = 1,
11
+ CONDITIONAL = 2,
12
+ LOOP = 3,
13
+ FUNCTION = 4,
14
+ OTHER = 5
15
+ }
16
+ interface FlowNode {
17
+ type: 'root' | 'chain' | 'condition' | 'loop' | 'function' | 'call';
18
+ name: string;
19
+ children: FlowNode[];
20
+ depth: number;
21
+ priority: Priority;
22
+ }
23
+ interface ParseResult {
24
+ root: FlowNode[];
25
+ rawCode: string;
26
+ removedCode: string;
27
+ }
28
+ interface ReaderMarkdownMeta {
29
+ pr?: string;
30
+ commit?: string;
31
+ file?: string;
32
+ repo?: string;
33
+ }
34
+ /**
35
+ * Step 1: Filter diff lines — added (+) and removed (-) separately
36
+ */
37
+ declare function filterDiffLines(diffText: string): {
38
+ added: string[];
39
+ removed: string[];
40
+ };
41
+ /**
42
+ * Step 2: Normalize code — remove noise, preserve structure
43
+ */
44
+ declare function normalizeCode(lines: string[]): string[];
45
+ /**
46
+ * Main parser: convert normalized lines → FlowNode tree
47
+ */
48
+ declare function parseToFlowTree(lines: string[]): FlowNode[];
49
+ /**
50
+ * Render flow tree as markdown lines
51
+ */
52
+ declare function renderFlowTree(nodes: FlowNode[], indent?: number): string[];
53
+ /**
54
+ * Main entry: parse a raw diff string → ParseResult
55
+ */
56
+ declare function parseDiffToLogicalFlow(diffText: string): ParseResult;
57
+ /**
58
+ * Generate the complete Reader Markdown document
59
+ */
60
+ declare function generateReaderMarkdown(diffText: string, meta?: ReaderMarkdownMeta): string;
61
+
62
+ export { type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, filterDiffLines, generateReaderMarkdown, normalizeCode, parseDiffToLogicalFlow, parseToFlowTree, renderFlowTree };