github-mobile-reader 0.1.1 → 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/dist/cli.js ADDED
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var fs = __toESM(require("fs"));
28
+ var path = __toESM(require("path"));
29
+
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
+ if (comp === "unknown") continue;
78
+ const pureAdded = [...added].filter((c) => !removed.has(c));
79
+ const pureRemoved = [...removed].filter((c) => !added.has(c));
80
+ if (pureAdded.length === 0 && pureRemoved.length === 0) continue;
81
+ changes.push({ component: comp, added: pureAdded, removed: pureRemoved });
82
+ }
83
+ return changes;
84
+ }
85
+ function renderStyleChanges(changes) {
86
+ const lines = [];
87
+ for (const change of changes) {
88
+ lines.push(`**${change.component}**`);
89
+ if (change.added.length > 0) lines.push(` + ${change.added.join(" ")}`);
90
+ if (change.removed.length > 0) lines.push(` - ${change.removed.join(" ")}`);
91
+ }
92
+ return lines;
93
+ }
94
+ function isJSXElement(line) {
95
+ const t = line.trim();
96
+ return /^<[A-Za-z]/.test(t) || /^<\/[A-Za-z]/.test(t);
97
+ }
98
+ function isJSXClosing(line) {
99
+ return /^<\/[A-Za-z]/.test(line.trim());
100
+ }
101
+ function isJSXSelfClosing(line) {
102
+ return /\/>[\s]*$/.test(line.trim());
103
+ }
104
+ function extractJSXComponentName(line) {
105
+ const trimmed = line.trim();
106
+ const closingMatch = trimmed.match(/^<\/([A-Za-z][A-Za-z0-9.]*)/);
107
+ if (closingMatch) return `/${closingMatch[1]}`;
108
+ const nameMatch = trimmed.match(/^<([A-Za-z][A-Za-z0-9.]*)/);
109
+ if (!nameMatch) return trimmed;
110
+ const name = nameMatch[1];
111
+ const eventProps = [];
112
+ for (const m of trimmed.matchAll(/\b(on[A-Z]\w+)=/g)) {
113
+ eventProps.push(m[1]);
114
+ }
115
+ return eventProps.length > 0 ? `${name}(${eventProps.join(", ")})` : name;
116
+ }
117
+ function shouldIgnoreJSX(line) {
118
+ const t = line.trim();
119
+ 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);
120
+ }
121
+ function parseJSXToFlowTree(lines) {
122
+ const roots = [];
123
+ const stack = [];
124
+ for (const line of lines) {
125
+ if (!isJSXElement(line)) continue;
126
+ if (shouldIgnoreJSX(line)) continue;
127
+ const depth = getIndentDepth(line);
128
+ if (isJSXClosing(line)) {
129
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
130
+ stack.pop();
131
+ }
132
+ continue;
133
+ }
134
+ const name = extractJSXComponentName(line);
135
+ const selfClosing = isJSXSelfClosing(line);
136
+ const node = {
137
+ type: "call",
138
+ name,
139
+ children: [],
140
+ depth,
141
+ priority: 5 /* OTHER */
142
+ };
143
+ while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
144
+ stack.pop();
145
+ }
146
+ if (stack.length === 0) {
147
+ roots.push(node);
148
+ } else {
149
+ stack[stack.length - 1].node.children.push(node);
150
+ }
151
+ if (!selfClosing) {
152
+ stack.push({ node, depth });
153
+ }
154
+ }
155
+ return roots;
156
+ }
157
+ function filterDiffLines(diffText) {
158
+ const lines = diffText.split("\n");
159
+ const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++") && l.trim() !== "+").map((l) => l.substring(1));
160
+ const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---") && l.trim() !== "-").map((l) => l.substring(1));
161
+ return { added, removed };
162
+ }
163
+ function getIndentDepth(line) {
164
+ const match = line.match(/^(\s*)/);
165
+ if (!match) return 0;
166
+ return Math.floor(match[1].length / 2);
167
+ }
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);
178
+ }
179
+ }
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" });
193
+ }
194
+ }
195
+ return results;
196
+ }
197
+ function renderJSXTreeCompact(nodes, maxDepth = 3) {
198
+ const lines = [];
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);
206
+ }
207
+ }
208
+ for (const root of nodes) {
209
+ walk(root, 0);
210
+ }
211
+ return lines.join("\n");
212
+ }
213
+ function generateReaderMarkdown(diffText, meta = {}) {
214
+ const { added, removed } = filterDiffLines(diffText);
215
+ const isJSX = Boolean(
216
+ meta.file && isJSXFile(meta.file) || hasJSXContent(added)
217
+ );
218
+ const changedSymbols = extractChangedSymbols(added, removed);
219
+ const classNameChanges = isJSX ? parseClassNameChanges(added, removed) : [];
220
+ const jsxTree = isJSX ? parseJSXToFlowTree(added) : [];
221
+ const sections = [];
222
+ sections.push("# \u{1F4D6} GitHub Reader View\n");
223
+ sections.push("> Generated by **github-mobile-reader**");
224
+ if (meta.repo) sections.push(`> Repository: ${meta.repo}`);
225
+ if (meta.pr) sections.push(`> Pull Request: #${meta.pr}`);
226
+ if (meta.commit) sections.push(`> Commit: \`${meta.commit}\``);
227
+ if (meta.file) sections.push(`> File: \`${meta.file}\``);
228
+ sections.push("\n");
229
+ if (changedSymbols.length > 0) {
230
+ sections.push("### \uBCC0\uACBD\uB41C \uD568\uC218 / \uCEF4\uD3EC\uB10C\uD2B8\n");
231
+ const STATUS_ICON = { added: "\u2705", removed: "\u274C", modified: "\u270F\uFE0F" };
232
+ for (const { name, status } of changedSymbols) {
233
+ sections.push(`- ${STATUS_ICON[status]} \`${name}()\` \u2014 ${status}`);
234
+ }
235
+ sections.push("");
236
+ }
237
+ if (isJSX && jsxTree.length > 0) {
238
+ sections.push("### \u{1F3A8} JSX Structure\n");
239
+ sections.push("```");
240
+ sections.push(renderJSXTreeCompact(jsxTree));
241
+ sections.push("```\n");
242
+ }
243
+ if (isJSX && classNameChanges.length > 0) {
244
+ sections.push("### \u{1F485} Style Changes\n");
245
+ sections.push(...renderStyleChanges(classNameChanges));
246
+ sections.push("");
247
+ }
248
+ sections.push("---");
249
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
250
+ return sections.join("\n");
251
+ }
252
+
253
+ // src/cli.ts
254
+ function parseArgs() {
255
+ const args = process.argv.slice(2);
256
+ const get = (flag) => {
257
+ const idx = args.indexOf(flag);
258
+ return idx !== -1 ? args[idx + 1] : void 0;
259
+ };
260
+ const repo = get("--repo");
261
+ if (!repo) {
262
+ console.error("Error: --repo <owner/repo> is required");
263
+ console.error("");
264
+ console.error("Examples:");
265
+ console.error(" npx github-mobile-reader --repo 3rdflr/-FE- --pr 5");
266
+ console.error(" npx github-mobile-reader --repo 3rdflr/-FE- --all");
267
+ process.exit(1);
268
+ }
269
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.\-]+$/.test(repo)) {
270
+ console.error('Error: --repo must be in "owner/repo" format (e.g. "3rdflr/my-app")');
271
+ process.exit(1);
272
+ }
273
+ const rawOut = get("--out") ?? "./reader-output";
274
+ if (path.isAbsolute(rawOut) || rawOut.includes("..")) {
275
+ console.error('Error: --out must be a relative path without ".." (e.g. "./reader-output")');
276
+ process.exit(1);
277
+ }
278
+ if (args.includes("--token")) {
279
+ console.error("Error: --token flag is not supported for security reasons.");
280
+ console.error(" Set the GITHUB_TOKEN environment variable instead:");
281
+ console.error(" export GITHUB_TOKEN=ghp_xxxx");
282
+ process.exit(1);
283
+ }
284
+ return {
285
+ repo,
286
+ pr: get("--pr") ? Number(get("--pr")) : void 0,
287
+ all: args.includes("--all"),
288
+ token: process.env.GITHUB_TOKEN,
289
+ out: rawOut,
290
+ limit: Number(get("--limit") ?? "10")
291
+ };
292
+ }
293
+ async function githubFetch(url, token, accept = "application/vnd.github+json") {
294
+ const headers = { Accept: accept };
295
+ if (token) headers["Authorization"] = `token ${token}`;
296
+ const resp = await fetch(url, { headers });
297
+ if (!resp.ok) {
298
+ if (resp.status === 404) throw new Error(`Not found: ${url}`);
299
+ if (resp.status === 401) throw new Error("Authentication failed. Set the GITHUB_TOKEN environment variable.");
300
+ if (resp.status === 403) throw new Error("Rate limit or permission error. Set GITHUB_TOKEN for higher rate limits.");
301
+ throw new Error(`GitHub API error (status ${resp.status})`);
302
+ }
303
+ return resp;
304
+ }
305
+ async function getPRList(repo, token, limit = 10) {
306
+ const url = `https://api.github.com/repos/${repo}/pulls?state=all&per_page=${limit}&sort=updated&direction=desc`;
307
+ const resp = await githubFetch(url, token);
308
+ const data = await resp.json();
309
+ return data.map((pr) => ({ number: pr.number, title: pr.title }));
310
+ }
311
+ async function getPRMeta(repo, prNumber, token) {
312
+ const url = `https://api.github.com/repos/${repo}/pulls/${prNumber}`;
313
+ const resp = await githubFetch(url, token);
314
+ const data = await resp.json();
315
+ return { title: data.title, head: data.head.sha.slice(0, 7) };
316
+ }
317
+ var JS_TS_EXT = /\.(js|jsx|ts|tsx|mjs|cjs)$/;
318
+ async function getPRFileDiffs(repo, prNumber, token) {
319
+ const url = `https://api.github.com/repos/${repo}/pulls/${prNumber}`;
320
+ const resp = await githubFetch(url, token, "application/vnd.github.v3.diff");
321
+ const rawDiff = await resp.text();
322
+ const chunks = rawDiff.split(/(?=^diff --git )/m).filter(Boolean);
323
+ return chunks.map((chunk) => {
324
+ const match = chunk.match(/^diff --git a\/(.+?) b\//m);
325
+ return match ? { filename: match[1], diff: chunk } : null;
326
+ }).filter((item) => item !== null && JS_TS_EXT.test(item.filename));
327
+ }
328
+ async function processPR(repo, prNumber, outDir, token) {
329
+ process.stdout.write(` Fetching PR #${prNumber}...`);
330
+ const [fileDiffs, meta] = await Promise.all([
331
+ getPRFileDiffs(repo, prNumber, token),
332
+ getPRMeta(repo, prNumber, token)
333
+ ]);
334
+ if (fileDiffs.length === 0) {
335
+ console.log(` \u2014 JS/TS \uBCC0\uACBD \uC5C6\uC74C (\uC2A4\uD0B5)`);
336
+ return "";
337
+ }
338
+ const sections = [];
339
+ sections.push(`# \u{1F4D6} PR #${prNumber} \u2014 ${meta.title}
340
+ `);
341
+ sections.push(`> Repository: ${repo} `);
342
+ sections.push(`> Commit: \`${meta.head}\` `);
343
+ sections.push(`> \uBCC0\uACBD\uB41C JS/TS \uD30C\uC77C: ${fileDiffs.length}\uAC1C
344
+ `);
345
+ sections.push("---\n");
346
+ for (const { filename, diff } of fileDiffs) {
347
+ const section = generateReaderMarkdown(diff, {
348
+ pr: String(prNumber),
349
+ commit: meta.head,
350
+ file: filename,
351
+ repo
352
+ });
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;
355
+ sections.push(`## \u{1F4C4} \`${filename}\`
356
+ `);
357
+ sections.push(withoutHeader);
358
+ sections.push("\n---\n");
359
+ }
360
+ sections.push("\u{1F6E0} Auto-generated by [github-mobile-reader](https://github.com/3rdflr/github-mobile-reader). Do not edit manually.");
361
+ const markdown = sections.join("\n");
362
+ fs.mkdirSync(outDir, { recursive: true });
363
+ const outPath = path.join(outDir, `pr-${prNumber}.md`);
364
+ fs.writeFileSync(outPath, markdown, "utf8");
365
+ console.log(` \u2713 "${meta.title}" (${fileDiffs.length}\uAC1C \uD30C\uC77C)`);
366
+ return outPath;
367
+ }
368
+ async function main() {
369
+ const opts = parseArgs();
370
+ console.log(`
371
+ \u{1F4D6} github-mobile-reader CLI`);
372
+ console.log(` repo : ${opts.repo}`);
373
+ console.log(` out : ${opts.out}`);
374
+ if (!opts.token) {
375
+ console.log(` auth : none (60 req/hr limit \u2014 use --token or GITHUB_TOKEN for more)
376
+ `);
377
+ } else {
378
+ console.log(` auth : token provided
379
+ `);
380
+ }
381
+ if (opts.pr) {
382
+ const outPath = await processPR(opts.repo, opts.pr, opts.out, opts.token);
383
+ if (outPath) console.log(`
384
+ \u2705 Done \u2192 ${outPath}
385
+ `);
386
+ return;
387
+ }
388
+ if (opts.all) {
389
+ console.log(` Fetching PR list (limit: ${opts.limit})...`);
390
+ const prs = await getPRList(opts.repo, opts.token, opts.limit);
391
+ if (prs.length === 0) {
392
+ console.log(" No PRs found.");
393
+ return;
394
+ }
395
+ console.log(` Found ${prs.length} PR(s)
396
+ `);
397
+ const results = [];
398
+ for (const pr of prs) {
399
+ try {
400
+ const outPath = await processPR(opts.repo, pr.number, opts.out, opts.token);
401
+ if (outPath) results.push(outPath);
402
+ } catch (err) {
403
+ console.log(` \u2717 PR #${pr.number} skipped: ${err.message}`);
404
+ }
405
+ }
406
+ console.log(`
407
+ \u2705 Done \u2014 ${results.length} file(s) written to ${opts.out}/
408
+ `);
409
+ results.forEach((p) => console.log(` ${p}`));
410
+ console.log("");
411
+ return;
412
+ }
413
+ console.error("Error: specify --pr <number> or --all");
414
+ process.exit(1);
415
+ }
416
+ main().catch((err) => {
417
+ console.error(`
418
+ \u274C ${err.message}
419
+ `);
420
+ process.exit(1);
421
+ });
package/dist/index.d.mts CHANGED
@@ -31,6 +31,20 @@ interface ReaderMarkdownMeta {
31
31
  file?: string;
32
32
  repo?: string;
33
33
  }
34
+ interface ClassNameChange {
35
+ component: string;
36
+ added: string[];
37
+ removed: string[];
38
+ }
39
+ declare function isJSXFile(filename: string): boolean;
40
+ declare function hasJSXContent(lines: string[]): boolean;
41
+ declare function isClassNameOnlyLine(line: string): boolean;
42
+ declare function extractClassName(line: string): string | null;
43
+ declare function parseClassNameChanges(addedLines: string[], removedLines: string[]): ClassNameChange[];
44
+ declare function renderStyleChanges(changes: ClassNameChange[]): string[];
45
+ declare function isJSXElement(line: string): boolean;
46
+ declare function extractJSXComponentName(line: string): string;
47
+ declare function parseJSXToFlowTree(lines: string[]): FlowNode[];
34
48
  /**
35
49
  * Step 1: Filter diff lines — added (+) and removed (-) separately
36
50
  */
@@ -54,9 +68,22 @@ declare function renderFlowTree(nodes: FlowNode[], indent?: number): string[];
54
68
  * Main entry: parse a raw diff string → ParseResult
55
69
  */
56
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;
57
84
  /**
58
85
  * Generate the complete Reader Markdown document
59
86
  */
60
87
  declare function generateReaderMarkdown(diffText: string, meta?: ReaderMarkdownMeta): string;
61
88
 
62
- export { type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, filterDiffLines, generateReaderMarkdown, normalizeCode, parseDiffToLogicalFlow, parseToFlowTree, renderFlowTree };
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
@@ -31,6 +31,20 @@ interface ReaderMarkdownMeta {
31
31
  file?: string;
32
32
  repo?: string;
33
33
  }
34
+ interface ClassNameChange {
35
+ component: string;
36
+ added: string[];
37
+ removed: string[];
38
+ }
39
+ declare function isJSXFile(filename: string): boolean;
40
+ declare function hasJSXContent(lines: string[]): boolean;
41
+ declare function isClassNameOnlyLine(line: string): boolean;
42
+ declare function extractClassName(line: string): string | null;
43
+ declare function parseClassNameChanges(addedLines: string[], removedLines: string[]): ClassNameChange[];
44
+ declare function renderStyleChanges(changes: ClassNameChange[]): string[];
45
+ declare function isJSXElement(line: string): boolean;
46
+ declare function extractJSXComponentName(line: string): string;
47
+ declare function parseJSXToFlowTree(lines: string[]): FlowNode[];
34
48
  /**
35
49
  * Step 1: Filter diff lines — added (+) and removed (-) separately
36
50
  */
@@ -54,9 +68,22 @@ declare function renderFlowTree(nodes: FlowNode[], indent?: number): string[];
54
68
  * Main entry: parse a raw diff string → ParseResult
55
69
  */
56
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;
57
84
  /**
58
85
  * Generate the complete Reader Markdown document
59
86
  */
60
87
  declare function generateReaderMarkdown(diffText: string, meta?: ReaderMarkdownMeta): string;
61
88
 
62
- export { type FlowNode, type ParseResult, Priority, type ReaderMarkdownMeta, filterDiffLines, generateReaderMarkdown, normalizeCode, parseDiffToLogicalFlow, parseToFlowTree, renderFlowTree };
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 };