@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;
|
|
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
|
-
|
|
106
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
console.
|
|
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
|