hale-commenting-system 1.0.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.
@@ -0,0 +1,843 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk11 from "chalk";
6
+
7
+ // src/commands/init.ts
8
+ import chalk9 from "chalk";
9
+ import ora from "ora";
10
+ import inquirer3 from "inquirer";
11
+
12
+ // src/utils/detect.ts
13
+ import fs from "fs/promises";
14
+ import path from "path";
15
+ async function detectProject(cwd) {
16
+ const packageJsonPath = path.join(cwd, "package.json");
17
+ let packageJson = {};
18
+ try {
19
+ const content = await fs.readFile(packageJsonPath, "utf-8");
20
+ packageJson = JSON.parse(content);
21
+ } catch (error) {
22
+ throw new Error("No package.json found. Are you in a Node.js project?");
23
+ }
24
+ const deps = {
25
+ ...packageJson.dependencies,
26
+ ...packageJson.devDependencies
27
+ };
28
+ const buildTool = await detectBuildTool(cwd, deps);
29
+ return {
30
+ root: cwd,
31
+ framework: detectFramework(deps),
32
+ buildTool,
33
+ isReact: !!deps["react"],
34
+ hasPatternFly: !!deps["@patternfly/react-core"],
35
+ hasTypeScript: !!deps["typescript"],
36
+ packageJson
37
+ };
38
+ }
39
+ function detectFramework(deps) {
40
+ if (deps["react"]) return "React";
41
+ if (deps["vue"]) return "Vue";
42
+ if (deps["@angular/core"]) return "Angular";
43
+ return "Unknown";
44
+ }
45
+ async function detectBuildTool(cwd, deps) {
46
+ if (deps["vite"]) return "Vite";
47
+ if (deps["webpack"]) return "Webpack";
48
+ try {
49
+ await fs.access(path.join(cwd, "next.config.js"));
50
+ return "Next.js";
51
+ } catch {
52
+ }
53
+ return "Unknown";
54
+ }
55
+ async function detectPlatform(cwd) {
56
+ try {
57
+ await fs.access(path.join(cwd, "vercel.json"));
58
+ return "vercel";
59
+ } catch {
60
+ }
61
+ try {
62
+ await fs.access(path.join(cwd, "netlify.toml"));
63
+ return "netlify";
64
+ } catch {
65
+ }
66
+ return null;
67
+ }
68
+
69
+ // src/utils/logger.ts
70
+ import chalk from "chalk";
71
+ function printWelcome() {
72
+ console.log(chalk.bold.cyan("\n\u{1F680} Apollo Commenting System Setup Wizard\n"));
73
+ }
74
+ function printSetupInstructions(platform, project) {
75
+ console.log(chalk.bold("\n\u{1F4DD} Manual Setup Required:\n"));
76
+ console.log(chalk.cyan("1. Add the CommentProvider to your App.tsx:\n"));
77
+ console.log(chalk.white(`import {
78
+ CommentProvider,
79
+ VersionProvider,
80
+ GitHubAuthProvider,
81
+ CommentOverlay,
82
+ CommentDrawer
83
+ } from '@apollo/commenting-system';
84
+ import apolloCommentsConfig from './apollo-comments.config.json';
85
+ import React from 'react';
86
+
87
+ function App() {
88
+ const [selectedThreadId, setSelectedThreadId] = React.useState<string | null>(null);
89
+
90
+ return (
91
+ <GitHubAuthProvider config={apolloCommentsConfig}>
92
+ <VersionProvider>
93
+ <CommentProvider>
94
+ <CommentDrawer
95
+ selectedThreadId={selectedThreadId}
96
+ onThreadSelect={setSelectedThreadId}
97
+ >
98
+ <CommentOverlay
99
+ selectedThreadId={selectedThreadId}
100
+ onThreadSelect={setSelectedThreadId}
101
+ />
102
+ {/* Your app content */}
103
+ </CommentDrawer>
104
+ </CommentProvider>
105
+ </VersionProvider>
106
+ </GitHubAuthProvider>
107
+ );
108
+ }
109
+ `));
110
+ console.log(chalk.cyan("\n2. Deploy your serverless functions:\n"));
111
+ if (platform === "vercel") {
112
+ console.log(chalk.white(" vercel --prod"));
113
+ } else if (platform === "netlify") {
114
+ console.log(chalk.white(" netlify deploy --prod"));
115
+ } else {
116
+ console.log(chalk.white(" Follow your platform's deployment guide"));
117
+ }
118
+ console.log(chalk.cyan("\n3. Update your GitHub OAuth App callback URL:\n"));
119
+ console.log(chalk.white(" https://your-domain.com/api/github-oauth-callback\n"));
120
+ }
121
+ function printSuccess() {
122
+ console.log(chalk.green.bold("\n\u2713 Setup complete!\n"));
123
+ }
124
+ function printNextSteps() {
125
+ console.log(chalk.cyan("\nNext steps:"));
126
+ console.log(chalk.white("1. Review generated files in your project"));
127
+ console.log(chalk.white("2. Add the CommentProvider to your App.tsx (see instructions above)"));
128
+ console.log(chalk.white("3. Deploy your serverless functions"));
129
+ console.log(chalk.white("4. Run: npm run dev\n"));
130
+ console.log(chalk.dim('Run "apollo-comments validate" to verify your setup.\n'));
131
+ }
132
+ function printWarning(message) {
133
+ console.log(chalk.yellow(`\u26A0\uFE0F ${message}`));
134
+ }
135
+
136
+ // src/prompts/platform.ts
137
+ import inquirer from "inquirer";
138
+ import chalk2 from "chalk";
139
+ async function promptPlatform(projectRoot) {
140
+ const detectedPlatform = await detectPlatform(projectRoot);
141
+ if (detectedPlatform) {
142
+ console.log(chalk2.green(`\u2713 Detected ${detectedPlatform} configuration`));
143
+ const { usePlatform } = await inquirer.prompt([
144
+ {
145
+ type: "confirm",
146
+ name: "usePlatform",
147
+ message: `Use ${detectedPlatform} for serverless functions?`,
148
+ default: true
149
+ }
150
+ ]);
151
+ if (usePlatform) {
152
+ return detectedPlatform;
153
+ }
154
+ }
155
+ const { platform } = await inquirer.prompt([
156
+ {
157
+ type: "list",
158
+ name: "platform",
159
+ message: "Select your deployment platform:",
160
+ choices: [
161
+ { name: "Vercel", value: "vercel" },
162
+ { name: "Netlify", value: "netlify" },
163
+ { name: "Manual (I will configure myself)", value: "manual" }
164
+ ]
165
+ }
166
+ ]);
167
+ return platform;
168
+ }
169
+
170
+ // src/prompts/github.ts
171
+ import inquirer2 from "inquirer";
172
+ import chalk3 from "chalk";
173
+ async function promptGitHubConfig(skipPrompts = false) {
174
+ if (skipPrompts) {
175
+ console.log(chalk3.yellow("Using default/environment values..."));
176
+ return {
177
+ clientId: process.env.GITHUB_CLIENT_ID || "",
178
+ clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
179
+ owner: process.env.GITHUB_OWNER || "",
180
+ repo: process.env.GITHUB_REPO || ""
181
+ };
182
+ }
183
+ console.log(chalk3.bold("\n\u{1F4CB} GitHub OAuth Configuration\n"));
184
+ console.log(chalk3.dim("You need to create a GitHub OAuth App:"));
185
+ console.log(chalk3.dim("https://github.com/settings/developers\n"));
186
+ const answers = await inquirer2.prompt([
187
+ {
188
+ type: "input",
189
+ name: "clientId",
190
+ message: "GitHub OAuth Client ID:",
191
+ validate: (input) => input.length > 0 || "Client ID is required"
192
+ },
193
+ {
194
+ type: "password",
195
+ name: "clientSecret",
196
+ message: "GitHub OAuth Client Secret:",
197
+ mask: "*",
198
+ validate: (input) => input.length > 0 || "Client Secret is required"
199
+ },
200
+ {
201
+ type: "input",
202
+ name: "owner",
203
+ message: "GitHub Repository Owner (user or org):",
204
+ validate: (input) => input.length > 0 || "Owner is required"
205
+ },
206
+ {
207
+ type: "input",
208
+ name: "repo",
209
+ message: "GitHub Repository Name:",
210
+ validate: (input) => input.length > 0 || "Repository name is required"
211
+ }
212
+ ]);
213
+ return answers;
214
+ }
215
+
216
+ // src/generators/serverless.ts
217
+ import path2 from "path";
218
+ import chalk4 from "chalk";
219
+
220
+ // src/utils/fs.ts
221
+ import fs2 from "fs/promises";
222
+ async function fileExists(filePath) {
223
+ try {
224
+ await fs2.access(filePath);
225
+ return true;
226
+ } catch {
227
+ return false;
228
+ }
229
+ }
230
+ async function ensureDir(dirPath) {
231
+ await fs2.mkdir(dirPath, { recursive: true });
232
+ }
233
+ async function appendToFile(filePath, content) {
234
+ await fs2.appendFile(filePath, content, "utf-8");
235
+ }
236
+ async function readFile(filePath) {
237
+ return fs2.readFile(filePath, "utf-8");
238
+ }
239
+ async function writeFile(filePath, content) {
240
+ await fs2.writeFile(filePath, content, "utf-8");
241
+ }
242
+
243
+ // src/templates/vercel-auth.ts
244
+ var vercelAuthLoginTemplate = `import { VercelRequest, VercelResponse } from '@vercel/node';
245
+
246
+ export default async function handler(req: VercelRequest, res: VercelResponse): Promise<void> {
247
+ try {
248
+ const clientId = process.env.GITHUB_CLIENT_ID || process.env.VITE_GITHUB_CLIENT_ID;
249
+
250
+ if (!clientId) {
251
+ res.status(500).json({ error: 'GitHub OAuth not configured - missing client ID' });
252
+ return;
253
+ }
254
+
255
+ // Get the base URL from the request
256
+ const protocol = (req.headers['x-forwarded-proto'] as string) || 'https';
257
+ const host = (req.headers.host || req.headers['x-forwarded-host']) as string;
258
+
259
+ if (!host) {
260
+ res.status(500).json({ error: 'Could not determine host' });
261
+ return;
262
+ }
263
+
264
+ const baseUrl = \`\${protocol}://\${host}\`;
265
+ const redirectUri = \`\${baseUrl}/api/github-oauth-callback\`;
266
+
267
+ // Redirect to GitHub OAuth
268
+ // Scope: public_repo allows read/write access to public repositories only
269
+ const githubAuthUrl = \`https://github.com/login/oauth/authorize?client_id=\${clientId}&redirect_uri=\${encodeURIComponent(redirectUri)}&scope=public_repo\`;
270
+
271
+ res.redirect(302, githubAuthUrl);
272
+ } catch (error: any) {
273
+ res.status(500).json({
274
+ error: 'Internal server error',
275
+ details: error.message
276
+ });
277
+ }
278
+ }
279
+ `;
280
+ var vercelAuthCallbackTemplate = `import { VercelRequest, VercelResponse } from '@vercel/node';
281
+ import axios from 'axios';
282
+
283
+ export default async function handler(req: VercelRequest, res: VercelResponse): Promise<void> {
284
+ try {
285
+ const code = req.query.code as string;
286
+ const clientId = process.env.GITHUB_CLIENT_ID || process.env.VITE_GITHUB_CLIENT_ID;
287
+ const clientSecret = process.env.GITHUB_CLIENT_SECRET;
288
+
289
+ if (!code) {
290
+ res.status(400).json({ error: 'No code provided' });
291
+ return;
292
+ }
293
+
294
+ if (!clientId || !clientSecret) {
295
+ res.status(500).json({ error: 'GitHub OAuth not configured properly' });
296
+ return;
297
+ }
298
+
299
+ // Exchange code for access token
300
+ const tokenResponse = await axios.post(
301
+ 'https://github.com/login/oauth/access_token',
302
+ {
303
+ client_id: clientId,
304
+ client_secret: clientSecret,
305
+ code,
306
+ },
307
+ {
308
+ headers: {
309
+ Accept: 'application/json',
310
+ },
311
+ }
312
+ );
313
+
314
+ const accessToken = tokenResponse.data.access_token;
315
+
316
+ if (!accessToken) {
317
+ throw new Error('No access token received');
318
+ }
319
+
320
+ // Get user info
321
+ const userResponse = await axios.get('https://api.github.com/user', {
322
+ headers: {
323
+ Authorization: \`token \${accessToken}\`,
324
+ },
325
+ });
326
+
327
+ const user = userResponse.data;
328
+
329
+ // Redirect back to the app with token in URL fragment (client-side only)
330
+ const protocol = req.headers['x-forwarded-proto'] || 'https';
331
+ const host = req.headers.host || req.headers['x-forwarded-host'];
332
+ const baseUrl = \`\${protocol}://\${host}\`;
333
+ const redirectUrl = \`\${baseUrl}/#/auth-callback?token=\${accessToken}&login=\${user.login}&avatar=\${encodeURIComponent(user.avatar_url)}\`;
334
+
335
+ res.redirect(302, redirectUrl);
336
+ } catch (error: any) {
337
+ res.status(500).json({
338
+ error: 'Failed to exchange code for token',
339
+ details: error.message,
340
+ });
341
+ }
342
+ }
343
+ `;
344
+
345
+ // src/templates/netlify-auth.ts
346
+ var netlifyAuthLoginTemplate = `import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
347
+ import axios from 'axios';
348
+
349
+ const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
350
+ try {
351
+ const clientId = process.env.GITHUB_CLIENT_ID || process.env.VITE_GITHUB_CLIENT_ID;
352
+
353
+ if (!clientId) {
354
+ return {
355
+ statusCode: 500,
356
+ body: JSON.stringify({ error: 'GitHub OAuth not configured - missing client ID' })
357
+ };
358
+ }
359
+
360
+ // Get the base URL from the request
361
+ const protocol = event.headers['x-forwarded-proto'] || 'https';
362
+ const host = event.headers.host;
363
+
364
+ if (!host) {
365
+ return {
366
+ statusCode: 500,
367
+ body: JSON.stringify({ error: 'Could not determine host' })
368
+ };
369
+ }
370
+
371
+ const baseUrl = \`\${protocol}://\${host}\`;
372
+ const redirectUri = \`\${baseUrl}/.netlify/functions/github-oauth-callback\`;
373
+
374
+ // Redirect to GitHub OAuth
375
+ const githubAuthUrl = \`https://github.com/login/oauth/authorize?client_id=\${clientId}&redirect_uri=\${encodeURIComponent(redirectUri)}&scope=public_repo\`;
376
+
377
+ return {
378
+ statusCode: 302,
379
+ headers: {
380
+ Location: githubAuthUrl
381
+ },
382
+ body: ''
383
+ };
384
+ } catch (error: any) {
385
+ return {
386
+ statusCode: 500,
387
+ body: JSON.stringify({
388
+ error: 'Internal server error',
389
+ details: error.message
390
+ })
391
+ };
392
+ }
393
+ };
394
+
395
+ export { handler };
396
+ `;
397
+ var netlifyAuthCallbackTemplate = `import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
398
+ import axios from 'axios';
399
+
400
+ const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
401
+ try {
402
+ const code = event.queryStringParameters?.code;
403
+ const clientId = process.env.GITHUB_CLIENT_ID || process.env.VITE_GITHUB_CLIENT_ID;
404
+ const clientSecret = process.env.GITHUB_CLIENT_SECRET;
405
+
406
+ if (!code) {
407
+ return {
408
+ statusCode: 400,
409
+ body: JSON.stringify({ error: 'No code provided' })
410
+ };
411
+ }
412
+
413
+ if (!clientId || !clientSecret) {
414
+ return {
415
+ statusCode: 500,
416
+ body: JSON.stringify({ error: 'GitHub OAuth not configured properly' })
417
+ };
418
+ }
419
+
420
+ // Exchange code for access token
421
+ const tokenResponse = await axios.post(
422
+ 'https://github.com/login/oauth/access_token',
423
+ {
424
+ client_id: clientId,
425
+ client_secret: clientSecret,
426
+ code,
427
+ },
428
+ {
429
+ headers: {
430
+ Accept: 'application/json',
431
+ },
432
+ }
433
+ );
434
+
435
+ const accessToken = tokenResponse.data.access_token;
436
+
437
+ if (!accessToken) {
438
+ throw new Error('No access token received');
439
+ }
440
+
441
+ // Get user info
442
+ const userResponse = await axios.get('https://api.github.com/user', {
443
+ headers: {
444
+ Authorization: \`token \${accessToken}\`,
445
+ },
446
+ });
447
+
448
+ const user = userResponse.data;
449
+
450
+ // Redirect back to the app with token in URL fragment
451
+ const protocol = event.headers['x-forwarded-proto'] || 'https';
452
+ const host = event.headers.host;
453
+ const baseUrl = \`\${protocol}://\${host}\`;
454
+ const redirectUrl = \`\${baseUrl}/#/auth-callback?token=\${accessToken}&login=\${user.login}&avatar=\${encodeURIComponent(user.avatar_url)}\`;
455
+
456
+ return {
457
+ statusCode: 302,
458
+ headers: {
459
+ Location: redirectUrl
460
+ },
461
+ body: ''
462
+ };
463
+ } catch (error: any) {
464
+ return {
465
+ statusCode: 500,
466
+ body: JSON.stringify({
467
+ error: 'Failed to exchange code for token',
468
+ details: error.message,
469
+ })
470
+ };
471
+ }
472
+ };
473
+
474
+ export { handler };
475
+ `;
476
+
477
+ // src/generators/serverless.ts
478
+ async function generateServerless(platform, projectRoot) {
479
+ if (platform === "vercel") {
480
+ const apiDir = path2.join(projectRoot, "api");
481
+ await ensureDir(apiDir);
482
+ await writeFile(
483
+ path2.join(apiDir, "github-oauth-login.ts"),
484
+ vercelAuthLoginTemplate
485
+ );
486
+ await writeFile(
487
+ path2.join(apiDir, "github-oauth-callback.ts"),
488
+ vercelAuthCallbackTemplate
489
+ );
490
+ console.log(chalk4.dim(" \u2192 Created api/github-oauth-login.ts"));
491
+ console.log(chalk4.dim(" \u2192 Created api/github-oauth-callback.ts"));
492
+ } else if (platform === "netlify") {
493
+ const functionsDir = path2.join(projectRoot, "netlify", "functions");
494
+ await ensureDir(functionsDir);
495
+ await writeFile(
496
+ path2.join(functionsDir, "github-oauth-login.ts"),
497
+ netlifyAuthLoginTemplate
498
+ );
499
+ await writeFile(
500
+ path2.join(functionsDir, "github-oauth-callback.ts"),
501
+ netlifyAuthCallbackTemplate
502
+ );
503
+ console.log(chalk4.dim(" \u2192 Created netlify/functions/github-oauth-login.ts"));
504
+ console.log(chalk4.dim(" \u2192 Created netlify/functions/github-oauth-callback.ts"));
505
+ } else {
506
+ console.log(chalk4.yellow(" Manual platform selected. You must create serverless functions yourself."));
507
+ console.log(chalk4.dim(" See: https://github.com/apollo/commenting-system/blob/main/docs/manual-setup.md"));
508
+ }
509
+ }
510
+
511
+ // src/generators/env.ts
512
+ import path3 from "path";
513
+ import chalk5 from "chalk";
514
+ async function generateEnvFiles(githubConfig, platform, projectRoot) {
515
+ const envContent = `
516
+ # Apollo Commenting System Configuration
517
+ # Generated by apollo-comments CLI
518
+
519
+ # GitHub OAuth
520
+ GITHUB_CLIENT_ID=${githubConfig.clientId}
521
+ GITHUB_CLIENT_SECRET=${githubConfig.clientSecret}
522
+ GITHUB_OWNER=${githubConfig.owner}
523
+ GITHUB_REPO=${githubConfig.repo}
524
+
525
+ # Vite (if using Vite)
526
+ VITE_GITHUB_CLIENT_ID=${githubConfig.clientId}
527
+ VITE_GITHUB_OWNER=${githubConfig.owner}
528
+ VITE_GITHUB_REPO=${githubConfig.repo}
529
+ `;
530
+ const envLocalPath = path3.join(projectRoot, ".env.local");
531
+ const envPath = path3.join(projectRoot, ".env");
532
+ const envLocalExists = await fileExists(envLocalPath);
533
+ if (envLocalExists) {
534
+ const existingEnv = await readFile(envLocalPath);
535
+ if (existingEnv.includes("GITHUB_CLIENT_ID")) {
536
+ console.log(chalk5.yellow(" .env.local already contains GitHub config. Skipping..."));
537
+ } else {
538
+ await appendToFile(envLocalPath, envContent);
539
+ console.log(chalk5.dim(" \u2192 Updated .env.local"));
540
+ }
541
+ } else {
542
+ await writeFile(envLocalPath, envContent.trim());
543
+ console.log(chalk5.dim(" \u2192 Created .env.local"));
544
+ }
545
+ const envExampleContent = `# Apollo Commenting System Configuration
546
+
547
+ # GitHub OAuth (get from https://github.com/settings/developers)
548
+ GITHUB_CLIENT_ID=your_client_id_here
549
+ GITHUB_CLIENT_SECRET=your_client_secret_here
550
+ GITHUB_OWNER=your_github_org
551
+ GITHUB_REPO=your_repo_name
552
+
553
+ # Vite (if using Vite)
554
+ VITE_GITHUB_CLIENT_ID=your_client_id_here
555
+ VITE_GITHUB_OWNER=your_github_org
556
+ VITE_GITHUB_REPO=your_repo_name
557
+ `;
558
+ await writeFile(path3.join(projectRoot, ".env.example"), envExampleContent);
559
+ console.log(chalk5.dim(" \u2192 Created .env.example"));
560
+ const gitignorePath = path3.join(projectRoot, ".gitignore");
561
+ if (await fileExists(gitignorePath)) {
562
+ const gitignoreContent = await readFile(gitignorePath);
563
+ if (!gitignoreContent.includes(".env.local")) {
564
+ await appendToFile(gitignorePath, "\n# Apollo Commenting System\n.env.local\n");
565
+ console.log(chalk5.dim(" \u2192 Updated .gitignore"));
566
+ }
567
+ }
568
+ }
569
+
570
+ // src/generators/config.ts
571
+ import path4 from "path";
572
+ import chalk6 from "chalk";
573
+ async function generateConfig(githubConfig, platform, projectRoot) {
574
+ let redirectUri = "http://localhost:9000/api/github-oauth-callback";
575
+ if (platform === "netlify") {
576
+ redirectUri = "https://your-domain.com/.netlify/functions/github-oauth-callback";
577
+ } else if (platform === "vercel") {
578
+ redirectUri = "https://your-domain.com/api/github-oauth-callback";
579
+ }
580
+ const config = {
581
+ version: "1.0.0",
582
+ platform,
583
+ github: {
584
+ owner: githubConfig.owner,
585
+ repo: githubConfig.repo,
586
+ clientId: githubConfig.clientId
587
+ },
588
+ redirectUri,
589
+ features: {
590
+ aiSummarization: true,
591
+ versionTracking: true,
592
+ gitlabIntegration: false
593
+ },
594
+ labels: [
595
+ { name: "comment", color: "0075ca", description: "User feedback comment" },
596
+ { name: "prototype-feedback", color: "d93f0b", description: "Prototype feedback" },
597
+ { name: "needs-review", color: "fbca04", description: "Needs team review" }
598
+ ]
599
+ };
600
+ const configPath = path4.join(projectRoot, "apollo-comments.config.json");
601
+ await writeFile(configPath, JSON.stringify(config, null, 2));
602
+ console.log(chalk6.dim(" \u2192 Created apollo-comments.config.json"));
603
+ }
604
+
605
+ // src/validators/github.ts
606
+ import axios from "axios";
607
+ async function validateGitHubConnection(config) {
608
+ try {
609
+ const response = await axios.get("https://api.github.com/rate_limit", {
610
+ headers: {
611
+ "Accept": "application/vnd.github.v3+json"
612
+ },
613
+ timeout: 5e3
614
+ });
615
+ return response.status === 200;
616
+ } catch (error) {
617
+ console.error("GitHub connection error:", error);
618
+ return false;
619
+ }
620
+ }
621
+
622
+ // src/validators/repo.ts
623
+ import axios2 from "axios";
624
+ import chalk7 from "chalk";
625
+ async function validateRepoAccess(config) {
626
+ try {
627
+ const response = await axios2.get(
628
+ `https://api.github.com/repos/${config.owner}/${config.repo}`,
629
+ {
630
+ headers: {
631
+ "Accept": "application/vnd.github.v3+json"
632
+ },
633
+ timeout: 5e3
634
+ }
635
+ );
636
+ return response.status === 200;
637
+ } catch (error) {
638
+ if (error.response?.status === 404) {
639
+ console.log(chalk7.yellow(` Repository ${config.owner}/${config.repo} not found or is private`));
640
+ console.log(chalk7.yellow(" Note: Private repos require authentication at runtime"));
641
+ return true;
642
+ }
643
+ console.error(" Repo access error:", error.message);
644
+ return false;
645
+ }
646
+ }
647
+
648
+ // src/validators/labels.ts
649
+ import chalk8 from "chalk";
650
+ var REQUIRED_LABELS = [
651
+ { name: "comment", color: "0075ca", description: "User feedback comment" },
652
+ { name: "prototype-feedback", color: "d93f0b", description: "Prototype feedback" },
653
+ { name: "needs-review", color: "fbca04", description: "Needs team review" }
654
+ ];
655
+ async function ensureLabels(config) {
656
+ console.log(chalk8.dim("\n Required labels for GitHub issues:"));
657
+ REQUIRED_LABELS.forEach((label) => {
658
+ console.log(chalk8.dim(` - ${label.name} (#${label.color}): ${label.description}`));
659
+ });
660
+ console.log(chalk8.dim(" These will be created automatically at runtime with proper authentication.\n"));
661
+ }
662
+
663
+ // src/commands/init.ts
664
+ async function initCommand(options) {
665
+ printWelcome();
666
+ const spinner = ora("Detecting project...").start();
667
+ let project;
668
+ try {
669
+ project = await detectProject(process.cwd());
670
+ spinner.succeed(`Detected: ${project.framework} with ${project.buildTool}`);
671
+ } catch (error) {
672
+ spinner.fail(error.message);
673
+ process.exit(1);
674
+ }
675
+ if (!project.isReact) {
676
+ spinner.fail("This package requires a React project");
677
+ console.log(chalk9.red("\n\u2717 Apollo Commenting System requires React.\n"));
678
+ process.exit(1);
679
+ }
680
+ if (!project.hasPatternFly) {
681
+ printWarning("PatternFly not detected. This package is optimized for PatternFly projects.");
682
+ if (!options.yes) {
683
+ const { continueAnyway } = await inquirer3.prompt([
684
+ {
685
+ type: "confirm",
686
+ name: "continueAnyway",
687
+ message: "Continue anyway?",
688
+ default: false
689
+ }
690
+ ]);
691
+ if (!continueAnyway) {
692
+ console.log(chalk9.dim("\nSetup cancelled.\n"));
693
+ process.exit(0);
694
+ }
695
+ }
696
+ }
697
+ const platform = options.platform || await promptPlatform(project.root);
698
+ const githubConfig = await promptGitHubConfig(options.yes || false);
699
+ spinner.start("Validating GitHub connection...");
700
+ const isValid = await validateGitHubConnection(githubConfig);
701
+ if (!isValid) {
702
+ spinner.fail("GitHub connection failed. Please check your internet connection.");
703
+ process.exit(1);
704
+ }
705
+ spinner.succeed("GitHub connection validated");
706
+ spinner.start("Checking repository access...");
707
+ const hasAccess = await validateRepoAccess(githubConfig);
708
+ if (!hasAccess) {
709
+ spinner.fail("Cannot access repository. Check the owner/repo name.");
710
+ process.exit(1);
711
+ }
712
+ spinner.succeed("Repository access confirmed");
713
+ spinner.start("Checking issue labels...");
714
+ await ensureLabels(githubConfig);
715
+ spinner.succeed("Issue labels documented");
716
+ spinner.start("Generating serverless functions...");
717
+ try {
718
+ await generateServerless(platform, project.root);
719
+ spinner.succeed("Serverless functions created");
720
+ } catch (error) {
721
+ spinner.fail(`Failed to generate serverless functions: ${error.message}`);
722
+ process.exit(1);
723
+ }
724
+ spinner.start("Creating environment files...");
725
+ try {
726
+ await generateEnvFiles(githubConfig, platform, project.root);
727
+ spinner.succeed("Environment files created");
728
+ } catch (error) {
729
+ spinner.fail(`Failed to create environment files: ${error.message}`);
730
+ process.exit(1);
731
+ }
732
+ spinner.start("Creating apollo-comments.config.json...");
733
+ try {
734
+ await generateConfig(githubConfig, platform, project.root);
735
+ spinner.succeed("Configuration file created");
736
+ } catch (error) {
737
+ spinner.fail(`Failed to create config file: ${error.message}`);
738
+ process.exit(1);
739
+ }
740
+ printSuccess();
741
+ printSetupInstructions(platform, project);
742
+ printNextSteps();
743
+ }
744
+
745
+ // src/commands/validate.ts
746
+ import chalk10 from "chalk";
747
+ import ora2 from "ora";
748
+ import path5 from "path";
749
+ async function validateCommand() {
750
+ console.log(chalk10.bold.cyan("\n\u{1F50D} Validating Apollo Commenting System Setup\n"));
751
+ const spinner = ora2("Checking configuration files...").start();
752
+ const cwd = process.cwd();
753
+ const configPath = path5.join(cwd, "apollo-comments.config.json");
754
+ const hasConfig = await fileExists(configPath);
755
+ if (!hasConfig) {
756
+ spinner.fail("Configuration file not found");
757
+ console.log(chalk10.red("\n\u2717 apollo-comments.config.json not found"));
758
+ console.log(chalk10.dim(" Run: apollo-comments init\n"));
759
+ process.exit(1);
760
+ }
761
+ let config;
762
+ try {
763
+ const configContent = await readFile(configPath);
764
+ config = JSON.parse(configContent);
765
+ spinner.succeed("Configuration file found");
766
+ } catch (error) {
767
+ spinner.fail("Invalid configuration file");
768
+ console.log(chalk10.red("\n\u2717 Could not parse apollo-comments.config.json\n"));
769
+ process.exit(1);
770
+ }
771
+ spinner.start("Checking environment files...");
772
+ const envPath = path5.join(cwd, ".env.local");
773
+ const hasEnv = await fileExists(envPath);
774
+ if (!hasEnv) {
775
+ spinner.warn("Environment file not found (.env.local)");
776
+ } else {
777
+ spinner.succeed("Environment file found");
778
+ }
779
+ spinner.start("Checking serverless functions...");
780
+ let hasServerless = false;
781
+ if (config.platform === "vercel") {
782
+ const apiDir = path5.join(cwd, "api");
783
+ const loginPath = path5.join(apiDir, "github-oauth-login.ts");
784
+ const callbackPath = path5.join(apiDir, "github-oauth-callback.ts");
785
+ hasServerless = await fileExists(loginPath) && await fileExists(callbackPath);
786
+ } else if (config.platform === "netlify") {
787
+ const functionsDir = path5.join(cwd, "netlify", "functions");
788
+ const loginPath = path5.join(functionsDir, "github-oauth-login.ts");
789
+ const callbackPath = path5.join(functionsDir, "github-oauth-callback.ts");
790
+ hasServerless = await fileExists(loginPath) && await fileExists(callbackPath);
791
+ }
792
+ if (!hasServerless && config.platform !== "manual") {
793
+ spinner.warn("Serverless functions not found");
794
+ } else {
795
+ spinner.succeed("Serverless functions found");
796
+ }
797
+ spinner.start("Validating GitHub connection...");
798
+ const isValid = await validateGitHubConnection(config.github);
799
+ if (!isValid) {
800
+ spinner.fail("GitHub connection failed");
801
+ console.log(chalk10.red("\n\u2717 Could not connect to GitHub API\n"));
802
+ process.exit(1);
803
+ }
804
+ spinner.succeed("GitHub connection validated");
805
+ spinner.start("Validating repository access...");
806
+ const hasAccess = await validateRepoAccess(config.github);
807
+ if (!hasAccess) {
808
+ spinner.warn("Repository access could not be confirmed");
809
+ } else {
810
+ spinner.succeed("Repository access confirmed");
811
+ }
812
+ console.log(chalk10.green.bold("\n\u2713 Validation complete!\n"));
813
+ console.log(chalk10.white("Your Apollo Commenting System setup looks good.\n"));
814
+ }
815
+
816
+ // src/index.ts
817
+ var program = new Command();
818
+ program.name("apollo-comments").description("Apollo Commenting System CLI - Setup wizard and tooling").version("1.0.0");
819
+ program.command("init").description("Initialize Apollo Commenting System in your project").option("-y, --yes", "Skip prompts and use defaults").option("--platform <platform>", "Target platform (vercel, netlify, manual)").action(async (options) => {
820
+ try {
821
+ await initCommand(options);
822
+ } catch (error) {
823
+ console.error(chalk11.red("\n\u2717 Error:"), error.message);
824
+ process.exit(1);
825
+ }
826
+ });
827
+ program.command("validate").description("Validate your commenting system setup").action(async () => {
828
+ try {
829
+ await validateCommand();
830
+ } catch (error) {
831
+ console.error(chalk11.red("\n\u2717 Error:"), error.message);
832
+ process.exit(1);
833
+ }
834
+ });
835
+ program.command("update").description("Update existing configuration").action(() => {
836
+ console.log(chalk11.yellow("\n\u26A0\uFE0F Coming soon: Update configuration\n"));
837
+ console.log(chalk11.dim("For now, you can manually edit apollo-comments.config.json\n"));
838
+ });
839
+ if (!process.argv.slice(2).length) {
840
+ program.help();
841
+ }
842
+ program.parse();
843
+ //# sourceMappingURL=index.js.map