@fractary/faber-cli 1.4.0 → 1.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;AAwYD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;AAqkBD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
@@ -22,6 +22,74 @@ export function createAuthSetupCommand() {
22
22
  await runSetup(options);
23
23
  });
24
24
  }
25
+ /**
26
+ * Manual configuration for existing GitHub App
27
+ */
28
+ async function manualAppConfiguration(configPath, org, repo) {
29
+ const os = await import('os');
30
+ const path = await import('path');
31
+ console.log(chalk.bold('━'.repeat(60)));
32
+ console.log(chalk.bold('šŸ“‹ Manual GitHub App Configuration'));
33
+ console.log(chalk.bold('━'.repeat(60)));
34
+ console.log();
35
+ console.log('You will need the following information from your GitHub App:');
36
+ console.log(' • App ID (from app settings page)');
37
+ console.log(' • Installation ID (from installations page URL)');
38
+ console.log(' • Private key file (.pem)\n');
39
+ const rl = readline.createInterface({
40
+ input: process.stdin,
41
+ output: process.stdout,
42
+ });
43
+ const appIdInput = await rl.question('App ID: ');
44
+ const installationIdInput = await rl.question('Installation ID: ');
45
+ const privateKeyPath = await rl.question('Private key path (e.g., ~/.github/faber-app.pem): ');
46
+ rl.close();
47
+ if (!appIdInput || !installationIdInput || !privateKeyPath) {
48
+ console.error(chalk.red('\nāŒ All fields are required\n'));
49
+ process.exit(1);
50
+ }
51
+ // Validate App ID is a positive integer
52
+ const appId = parseInt(appIdInput, 10);
53
+ if (isNaN(appId) || appId <= 0) {
54
+ console.error(chalk.red('\nāŒ App ID must be a positive integer\n'));
55
+ process.exit(1);
56
+ }
57
+ // Validate Installation ID is a positive integer
58
+ const installationId = parseInt(installationIdInput, 10);
59
+ if (isNaN(installationId) || installationId <= 0) {
60
+ console.error(chalk.red('\nāŒ Installation ID must be a positive integer\n'));
61
+ process.exit(1);
62
+ }
63
+ // Expand tilde in path
64
+ const expandedKeyPath = privateKeyPath.replace(/^~/, os.homedir());
65
+ // Verify private key exists
66
+ try {
67
+ await fs.access(expandedKeyPath);
68
+ }
69
+ catch (error) {
70
+ console.error(chalk.red(`\nāŒ Private key file not found: ${expandedKeyPath}\n`));
71
+ process.exit(1);
72
+ }
73
+ // Create config
74
+ const config = {
75
+ github: {
76
+ organization: org,
77
+ project: repo,
78
+ app: {
79
+ id: appId.toString(),
80
+ installation_id: installationId.toString(),
81
+ private_key_path: privateKeyPath,
82
+ },
83
+ },
84
+ };
85
+ // Save config
86
+ const configDir = path.dirname(configPath);
87
+ await fs.mkdir(configDir, { recursive: true });
88
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
89
+ console.log(chalk.green(`\nāœ“ Configuration saved to ${configPath}\n`));
90
+ console.log('Test the configuration with:');
91
+ console.log(chalk.cyan(` fractary-faber work issue fetch 1\n`));
92
+ }
25
93
  /**
26
94
  * Run the setup flow
27
95
  */
@@ -68,6 +136,22 @@ async function runSetup(options) {
68
136
  }
69
137
  console.log();
70
138
  }
139
+ // Ask user if they want to create new or configure existing app
140
+ const rl0 = readline.createInterface({
141
+ input: process.stdin,
142
+ output: process.stdout,
143
+ });
144
+ console.log(chalk.bold('Choose setup method:'));
145
+ console.log(' 1. Create a new GitHub App (guided)');
146
+ console.log(' 2. Configure an existing GitHub App (manual)\n');
147
+ const method = (await rl0.question('Enter 1 or 2: ')).trim();
148
+ rl0.close();
149
+ console.log();
150
+ if (method === '2') {
151
+ // Manual configuration for existing app
152
+ await manualAppConfiguration(configPath, org, repo);
153
+ return;
154
+ }
71
155
  // Step 3: Generate manifest
72
156
  const manifest = generateAppManifest({ organization: org, repository: repo });
73
157
  if (options.showManifest) {
@@ -102,33 +186,53 @@ async function runSetup(options) {
102
186
  }
103
187
  process.exit(1);
104
188
  }
105
- console.log('Opening GitHub App creation page in your browser...\n');
106
- // Open HTML file in default browser (safe - we control the path)
189
+ // Convert WSL path to Windows path if needed
190
+ let displayPath = htmlPath;
191
+ if (isWsl && htmlPath.startsWith('/mnt/')) {
192
+ // Convert /mnt/c/... to C:\...
193
+ const match = htmlPath.match(/^\/mnt\/([a-z])(\/.*)/);
194
+ if (match) {
195
+ const driveLetter = match[1].toUpperCase();
196
+ const windowsPath = match[2].replace(/\//g, '\\');
197
+ displayPath = `${driveLetter}:${windowsPath}`;
198
+ }
199
+ }
200
+ console.log(chalk.bold('šŸ“„ Manifest file created!\n'));
201
+ console.log(chalk.cyan('Open this file in your browser (copy the full path):\n'));
202
+ console.log(chalk.bold(` ${displayPath}\n`));
203
+ if (isWsl) {
204
+ console.log(chalk.gray('šŸ’” Tip: From Windows, you can also open it with:'));
205
+ console.log(chalk.gray(` - Press Win+R, paste the path above, press Enter`));
206
+ console.log(chalk.gray(` - Or open File Explorer and paste the path\n`));
207
+ }
208
+ // Try to open automatically, but don't block if it fails
107
209
  const { execFile } = await import('child_process');
108
210
  const { promisify } = await import('util');
109
211
  const execFileAsync = promisify(execFile);
110
212
  try {
111
213
  if (process.platform === 'darwin') {
112
214
  await execFileAsync('open', [htmlPath]);
215
+ console.log(chalk.gray('(Opened in default browser)\n'));
113
216
  }
114
217
  else if (process.platform === 'win32') {
115
- // Windows requires cmd /c start for file associations
116
218
  await execFileAsync('cmd', ['/c', 'start', '', htmlPath]);
219
+ console.log(chalk.gray('(Opened in default browser)\n'));
117
220
  }
118
- else {
119
- // Linux/Unix
221
+ else if (!isWsl) {
222
+ // Only try xdg-open on native Linux, not WSL
120
223
  await execFileAsync('xdg-open', [htmlPath]);
224
+ console.log(chalk.gray('(Opened in default browser)\n'));
121
225
  }
122
226
  }
123
227
  catch (error) {
124
- console.log(chalk.yellow('\nāš ļø Could not open browser automatically.'));
125
- console.log(chalk.yellow(`Please open this file manually:\n ${htmlPath}\n`));
228
+ // Silently fail - we already printed the path above
126
229
  }
127
230
  console.log(chalk.bold('In your browser:'));
128
231
  console.log(' 1. Review the app permissions');
129
232
  console.log(' 2. Click the green "Create GitHub App →" button');
130
233
  console.log(' 3. GitHub will ask you to confirm - click "Create GitHub App" again');
131
- console.log(' 4. After creation, look at the browser URL bar\n');
234
+ console.log(' 4. After creation, GitHub will redirect to example.com');
235
+ console.log(chalk.yellow(' (This is expected! The code you need will be in the URL)\n'));
132
236
  const rl1 = readline.createInterface({
133
237
  input: process.stdin,
134
238
  output: process.stdout,
@@ -141,7 +245,10 @@ async function runSetup(options) {
141
245
  console.log(chalk.bold('šŸ“‹ STEP 2: Copy the code from the redirect URL'));
142
246
  console.log(chalk.bold('━'.repeat(60)));
143
247
  console.log();
144
- console.log('After creating the app, GitHub will redirect you to a URL like:');
248
+ console.log('After creating the app, GitHub will redirect to example.com');
249
+ console.log(chalk.yellow('(Don\'t worry - example.com is just a placeholder!)'));
250
+ console.log();
251
+ console.log('The URL will look like:');
145
252
  console.log(chalk.gray('https://github.com/settings/apps/your-app?code=XXXXXXXXXXXXX'));
146
253
  console.log();
147
254
  console.log('Copy the entire code from the URL bar and paste it below:\n');
@@ -196,15 +303,68 @@ async function runSetup(options) {
196
303
  installationId = await getInstallationId(conversionResponse.id.toString(), conversionResponse.pem, org);
197
304
  }
198
305
  catch (error) {
199
- if (error instanceof Error) {
200
- console.error(chalk.red('\nāŒ Error: ' + error.message + '\n'));
306
+ // App created but not installed yet - guide user through installation
307
+ console.log(chalk.yellow('\nāš ļø App created but not yet installed on your organization\n'));
308
+ console.log(chalk.bold('━'.repeat(60)));
309
+ console.log(chalk.bold('šŸ“‹ STEP 3: Install the App on Your Organization'));
310
+ console.log(chalk.bold('━'.repeat(60)));
311
+ console.log();
312
+ const installUrl = `https://github.com/apps/${conversionResponse.slug}/installations/new`;
313
+ console.log('The app needs to be installed on your GitHub organization/repositories.');
314
+ console.log('Opening installation page in your browser...\n');
315
+ // Convert to Windows path if WSL
316
+ const os = await import('os');
317
+ const isWsl = process.platform === 'linux' && os.release().toLowerCase().includes('microsoft');
318
+ // Try to open installation URL
319
+ const { execFile } = await import('child_process');
320
+ const { promisify } = await import('util');
321
+ const execFileAsync = promisify(execFile);
322
+ console.log(chalk.cyan(`Installation URL: ${installUrl}\n`));
323
+ try {
324
+ if (process.platform === 'darwin') {
325
+ await execFileAsync('open', [installUrl]);
326
+ }
327
+ else if (process.platform === 'win32') {
328
+ await execFileAsync('cmd', ['/c', 'start', '', installUrl]);
329
+ }
330
+ else if (!isWsl) {
331
+ await execFileAsync('xdg-open', [installUrl]);
332
+ }
333
+ else {
334
+ // WSL - can't auto-open, show instructions
335
+ console.log(chalk.yellow('šŸ’” Copy the URL above and open it in your Windows browser\n'));
336
+ }
201
337
  }
202
- else {
203
- console.error(chalk.red('\nāŒ Unknown error occurred\n'));
338
+ catch (openError) {
339
+ console.log(chalk.yellow('šŸ’” Copy the URL above and open it in your browser\n'));
340
+ }
341
+ console.log('In your browser:');
342
+ console.log(' 1. Select which repositories to give access to:');
343
+ console.log(' - "All repositories" (easiest) OR');
344
+ console.log(` - "Only select repositories" → choose "${repo}"`);
345
+ console.log(' 2. Click the green "Install" button\n');
346
+ const rl2 = readline.createInterface({
347
+ input: process.stdin,
348
+ output: process.stdout,
349
+ });
350
+ await rl2.question('Press Enter after you have installed the app...');
351
+ rl2.close();
352
+ // Try fetching installation ID again
353
+ console.log(chalk.gray('\nVerifying installation...'));
354
+ try {
355
+ installationId = await getInstallationId(conversionResponse.id.toString(), conversionResponse.pem, org);
356
+ console.log(chalk.green(`āœ“ Installation verified! Installation ID: ${chalk.cyan(installationId)}\n`));
357
+ }
358
+ catch (retryError) {
359
+ console.error(chalk.red('\nāŒ Still unable to find installation.\n'));
360
+ console.log('Please ensure you:');
361
+ console.log(' 1. Clicked "Install" on the installation page');
362
+ console.log(' 2. Selected at least one repository');
363
+ console.log(` 3. Installed on the "${org}" organization\n`);
364
+ console.log(`Visit: ${chalk.cyan(installUrl)}`);
365
+ console.log('\nAfter installing, run this command again to complete setup.\n');
366
+ process.exit(1);
204
367
  }
205
- console.log('The app was created but could not find the installation.');
206
- console.log(`Please install the app on your organization: ${chalk.cyan(`https://github.com/apps/${conversionResponse.slug}/installations/new`)}`);
207
- process.exit(1);
208
368
  }
209
369
  console.log(chalk.green(`āœ“ Installation ID: ${chalk.cyan(installationId)}\n`));
210
370
  // Step 8: Save private key
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fractary/faber-cli",
3
- "version": "1.4.0",
3
+ "version": "1.4.3",
4
4
  "description": "FABER CLI - Command-line interface for FABER development toolkit",
5
5
  "main": "dist/index.js",
6
6
  "bin": {