gh-load-pull-request 0.4.2 → 0.6.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/README.md CHANGED
@@ -36,12 +36,8 @@ gh-load-pull-request owner/private-repo#456
36
36
  Install globally for system-wide access:
37
37
 
38
38
  ```bash
39
- # Using bun
40
39
  bun install -g gh-load-pull-request
41
40
 
42
- # Using npm
43
- npm install -g gh-load-pull-request
44
-
45
41
  # After installation, use anywhere:
46
42
  gh-load-pull-request --help
47
43
  ```
@@ -51,11 +47,7 @@ gh-load-pull-request --help
51
47
  Remove the global installation:
52
48
 
53
49
  ```bash
54
- # Using bun
55
50
  bun uninstall -g gh-load-pull-request
56
-
57
- # Using npm
58
- npm uninstall -g gh-load-pull-request
59
51
  ```
60
52
 
61
53
  ### Local Installation
@@ -66,7 +58,7 @@ git clone https://github.com/link-foundation/gh-load-pull-request.git
66
58
  cd gh-load-pull-request
67
59
 
68
60
  # Install dependencies
69
- npm install
61
+ bun install
70
62
 
71
63
  # Make the script executable
72
64
  chmod +x gh-load-pull-request.mjs
@@ -174,7 +166,7 @@ gh-load-pull-request owner/repo#123 | claude-analyze
174
166
 
175
167
  ## Requirements
176
168
 
177
- - [Bun](https://bun.sh/) (>=1.2.0) or [Node.js](https://nodejs.org/) (>=22.17.0) runtime
169
+ - [Bun](https://bun.sh/) (>=1.2.0) runtime
178
170
  - For private repositories (optional):
179
171
  - [GitHub CLI](https://cli.github.com/) (recommended) OR
180
172
  - GitHub personal access token (via `--token` or `GITHUB_TOKEN` env var)
@@ -191,11 +183,7 @@ gh-load-pull-request owner/repo#123 | claude-analyze
191
183
 
192
184
  ```bash
193
185
  # Run all tests
194
- npm test
195
-
196
- # Or run test files directly
197
- node tests/all.test.mjs
198
- node tests/cli.test.mjs
186
+ bun test
199
187
  ```
200
188
 
201
189
  ## Development
@@ -206,7 +194,7 @@ git clone https://github.com/link-foundation/gh-load-pull-request.git
206
194
  cd gh-load-pull-request
207
195
 
208
196
  # Install dependencies
209
- npm install
197
+ bun install
210
198
 
211
199
  # Make executable
212
200
  chmod +x gh-load-pull-request.mjs
@@ -215,10 +203,10 @@ chmod +x gh-load-pull-request.mjs
215
203
  ./gh-load-pull-request.mjs owner/repo#123
216
204
 
217
205
  # Run tests
218
- npm test
206
+ bun test
219
207
 
220
208
  # Run linting
221
- npm run lint
209
+ bun run lint
222
210
 
223
211
  # Bump version
224
212
  ./version.mjs patch # or minor, major
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gh-load-pull-request",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "Download GitHub pull request and convert it to markdown",
5
5
  "type": "module",
6
6
  "main": "src/gh-load-pull-request.mjs",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "homepage": "https://github.com/link-foundation/gh-load-pull-request#readme",
46
46
  "engines": {
47
- "node": ">=20.0.0"
47
+ "bun": ">=1.2.0"
48
48
  },
49
49
  "files": [
50
50
  "src/",
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Formatters for gh-load-pull-request
3
+ * Contains functions for converting PR data to markdown and JSON formats
4
+ */
5
+
6
+ /**
7
+ * Format a date string for display
8
+ * @param {string} dateStr - ISO date string
9
+ * @returns {string} Formatted date string
10
+ */
11
+ export function formatDate(dateStr) {
12
+ if (!dateStr) {
13
+ return '';
14
+ }
15
+ const date = new Date(dateStr);
16
+ return date
17
+ .toISOString()
18
+ .replace('T', ' ')
19
+ .replace(/\.\d+Z$/, ' UTC');
20
+ }
21
+
22
+ /**
23
+ * Convert PR data to JSON format
24
+ * @param {Object} data - PR data from loadPullRequest
25
+ * @param {Array} downloadedImages - Array of downloaded image info
26
+ * @returns {string} JSON string
27
+ */
28
+ export function convertToJson(data, downloadedImages = []) {
29
+ const { pr, files, comments, reviewComments, reviews, commits } = data;
30
+
31
+ return JSON.stringify(
32
+ {
33
+ pullRequest: {
34
+ number: pr.number,
35
+ title: pr.title,
36
+ state: pr.state,
37
+ draft: pr.draft,
38
+ merged: pr.merged,
39
+ url: pr.html_url,
40
+ author: {
41
+ login: pr.user.login,
42
+ url: `https://github.com/${pr.user.login}`,
43
+ },
44
+ createdAt: pr.created_at,
45
+ updatedAt: pr.updated_at,
46
+ mergedAt: pr.merged_at,
47
+ closedAt: pr.closed_at,
48
+ mergedBy: pr.merged_by
49
+ ? {
50
+ login: pr.merged_by.login,
51
+ url: `https://github.com/${pr.merged_by.login}`,
52
+ }
53
+ : null,
54
+ base: {
55
+ ref: pr.base.ref,
56
+ sha: pr.base.sha,
57
+ },
58
+ head: {
59
+ ref: pr.head.ref,
60
+ sha: pr.head.sha,
61
+ },
62
+ additions: pr.additions,
63
+ deletions: pr.deletions,
64
+ changedFiles: pr.changed_files,
65
+ labels: pr.labels?.map((l) => ({ name: l.name, color: l.color })) || [],
66
+ assignees:
67
+ pr.assignees?.map((a) => ({
68
+ login: a.login,
69
+ url: `https://github.com/${a.login}`,
70
+ })) || [],
71
+ requestedReviewers:
72
+ pr.requested_reviewers?.map((r) => ({
73
+ login: r.login,
74
+ url: `https://github.com/${r.login}`,
75
+ })) || [],
76
+ milestone: pr.milestone
77
+ ? { title: pr.milestone.title, number: pr.milestone.number }
78
+ : null,
79
+ body: pr.body,
80
+ },
81
+ commits: commits.map((c) => ({
82
+ sha: c.sha,
83
+ message: c.commit.message,
84
+ author: c.author?.login || c.commit.author?.name || 'unknown',
85
+ url: c.html_url,
86
+ date: c.commit.author?.date,
87
+ })),
88
+ files: files.map((f) => ({
89
+ filename: f.filename,
90
+ status: f.status,
91
+ additions: f.additions,
92
+ deletions: f.deletions,
93
+ previousFilename: f.previous_filename,
94
+ patch: f.patch,
95
+ })),
96
+ reviews: reviews.map((r) => ({
97
+ id: r.id,
98
+ author: r.user.login,
99
+ state: r.state,
100
+ body: r.body,
101
+ submittedAt: r.submitted_at,
102
+ })),
103
+ reviewComments: reviewComments.map((c) => ({
104
+ id: c.id,
105
+ author: c.user.login,
106
+ body: c.body,
107
+ path: c.path,
108
+ line: c.line,
109
+ createdAt: c.created_at,
110
+ diffHunk: c.diff_hunk,
111
+ reviewId: c.pull_request_review_id,
112
+ })),
113
+ comments: comments.map((c) => ({
114
+ id: c.id,
115
+ author: c.user.login,
116
+ body: c.body,
117
+ createdAt: c.created_at,
118
+ })),
119
+ downloadedImages: downloadedImages.map((img) => ({
120
+ originalUrl: img.originalUrl,
121
+ localPath: img.relativePath,
122
+ format: img.format,
123
+ })),
124
+ },
125
+ null,
126
+ 2
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Generate markdown for metadata section
132
+ * @param {Object} pr - Pull request data
133
+ * @returns {string} Markdown content
134
+ */
135
+ export function generateMetadataMarkdown(pr) {
136
+ let markdown = `## Metadata\n\n`;
137
+
138
+ markdown += `| Field | Value |\n`;
139
+ markdown += `|-------|-------|\n`;
140
+ markdown += `| **Number** | #${pr.number} |\n`;
141
+ markdown += `| **URL** | ${pr.html_url} |\n`;
142
+ markdown += `| **Author** | [@${pr.user.login}](https://github.com/${pr.user.login}) |\n`;
143
+ markdown += `| **State** | ${pr.state}${pr.merged ? ' (merged)' : pr.draft ? ' (draft)' : ''} |\n`;
144
+ markdown += `| **Created** | ${formatDate(pr.created_at)} |\n`;
145
+ markdown += `| **Updated** | ${formatDate(pr.updated_at)} |\n`;
146
+
147
+ if (pr.merged_at) {
148
+ markdown += `| **Merged** | ${formatDate(pr.merged_at)} |\n`;
149
+ if (pr.merged_by) {
150
+ markdown += `| **Merged by** | [@${pr.merged_by.login}](https://github.com/${pr.merged_by.login}) |\n`;
151
+ }
152
+ }
153
+ if (pr.closed_at && !pr.merged_at) {
154
+ markdown += `| **Closed** | ${formatDate(pr.closed_at)} |\n`;
155
+ }
156
+
157
+ markdown += `| **Base** | \`${pr.base.ref}\` |\n`;
158
+ markdown += `| **Head** | \`${pr.head.ref}\` |\n`;
159
+ markdown += `| **Additions** | +${pr.additions} |\n`;
160
+ markdown += `| **Deletions** | -${pr.deletions} |\n`;
161
+ markdown += `| **Changed Files** | ${pr.changed_files} |\n`;
162
+ markdown += '\n';
163
+
164
+ if (pr.labels && pr.labels.length > 0) {
165
+ markdown += `**Labels:** ${pr.labels.map((l) => `\`${l.name}\``).join(', ')}\n\n`;
166
+ }
167
+
168
+ if (pr.assignees && pr.assignees.length > 0) {
169
+ markdown += `**Assignees:** ${pr.assignees.map((a) => `[@${a.login}](https://github.com/${a.login})`).join(', ')}\n\n`;
170
+ }
171
+
172
+ if (pr.requested_reviewers && pr.requested_reviewers.length > 0) {
173
+ markdown += `**Requested Reviewers:** ${pr.requested_reviewers.map((r) => `[@${r.login}](https://github.com/${r.login})`).join(', ')}\n\n`;
174
+ }
175
+
176
+ if (pr.milestone) {
177
+ markdown += `**Milestone:** ${pr.milestone.title}\n\n`;
178
+ }
179
+
180
+ return markdown;
181
+ }
182
+
183
+ /**
184
+ * Generate markdown for commits section
185
+ * @param {Array} commits - Array of commit data
186
+ * @returns {string} Markdown content
187
+ */
188
+ export function generateCommitsMarkdown(commits) {
189
+ if (commits.length === 0) {
190
+ return '';
191
+ }
192
+
193
+ let markdown = `## Commits (${commits.length})\n\n`;
194
+
195
+ for (const commit of commits) {
196
+ const message = commit.commit.message.split('\n')[0];
197
+ const sha = commit.sha.substring(0, 7);
198
+ const author =
199
+ commit.author?.login || commit.commit.author?.name || 'unknown';
200
+ const authorLink = commit.author
201
+ ? `[@${author}](https://github.com/${author})`
202
+ : author;
203
+ markdown += `- [\`${sha}\`](${commit.html_url}) ${message} — ${authorLink}\n`;
204
+ }
205
+
206
+ markdown += '\n';
207
+ return markdown;
208
+ }
209
+
210
+ /**
211
+ * Generate markdown for files changed section
212
+ * @param {Array} files - Array of file data
213
+ * @returns {string} Markdown content
214
+ */
215
+ export function generateFilesMarkdown(files) {
216
+ if (files.length === 0) {
217
+ return '';
218
+ }
219
+
220
+ let markdown = `## Files Changed (${files.length})\n\n`;
221
+ markdown += `| Status | File | Changes |\n`;
222
+ markdown += `|--------|------|--------:|\n`;
223
+
224
+ for (const file of files) {
225
+ const statusIcon =
226
+ file.status === 'added'
227
+ ? '🆕 Added'
228
+ : file.status === 'removed'
229
+ ? '🗑️ Removed'
230
+ : file.status === 'modified'
231
+ ? '✏️ Modified'
232
+ : file.status === 'renamed'
233
+ ? '📝 Renamed'
234
+ : `📄 ${file.status}`;
235
+ const changes = `+${file.additions} -${file.deletions}`;
236
+ let filename = file.filename;
237
+ if (file.status === 'renamed' && file.previous_filename) {
238
+ filename = `${file.previous_filename} → ${file.filename}`;
239
+ }
240
+ markdown += `| ${statusIcon} | \`${filename}\` | ${changes} |\n`;
241
+ }
242
+
243
+ markdown += '\n';
244
+ return markdown;
245
+ }