@fractary/faber-cli 1.4.1 → 1.4.4

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;AA6ZD;;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) {
@@ -147,7 +231,8 @@ async function runSetup(options) {
147
231
  console.log(' 1. Review the app permissions');
148
232
  console.log(' 2. Click the green "Create GitHub App →" button');
149
233
  console.log(' 3. GitHub will ask you to confirm - click "Create GitHub App" again');
150
- 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'));
151
236
  const rl1 = readline.createInterface({
152
237
  input: process.stdin,
153
238
  output: process.stdout,
@@ -160,7 +245,10 @@ async function runSetup(options) {
160
245
  console.log(chalk.bold('šŸ“‹ STEP 2: Copy the code from the redirect URL'));
161
246
  console.log(chalk.bold('━'.repeat(60)));
162
247
  console.log();
163
- 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:');
164
252
  console.log(chalk.gray('https://github.com/settings/apps/your-app?code=XXXXXXXXXXXXX'));
165
253
  console.log();
166
254
  console.log('Copy the entire code from the URL bar and paste it below:\n');
@@ -215,15 +303,68 @@ async function runSetup(options) {
215
303
  installationId = await getInstallationId(conversionResponse.id.toString(), conversionResponse.pem, org);
216
304
  }
217
305
  catch (error) {
218
- if (error instanceof Error) {
219
- 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
+ }
220
337
  }
221
- else {
222
- 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);
223
367
  }
224
- console.log('The app was created but could not find the installation.');
225
- console.log(`Please install the app on your organization: ${chalk.cyan(`https://github.com/apps/${conversionResponse.slug}/installations/new`)}`);
226
- process.exit(1);
227
368
  }
228
369
  console.log(chalk.green(`āœ“ Installation ID: ${chalk.cyan(installationId)}\n`));
229
370
  // Step 8: Save private key
@@ -260,10 +260,12 @@ async function assignWorkflows(issues, availableWorkflows, options, outputFormat
260
260
  validateWorkflowName(workflow);
261
261
  }
262
262
  if (!workflow) {
263
- // Extract from issue labels
264
- const workflowLabel = issue.labels.find(label => label.startsWith('workflow:'));
263
+ // Extract from issue labels - support both 'workflow:' and 'faber-workflow:' prefixes
264
+ const workflowLabel = issue.labels.find(label => label.startsWith('workflow:') || label.startsWith('faber-workflow:'));
265
265
  if (workflowLabel) {
266
- workflow = workflowLabel.replace('workflow:', '');
266
+ workflow = workflowLabel
267
+ .replace(/^workflow:/, '')
268
+ .replace(/^faber-workflow:/, '');
267
269
  // Validate extracted workflow name
268
270
  validateWorkflowName(workflow);
269
271
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,oBAAoB,CAAC;AAEpE;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAE5B,OAAO;IAIP;;OAEG;WACU,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAkEzC;;;OAGG;mBACkB,cAAc;IAwBnC;;OAEG;mBACkB,8BAA8B;IAoBnD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAyBnC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,WAAW,GAAG,GAAG;CAGjC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,oBAAoB,CAAC;AAEpE;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAE5B,OAAO;IAIP;;OAEG;WACU,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAoFzC;;;OAGG;mBACkB,cAAc;IAwBnC;;OAEG;mBACkB,8BAA8B;IAoBnD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAyBnC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,WAAW,GAAG,GAAG;CAGjC"}
@@ -67,9 +67,27 @@ export class ConfigManager {
67
67
  };
68
68
  }
69
69
  if (!config.workflow?.config_path) {
70
+ // Try new location first, fall back to legacy location for backward compatibility
71
+ const newPath = path.join(process.cwd(), '.fractary', 'faber', 'workflows');
72
+ const legacyPath = path.join(process.cwd(), '.fractary', 'plugins', 'faber', 'workflows');
73
+ let workflowPath = newPath;
74
+ try {
75
+ await fs.access(newPath);
76
+ }
77
+ catch {
78
+ // New path doesn't exist, try legacy path
79
+ try {
80
+ await fs.access(legacyPath);
81
+ workflowPath = legacyPath;
82
+ }
83
+ catch {
84
+ // Neither exists, use new path as default (will be created if needed)
85
+ workflowPath = newPath;
86
+ }
87
+ }
70
88
  config.workflow = {
71
89
  ...config.workflow,
72
- config_path: path.join(process.cwd(), 'plugins', 'faber', 'config', 'workflows'),
90
+ config_path: workflowPath,
73
91
  };
74
92
  }
75
93
  return config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fractary/faber-cli",
3
- "version": "1.4.1",
3
+ "version": "1.4.4",
4
4
  "description": "FABER CLI - Command-line interface for FABER development toolkit",
5
5
  "main": "dist/index.js",
6
6
  "bin": {