prsmith 2.0.0 → 2.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/bin/cli.js CHANGED
@@ -135,6 +135,19 @@ program
135
135
 
136
136
  // Post to GitHub if specified
137
137
  if (options.github || options.pr || options.repo) {
138
+ const token = config.githubToken || process.env.GITHUB_TOKEN;
139
+ if (!token) {
140
+ console.error(
141
+ chalk.red('\n❌ Error: GitHub Personal Access Token not found!')
142
+ );
143
+ console.error(
144
+ chalk.yellow(
145
+ "Please configure 'githubToken' in your local ~/.prsmith.json file or set the GITHUB_TOKEN environment variable.\n"
146
+ )
147
+ );
148
+ process.exit(1);
149
+ }
150
+
138
151
  const detectedRepo = detectGithubRepo();
139
152
  let owner = '';
140
153
  let repoName = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prsmith",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Forge professional pull request review comments from simple prompts.",
5
5
  "keywords": [
6
6
  "pull-request",
package/src/batch.js CHANGED
@@ -113,6 +113,19 @@ export async function runBatchReview(config = {}) {
113
113
  console.error(chalk.red(`Failed to write file: ${err.message}`));
114
114
  }
115
115
  } else if (action.includes('Post')) {
116
+ const token = config.githubToken || process.env.GITHUB_TOKEN;
117
+ if (!token) {
118
+ console.log(
119
+ chalk.red('\n❌ Error: GitHub Personal Access Token not found!')
120
+ );
121
+ console.log(
122
+ chalk.yellow(
123
+ "Please configure 'githubToken' in your local ~/.prsmith.json file or set the GITHUB_TOKEN environment variable.\n"
124
+ )
125
+ );
126
+ continue;
127
+ }
128
+
116
129
  const repo = config.githubRepo || detectGithubRepo();
117
130
  const defaultOwner = repo ? repo.owner : '';
118
131
  const defaultRepo = repo ? repo.repo : '';
package/src/config.js CHANGED
@@ -15,12 +15,24 @@ export function loadConfig() {
15
15
  try {
16
16
  const fileContent = fs.readFileSync(configPath, 'utf-8');
17
17
  const parsed = JSON.parse(fileContent);
18
+
18
19
  if (parsed.templates) {
19
20
  mergedConfig.templates = {
20
21
  ...mergedConfig.templates,
21
22
  ...parsed.templates,
22
23
  };
23
24
  }
25
+
26
+ if (parsed.editor) {
27
+ mergedConfig.editor = parsed.editor;
28
+ }
29
+
30
+ // Also merge any other config properties (tokens, providers, etc.)
31
+ Object.keys(parsed).forEach((key) => {
32
+ if (key !== 'templates') {
33
+ mergedConfig[key] = parsed[key];
34
+ }
35
+ });
24
36
  } catch (err) {
25
37
  console.warn(
26
38
  `Warning: Could not parse config at ${configPath} - ${err.message}`
@@ -29,5 +41,17 @@ export function loadConfig() {
29
41
  }
30
42
  }
31
43
 
44
+ // Senior Guardrail: Prevent the general public from getting stuck in Vim (no instructions)
45
+ // If the user has not set EDITOR/VISUAL globally, and did not set 'editor' in .prsmith.json,
46
+ // we default the spawned editor in our process to 'nano' on macOS/Linux.
47
+ // 'nano' has friendly, visible exit instructions at the bottom of the terminal by default!
48
+ if (mergedConfig.editor) {
49
+ process.env.EDITOR = mergedConfig.editor;
50
+ } else if (!process.env.EDITOR && !process.env.VISUAL) {
51
+ if (process.platform !== 'win32') {
52
+ process.env.EDITOR = 'nano';
53
+ }
54
+ }
55
+
32
56
  return mergedConfig;
33
57
  }
package/src/prompts.js CHANGED
@@ -35,7 +35,8 @@ export async function getReviewData(options = {}, config = {}) {
35
35
  coreQuestions.push({
36
36
  type: 'editor',
37
37
  name: 'issue',
38
- message: 'Describe the issue:',
38
+ message:
39
+ 'Describe the issue (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
39
40
  });
40
41
  }
41
42
 
@@ -43,7 +44,8 @@ export async function getReviewData(options = {}, config = {}) {
43
44
  coreQuestions.push({
44
45
  type: 'editor',
45
46
  name: 'fix',
46
- message: 'Suggested fix:',
47
+ message:
48
+ 'Suggested fix (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
47
49
  });
48
50
  }
49
51
 
@@ -102,12 +104,14 @@ export async function getReviewData(options = {}, config = {}) {
102
104
  {
103
105
  type: 'editor',
104
106
  name: 'before',
105
- message: 'Original Code (Before):',
107
+ message:
108
+ 'Original Code (Before) (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
106
109
  },
107
110
  {
108
111
  type: 'editor',
109
112
  name: 'after',
110
- message: 'Proposed Code (After):',
113
+ message:
114
+ 'Proposed Code (After) (Vim: i to write, Esc then :wq to save & exit. Nano: Ctrl+O, Enter, Ctrl+X):',
111
115
  },
112
116
  ]);
113
117
  }
@@ -3,6 +3,7 @@ import fs from 'fs';
3
3
  import { generateMarkdown } from '../src/formatter.js';
4
4
  import { detectGithubRepo } from '../src/github.js';
5
5
  import { polishText } from '../src/ai.js';
6
+ import { loadConfig } from '../src/config.js';
6
7
 
7
8
  describe('v2.0.0 Features Test Suite', () => {
8
9
  let existsSyncSpy;
@@ -207,4 +208,46 @@ describe('v2.0.0 Features Test Suite', () => {
207
208
  );
208
209
  });
209
210
  });
211
+
212
+ describe('Editor Config Guardrails', () => {
213
+ it('should dynamically set process.env.EDITOR to nano on non-windows systems if not defined', () => {
214
+ const originalPlatform = Object.getOwnPropertyDescriptor(
215
+ process,
216
+ 'platform'
217
+ );
218
+ const originalEditor = process.env.EDITOR;
219
+ const originalVisual = process.env.VISUAL;
220
+
221
+ delete process.env.EDITOR;
222
+ delete process.env.VISUAL;
223
+
224
+ Object.defineProperty(process, 'platform', {
225
+ value: 'darwin',
226
+ configurable: true,
227
+ });
228
+
229
+ existsSyncSpy.mockReturnValue(false);
230
+
231
+ loadConfig();
232
+
233
+ expect(process.env.EDITOR).toBe('nano');
234
+
235
+ // Restore original state
236
+ Object.defineProperty(process, 'platform', originalPlatform);
237
+ if (originalEditor !== undefined) process.env.EDITOR = originalEditor;
238
+ if (originalVisual !== undefined) process.env.VISUAL = originalVisual;
239
+ });
240
+
241
+ it('should respect editor defined in config', () => {
242
+ const originalEditor = process.env.EDITOR;
243
+ existsSyncSpy.mockReturnValue(true);
244
+ readFileSyncSpy.mockReturnValue(JSON.stringify({ editor: 'vim-mock' }));
245
+
246
+ loadConfig();
247
+
248
+ expect(process.env.EDITOR).toBe('vim-mock');
249
+
250
+ if (originalEditor !== undefined) process.env.EDITOR = originalEditor;
251
+ });
252
+ });
210
253
  });