@redpanda-data/docs-extensions-and-macros 4.13.0 → 4.13.2

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.
Files changed (49) hide show
  1. package/bin/doc-tools-mcp.js +15 -3
  2. package/bin/doc-tools.js +767 -2088
  3. package/bin/mcp-tools/property-docs.js +18 -0
  4. package/bin/mcp-tools/rpcn-docs.js +28 -3
  5. package/cli-utils/antora-utils.js +53 -2
  6. package/cli-utils/dependencies.js +313 -0
  7. package/cli-utils/diff-utils.js +273 -0
  8. package/cli-utils/doc-tools-utils.js +54 -0
  9. package/extensions/algolia-indexer/generate-index.js +134 -102
  10. package/extensions/algolia-indexer/index.js +70 -38
  11. package/extensions/collect-bloblang-samples.js +2 -1
  12. package/extensions/generate-rp-connect-categories.js +126 -67
  13. package/extensions/generate-rp-connect-info.js +291 -137
  14. package/macros/rp-connect-components.js +34 -5
  15. package/mcp/CLI_INTERFACE.adoc +384 -0
  16. package/mcp/COSTS.adoc +167 -0
  17. package/mcp/DEVELOPMENT.adoc +726 -0
  18. package/mcp/README.adoc +172 -0
  19. package/mcp/USER_GUIDE.adoc +1392 -0
  20. package/mcp/WRITER_EXTENSION_GUIDE.adoc +814 -0
  21. package/mcp/prompts/README.adoc +183 -0
  22. package/mcp/prompts/property-docs-guide.md +283 -0
  23. package/mcp/prompts/review-for-style.md +128 -0
  24. package/mcp/prompts/rpcn-connector-docs-guide.md +126 -0
  25. package/mcp/prompts/write-new-guide.md +222 -0
  26. package/mcp/team-standards/style-guide.md +321 -0
  27. package/mcp/templates/README.adoc +212 -0
  28. package/mcp/templates/prompt-review-template.md +80 -0
  29. package/mcp/templates/prompt-write-template.md +110 -0
  30. package/mcp/templates/resource-template.md +76 -0
  31. package/package.json +8 -5
  32. package/tools/add-commercial-names.js +207 -0
  33. package/tools/generate-cli-docs.js +6 -2
  34. package/tools/get-console-version.js +5 -0
  35. package/tools/get-redpanda-version.js +5 -0
  36. package/tools/property-extractor/compare-properties.js +3 -3
  37. package/tools/property-extractor/generate-handlebars-docs.js +14 -14
  38. package/tools/property-extractor/generate-pr-summary.js +46 -0
  39. package/tools/property-extractor/pr-summary-formatter.js +375 -0
  40. package/tools/redpanda-connect/README.adoc +403 -38
  41. package/tools/redpanda-connect/connector-binary-analyzer.js +588 -0
  42. package/tools/redpanda-connect/generate-rpcn-connector-docs.js +97 -34
  43. package/tools/redpanda-connect/parse-csv-connectors.js +1 -1
  44. package/tools/redpanda-connect/pr-summary-formatter.js +601 -0
  45. package/tools/redpanda-connect/report-delta.js +69 -2
  46. package/tools/redpanda-connect/rpcn-connector-docs-handler.js +1180 -0
  47. package/tools/redpanda-connect/templates/connector.hbs +38 -0
  48. package/tools/redpanda-connect/templates/intro.hbs +0 -20
  49. package/tools/redpanda-connect/update-nav.js +205 -0
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Format diff and cloud support data into a PR-friendly summary
3
+ * Outputs a console-parseable format for GitHub Actions
4
+ */
5
+
6
+ /**
7
+ * Generate a PR-friendly summary for connector changes
8
+ * @param {object} diffData - Diff data from generateConnectorDiffJson
9
+ * @param {object} binaryAnalysis - Cloud support data from getCloudSupport
10
+ * @param {array} draftedConnectors - Array of newly drafted connectors
11
+ * @returns {string} Formatted summary
12
+ */
13
+ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors = null) {
14
+ const lines = [];
15
+
16
+ // Header with delimiters for GitHub Action parsing
17
+ lines.push('<!-- PR_SUMMARY_START -->');
18
+ lines.push('');
19
+
20
+ // Quick Summary Section
21
+ lines.push('## 📊 Redpanda Connect Documentation Update');
22
+ lines.push('');
23
+ lines.push(`**OSS Version:** ${diffData.comparison.oldVersion} → ${diffData.comparison.newVersion}`);
24
+
25
+ if (binaryAnalysis) {
26
+ lines.push(`**Cloud Version:** ${binaryAnalysis.cloudVersion}`);
27
+ }
28
+
29
+ lines.push('');
30
+
31
+ // High-level stats
32
+ const stats = diffData.summary;
33
+ const hasChanges = Object.values(stats).some(v => v > 0);
34
+
35
+ if (!hasChanges) {
36
+ lines.push('✅ **No changes detected** - Documentation is up to date');
37
+ lines.push('');
38
+ lines.push('<!-- PR_SUMMARY_END -->');
39
+ return lines.join('\n');
40
+ }
41
+
42
+ lines.push('### Summary');
43
+ lines.push('');
44
+
45
+ if (stats.newComponents > 0) {
46
+ lines.push(`- **${stats.newComponents}** new connector${stats.newComponents !== 1 ? 's' : ''}`);
47
+
48
+ if (binaryAnalysis) {
49
+ const newConnectorKeys = diffData.details.newComponents.map(c => `${c.type}:${c.name}`);
50
+ const cloudSupported = newConnectorKeys.filter(key => {
51
+ const inCloud = binaryAnalysis.comparison.inCloud.some(c => `${c.type}:${c.name}` === key);
52
+ return inCloud;
53
+ }).length;
54
+
55
+ const needsCloudDocs = cloudSupported;
56
+
57
+ if (needsCloudDocs > 0) {
58
+ lines.push(` - ${needsCloudDocs} need${needsCloudDocs !== 1 ? '' : 's'} cloud docs ☁️`);
59
+ }
60
+ }
61
+ }
62
+
63
+ if (stats.newFields > 0) {
64
+ const affectedComponents = new Set(diffData.details.newFields.map(f => f.component)).size;
65
+ lines.push(`- **${stats.newFields}** new field${stats.newFields !== 1 ? 's' : ''} across ${affectedComponents} connector${affectedComponents !== 1 ? 's' : ''}`);
66
+ }
67
+
68
+ if (stats.removedComponents > 0) {
69
+ lines.push(`- **${stats.removedComponents}** removed connector${stats.removedComponents !== 1 ? 's' : ''} ⚠️`);
70
+ }
71
+
72
+ if (stats.removedFields > 0) {
73
+ lines.push(`- **${stats.removedFields}** removed field${stats.removedFields !== 1 ? 's' : ''} ⚠️`);
74
+ }
75
+
76
+ if (stats.deprecatedComponents > 0) {
77
+ lines.push(`- **${stats.deprecatedComponents}** deprecated connector${stats.deprecatedComponents !== 1 ? 's' : ''}`);
78
+ }
79
+
80
+ if (stats.deprecatedFields > 0) {
81
+ lines.push(`- **${stats.deprecatedFields}** deprecated field${stats.deprecatedFields !== 1 ? 's' : ''}`);
82
+ }
83
+
84
+ if (stats.changedDefaults > 0) {
85
+ lines.push(`- **${stats.changedDefaults}** default value change${stats.changedDefaults !== 1 ? 's' : ''} ⚠️`);
86
+ }
87
+
88
+ lines.push('');
89
+
90
+ // Writer Reminder for Commercial Names
91
+ if (stats.newComponents > 0) {
92
+ lines.push('### ✍️ Writer Action Required');
93
+ lines.push('');
94
+ lines.push('For each new connector, please add the `:commercial-names:` attribute to the frontmatter:');
95
+ lines.push('');
96
+ lines.push('```asciidoc');
97
+ lines.push('= Connector Name');
98
+ lines.push(':type: input');
99
+ lines.push(':commercial-names: Commercial Name, Alternative Name');
100
+ lines.push('```');
101
+ lines.push('');
102
+ lines.push('_This helps improve discoverability and ensures proper categorization._');
103
+ lines.push('');
104
+ }
105
+
106
+ // Breaking Changes Section
107
+ const breakingChanges = [];
108
+ if (stats.removedComponents > 0) breakingChanges.push('removed connectors');
109
+ if (stats.removedFields > 0) breakingChanges.push('removed fields');
110
+ if (stats.changedDefaults > 0) breakingChanges.push('changed defaults');
111
+
112
+ if (breakingChanges.length > 0) {
113
+ lines.push('### ⚠️ Breaking Changes Detected');
114
+ lines.push('');
115
+ lines.push(`This update includes **${breakingChanges.join(', ')}** that may affect existing configurations.`);
116
+ lines.push('');
117
+ }
118
+
119
+ // Newly Drafted Connectors Section
120
+ if (draftedConnectors && draftedConnectors.length > 0) {
121
+ lines.push('### 📝 Newly Drafted - Needs Review');
122
+ lines.push('');
123
+ lines.push(`**${draftedConnectors.length}** connector${draftedConnectors.length !== 1 ? 's have' : ' has'} been auto-generated and placed in the proper location. These drafts need writer review:`);
124
+ lines.push('');
125
+
126
+ // Group by type
127
+ const draftsByType = {};
128
+ draftedConnectors.forEach(draft => {
129
+ const type = draft.type || 'unknown';
130
+ if (!draftsByType[type]) {
131
+ draftsByType[type] = [];
132
+ }
133
+ draftsByType[type].push(draft);
134
+ });
135
+
136
+ // List drafts by type
137
+ Object.entries(draftsByType).forEach(([type, drafts]) => {
138
+ lines.push(`**${type}:**`);
139
+ drafts.forEach(draft => {
140
+ const cloudIndicator = binaryAnalysis?.comparison.inCloud.some(c =>
141
+ c.type === type && c.name === draft.name
142
+ ) ? ' ☁️' : '';
143
+ const cgoIndicator = draft.requiresCgo ? ' 🔧' : '';
144
+ const statusBadge = draft.status && draft.status !== 'stable' ? ` (${draft.status})` : '';
145
+ lines.push(`- \`${draft.name}\`${statusBadge}${cloudIndicator}${cgoIndicator} → \`${draft.path}\``);
146
+ });
147
+ lines.push('');
148
+ });
149
+ }
150
+
151
+ // Missing Descriptions Warning
152
+ const missingDescriptions = [];
153
+
154
+ // Check for new components with missing descriptions
155
+ if (stats.newComponents > 0) {
156
+ diffData.details.newComponents.forEach(connector => {
157
+ if (!connector.description || connector.description.trim() === '') {
158
+ missingDescriptions.push({
159
+ type: 'component',
160
+ name: connector.name,
161
+ componentType: connector.type
162
+ });
163
+ }
164
+ });
165
+ }
166
+
167
+ // Check for new fields with missing descriptions
168
+ if (stats.newFields > 0) {
169
+ diffData.details.newFields.forEach(field => {
170
+ if (!field.description || field.description.trim() === '') {
171
+ missingDescriptions.push({
172
+ type: 'field',
173
+ name: field.field,
174
+ component: field.component
175
+ });
176
+ }
177
+ });
178
+ }
179
+
180
+ if (missingDescriptions.length > 0) {
181
+ lines.push('### ⚠️ Missing Descriptions');
182
+ lines.push('');
183
+ lines.push(`**${missingDescriptions.length}** item${missingDescriptions.length !== 1 ? 's' : ''} missing descriptions - these need writer attention:`);
184
+ lines.push('');
185
+
186
+ const componentsMissing = missingDescriptions.filter(m => m.type === 'component');
187
+ const fieldsMissing = missingDescriptions.filter(m => m.type === 'field');
188
+
189
+ if (componentsMissing.length > 0) {
190
+ lines.push('**Components:**');
191
+ componentsMissing.forEach(m => {
192
+ lines.push(`- \`${m.name}\` (${m.componentType})`);
193
+ });
194
+ lines.push('');
195
+ }
196
+
197
+ if (fieldsMissing.length > 0) {
198
+ lines.push('**Fields:**');
199
+ // Group by component
200
+ const fieldsByComponent = {};
201
+ fieldsMissing.forEach(m => {
202
+ if (!fieldsByComponent[m.component]) {
203
+ fieldsByComponent[m.component] = [];
204
+ }
205
+ fieldsByComponent[m.component].push(m.name);
206
+ });
207
+
208
+ Object.entries(fieldsByComponent).forEach(([component, fields]) => {
209
+ const [type, name] = component.split(':');
210
+ lines.push(`- **${type}/${name}:** ${fields.map(f => `\`${f}\``).join(', ')}`);
211
+ });
212
+ lines.push('');
213
+ }
214
+ }
215
+
216
+ // Action Items
217
+ lines.push('### 📝 Action Items for Writers');
218
+ lines.push('');
219
+
220
+ const actionItems = [];
221
+
222
+ // Add action items for missing descriptions
223
+ if (missingDescriptions.length > 0) {
224
+ actionItems.push({
225
+ priority: 0,
226
+ text: `⚠️ Add descriptions for ${missingDescriptions.length} component${missingDescriptions.length !== 1 ? 's' : ''}/field${missingDescriptions.length !== 1 ? 's' : ''} (see Missing Descriptions section)`
227
+ });
228
+ }
229
+
230
+ // New connectors that need cloud docs
231
+ if (binaryAnalysis && stats.newComponents > 0) {
232
+ diffData.details.newComponents.forEach(connector => {
233
+ const key = `${connector.type}:${connector.name}`;
234
+ const inCloud = binaryAnalysis.comparison.inCloud.some(c => `${c.type}:${c.name}` === key);
235
+
236
+ if (inCloud) {
237
+ actionItems.push({
238
+ priority: 1,
239
+ text: `Document new \`${connector.name}\` ${connector.type} (☁️ **CLOUD SUPPORTED**)`
240
+ });
241
+ }
242
+ });
243
+ }
244
+
245
+ // New connectors without cloud support
246
+ if (stats.newComponents > 0) {
247
+ diffData.details.newComponents.forEach(connector => {
248
+ const key = `${connector.type}:${connector.name}`;
249
+ const inCloud = binaryAnalysis?.comparison.inCloud.some(c => `${c.type}:${c.name}` === key);
250
+
251
+ if (!inCloud) {
252
+ actionItems.push({
253
+ priority: 2,
254
+ text: `Document new \`${connector.name}\` ${connector.type} (self-hosted only)`
255
+ });
256
+ }
257
+ });
258
+ }
259
+
260
+ // Deprecated connectors
261
+ if (stats.deprecatedComponents > 0) {
262
+ diffData.details.deprecatedComponents.forEach(connector => {
263
+ actionItems.push({
264
+ priority: 3,
265
+ text: `Update docs for deprecated \`${connector.name}\` ${connector.type}`
266
+ });
267
+ });
268
+ }
269
+
270
+ // Removed connectors
271
+ if (stats.removedComponents > 0) {
272
+ diffData.details.removedComponents.forEach(connector => {
273
+ actionItems.push({
274
+ priority: 3,
275
+ text: `Update migration guide for removed \`${connector.name}\` ${connector.type}`
276
+ });
277
+ });
278
+ }
279
+
280
+ // Changed defaults that may break configs
281
+ if (stats.changedDefaults > 0) {
282
+ actionItems.push({
283
+ priority: 4,
284
+ text: `Review ${stats.changedDefaults} default value change${stats.changedDefaults !== 1 ? 's' : ''} for breaking changes`
285
+ });
286
+ }
287
+
288
+ // Sort by priority and output
289
+ actionItems.sort((a, b) => a.priority - b.priority);
290
+
291
+ if (actionItems.length > 0) {
292
+ actionItems.forEach(item => {
293
+ lines.push(`- [ ] ${item.text}`);
294
+ });
295
+ } else {
296
+ lines.push('- [ ] Review generated documentation');
297
+ }
298
+
299
+ lines.push('');
300
+
301
+ // Detailed breakdown (expandable)
302
+ lines.push('<details>');
303
+ lines.push('<summary><strong>📋 Detailed Changes</strong> (click to expand)</summary>');
304
+ lines.push('');
305
+
306
+ // New Connectors
307
+ if (stats.newComponents > 0) {
308
+ lines.push('#### New Connectors');
309
+ lines.push('');
310
+
311
+ if (binaryAnalysis) {
312
+ const cloudSupportedNew = [];
313
+ const selfHostedOnlyNew = [];
314
+
315
+ diffData.details.newComponents.forEach(connector => {
316
+ const key = `${connector.type}:${connector.name}`;
317
+ const inCloud = binaryAnalysis.comparison.inCloud.some(c => `${c.type}:${c.name}` === key);
318
+
319
+ const entry = {
320
+ name: connector.name,
321
+ type: connector.type,
322
+ status: connector.status,
323
+ description: connector.description
324
+ };
325
+
326
+ if (inCloud) {
327
+ cloudSupportedNew.push(entry);
328
+ } else {
329
+ selfHostedOnlyNew.push(entry);
330
+ }
331
+ });
332
+
333
+ if (cloudSupportedNew.length > 0) {
334
+ lines.push('**☁️ Cloud Supported:**');
335
+ lines.push('');
336
+ cloudSupportedNew.forEach(c => {
337
+ lines.push(`- **${c.name}** (${c.type}, ${c.status})`);
338
+ if (c.description) {
339
+ const shortDesc = truncateToSentence(c.description, 2);
340
+ lines.push(` - ${shortDesc}`);
341
+ }
342
+ });
343
+ lines.push('');
344
+ }
345
+
346
+ if (selfHostedOnlyNew.length > 0) {
347
+ lines.push('**Self-Hosted Only:**');
348
+ lines.push('');
349
+ selfHostedOnlyNew.forEach(c => {
350
+ lines.push(`- **${c.name}** (${c.type}, ${c.status})`);
351
+ if (c.description) {
352
+ const shortDesc = truncateToSentence(c.description, 2);
353
+ lines.push(` - ${shortDesc}`);
354
+ }
355
+ });
356
+ lines.push('');
357
+ }
358
+ } else {
359
+ // No cloud support info, just list all
360
+ diffData.details.newComponents.forEach(c => {
361
+ lines.push(`- **${c.name}** (${c.type}, ${c.status})`);
362
+ if (c.description) {
363
+ const shortDesc = truncateToSentence(c.description, 2);
364
+ lines.push(` - ${shortDesc}`);
365
+ }
366
+ });
367
+ lines.push('');
368
+ }
369
+ }
370
+
371
+ // cgo-only connectors (if any new connectors require cgo)
372
+ if (binaryAnalysis && binaryAnalysis.cgoOnly && binaryAnalysis.cgoOnly.length > 0 && stats.newComponents > 0) {
373
+ // Find new connectors that are cgo-only
374
+ const newCgoConnectors = diffData.details.newComponents.filter(connector => {
375
+ const key = `${connector.type}:${connector.name}`;
376
+ return binaryAnalysis.cgoOnly.some(cgo => `${cgo.type}:${cgo.name}` === key);
377
+ });
378
+
379
+ if (newCgoConnectors.length > 0) {
380
+ lines.push('#### 🔧 Cgo Requirements');
381
+ lines.push('');
382
+ lines.push('The following new connectors require cgo-enabled builds:');
383
+ lines.push('');
384
+
385
+ newCgoConnectors.forEach(connector => {
386
+ // Convert type to singular form for better grammar (e.g., "inputs" -> "input")
387
+ const typeSingular = connector.type.endsWith('s') ? connector.type.slice(0, -1) : connector.type;
388
+
389
+ lines.push(`**${connector.name}** (${connector.type}):`);
390
+ lines.push('');
391
+ lines.push('[NOTE]');
392
+ lines.push('====');
393
+ lines.push(`The \`${connector.name}\` ${typeSingular} requires a cgo-enabled build of Redpanda Connect.`);
394
+ lines.push('');
395
+ lines.push('For instructions, see:');
396
+ lines.push('');
397
+ lines.push('* xref:install:prebuilt-binary.adoc[Download a cgo-enabled binary]');
398
+ lines.push('* xref:install:build-from-source.adoc[Build Redpanda Connect from source]');
399
+ lines.push('====');
400
+ lines.push('');
401
+ });
402
+ }
403
+ }
404
+
405
+ // New Fields
406
+ if (stats.newFields > 0) {
407
+ lines.push('#### New Fields');
408
+ lines.push('');
409
+
410
+ // Group by component
411
+ const fieldsByComponent = {};
412
+ diffData.details.newFields.forEach(field => {
413
+ if (!fieldsByComponent[field.component]) {
414
+ fieldsByComponent[field.component] = [];
415
+ }
416
+ fieldsByComponent[field.component].push(field);
417
+ });
418
+
419
+ Object.entries(fieldsByComponent).forEach(([component, fields]) => {
420
+ const [type, name] = component.split(':');
421
+ lines.push(`**${type}/${name}:**`);
422
+ fields.forEach(f => {
423
+ lines.push(`- \`${f.field}\`${f.introducedIn ? ` (since ${f.introducedIn})` : ''}`);
424
+ });
425
+ lines.push('');
426
+ });
427
+ }
428
+
429
+ // Removed Connectors
430
+ if (stats.removedComponents > 0) {
431
+ lines.push('#### ⚠️ Removed Connectors');
432
+ lines.push('');
433
+ diffData.details.removedComponents.forEach(c => {
434
+ lines.push(`- **${c.name}** (${c.type})`);
435
+ });
436
+ lines.push('');
437
+ }
438
+
439
+ // Removed Fields
440
+ if (stats.removedFields > 0) {
441
+ lines.push('#### ⚠️ Removed Fields');
442
+ lines.push('');
443
+
444
+ const fieldsByComponent = {};
445
+ diffData.details.removedFields.forEach(field => {
446
+ if (!fieldsByComponent[field.component]) {
447
+ fieldsByComponent[field.component] = [];
448
+ }
449
+ fieldsByComponent[field.component].push(field);
450
+ });
451
+
452
+ Object.entries(fieldsByComponent).forEach(([component, fields]) => {
453
+ const [type, name] = component.split(':');
454
+ lines.push(`**${type}/${name}:**`);
455
+ fields.forEach(f => {
456
+ lines.push(`- \`${f.field}\``);
457
+ });
458
+ lines.push('');
459
+ });
460
+ }
461
+
462
+ // Deprecated Connectors
463
+ if (stats.deprecatedComponents > 0) {
464
+ lines.push('#### Deprecated Connectors');
465
+ lines.push('');
466
+ diffData.details.deprecatedComponents.forEach(c => {
467
+ lines.push(`- **${c.name}** (${c.type})`);
468
+ });
469
+ lines.push('');
470
+ }
471
+
472
+ // Deprecated Fields
473
+ if (stats.deprecatedFields > 0) {
474
+ lines.push('#### Deprecated Fields');
475
+ lines.push('');
476
+
477
+ const fieldsByComponent = {};
478
+ diffData.details.deprecatedFields.forEach(field => {
479
+ if (!fieldsByComponent[field.component]) {
480
+ fieldsByComponent[field.component] = [];
481
+ }
482
+ fieldsByComponent[field.component].push(field);
483
+ });
484
+
485
+ Object.entries(fieldsByComponent).forEach(([component, fields]) => {
486
+ const [type, name] = component.split(':');
487
+ lines.push(`**${type}/${name}:**`);
488
+ fields.forEach(f => {
489
+ lines.push(`- \`${f.field}\``);
490
+ });
491
+ lines.push('');
492
+ });
493
+ }
494
+
495
+ // Changed Defaults
496
+ if (stats.changedDefaults > 0) {
497
+ lines.push('#### ⚠️ Changed Default Values');
498
+ lines.push('');
499
+
500
+ const changesByComponent = {};
501
+ diffData.details.changedDefaults.forEach(change => {
502
+ if (!changesByComponent[change.component]) {
503
+ changesByComponent[change.component] = [];
504
+ }
505
+ changesByComponent[change.component].push(change);
506
+ });
507
+
508
+ Object.entries(changesByComponent).forEach(([component, changes]) => {
509
+ const [type, name] = component.split(':');
510
+ lines.push(`**${type}/${name}:**`);
511
+ changes.forEach(c => {
512
+ const oldStr = JSON.stringify(c.oldDefault);
513
+ const newStr = JSON.stringify(c.newDefault);
514
+ lines.push(`- \`${c.field}\`: ${oldStr} → ${newStr}`);
515
+ });
516
+ lines.push('');
517
+ });
518
+ }
519
+
520
+ // Cloud Support Gap Analysis
521
+ if (binaryAnalysis && binaryAnalysis.comparison.notInCloud.length > 0) {
522
+ lines.push('#### 🔍 Cloud Support Gap Analysis');
523
+ lines.push('');
524
+ lines.push(`**${binaryAnalysis.comparison.notInCloud.length} connector${binaryAnalysis.comparison.notInCloud.length !== 1 ? 's' : ''} available in OSS but not in cloud:**`);
525
+ lines.push('');
526
+
527
+ // Group by type
528
+ const gapsByType = {};
529
+ binaryAnalysis.comparison.notInCloud.forEach(connector => {
530
+ if (!gapsByType[connector.type]) {
531
+ gapsByType[connector.type] = [];
532
+ }
533
+ gapsByType[connector.type].push(connector);
534
+ });
535
+
536
+ Object.entries(gapsByType).forEach(([type, connectors]) => {
537
+ lines.push(`**${type}:**`);
538
+ connectors.forEach(c => {
539
+ lines.push(`- ${c.name} (${c.status})`);
540
+ });
541
+ lines.push('');
542
+ });
543
+ }
544
+
545
+ lines.push('</details>');
546
+ lines.push('');
547
+
548
+ // Footer
549
+ lines.push('---');
550
+ lines.push('');
551
+ lines.push(`*Generated: ${diffData.comparison.timestamp}*`);
552
+ lines.push('');
553
+ lines.push('<!-- PR_SUMMARY_END -->');
554
+
555
+ return lines.join('\n');
556
+ }
557
+
558
+ /**
559
+ * Truncate description to specified number of sentences
560
+ * @param {string} text - Text to truncate
561
+ * @param {number} sentences - Number of sentences to keep
562
+ * @returns {string} Truncated text
563
+ */
564
+ function truncateToSentence(text, sentences = 2) {
565
+ if (!text) return '';
566
+
567
+ // Remove markdown formatting
568
+ let clean = text
569
+ .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Remove links
570
+ .replace(/[*_`]/g, '') // Remove emphasis
571
+ .replace(/\n/g, ' '); // Replace newlines with spaces
572
+
573
+ // Split by sentence boundaries
574
+ const sentenceRegex = /[^.!?]+[.!?]+/g;
575
+ const matches = clean.match(sentenceRegex);
576
+
577
+ if (!matches || matches.length === 0) {
578
+ return clean.substring(0, 150);
579
+ }
580
+
581
+ const truncated = matches.slice(0, sentences).join(' ').trim();
582
+
583
+ return truncated.length > 200 ? truncated.substring(0, 200) + '...' : truncated;
584
+ }
585
+
586
+ /**
587
+ * Print the PR summary to console
588
+ * @param {object} diffData - Diff data
589
+ * @param {object} binaryAnalysis - Cloud support data
590
+ * @param {array} draftedConnectors - Array of newly drafted connectors
591
+ */
592
+ function printPRSummary(diffData, binaryAnalysis = null, draftedConnectors = null) {
593
+ const summary = generatePRSummary(diffData, binaryAnalysis, draftedConnectors);
594
+ console.log('\n' + summary + '\n');
595
+ }
596
+
597
+ module.exports = {
598
+ generatePRSummary,
599
+ printPRSummary,
600
+ truncateToSentence
601
+ };
@@ -4,7 +4,7 @@ const { execSync } = require('child_process');
4
4
  * Generate a JSON diff report between two connector index objects.
5
5
  * @param {object} oldIndex - Previous version connector index
6
6
  * @param {object} newIndex - Current version connector index
7
- * @param {object} opts - { oldVersion, newVersion, timestamp }
7
+ * @param {object} opts - { oldVersion, newVersion, timestamp, binaryAnalysis, oldBinaryAnalysis }
8
8
  * @returns {object} JSON diff report
9
9
  */
10
10
  function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
@@ -163,7 +163,7 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
163
163
  });
164
164
  });
165
165
 
166
- return {
166
+ const result = {
167
167
  comparison: {
168
168
  oldVersion: opts.oldVersion || '',
169
169
  newVersion: opts.newVersion || '',
@@ -188,6 +188,73 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
188
188
  changedDefaults
189
189
  }
190
190
  };
191
+
192
+ // Include binary analysis data if provided
193
+ if (opts.binaryAnalysis) {
194
+ const ba = opts.binaryAnalysis;
195
+ const oldBa = opts.oldBinaryAnalysis || {};
196
+
197
+ result.binaryAnalysis = {
198
+ versions: {
199
+ oss: ba.ossVersion,
200
+ cloud: ba.cloudVersion || null,
201
+ cgo: ba.cgoVersion || null
202
+ },
203
+ current: {
204
+ cloudSupported: ba.comparison?.inCloud?.length || 0,
205
+ selfHostedOnly: ba.comparison?.notInCloud?.length || 0,
206
+ cgoOnly: ba.cgoOnly?.length || 0
207
+ },
208
+ changes: {}
209
+ };
210
+
211
+ // Calculate cloud support changes
212
+ if (oldBa.comparison && ba.comparison) {
213
+ const oldCloudSet = new Set(oldBa.comparison.inCloud?.map(c => `${c.type}:${c.name}`) || []);
214
+ const newCloudSet = new Set(ba.comparison.inCloud?.map(c => `${c.type}:${c.name}`) || []);
215
+
216
+ const addedToCloud = ba.comparison.inCloud?.filter(c =>
217
+ !oldCloudSet.has(`${c.type}:${c.name}`)
218
+ ) || [];
219
+
220
+ const removedFromCloud = oldBa.comparison.inCloud?.filter(c =>
221
+ !newCloudSet.has(`${c.type}:${c.name}`)
222
+ ) || [];
223
+
224
+ result.binaryAnalysis.changes.cloud = {
225
+ added: addedToCloud.map(c => ({ type: c.type, name: c.name, status: c.status })),
226
+ removed: removedFromCloud.map(c => ({ type: c.type, name: c.name, status: c.status }))
227
+ };
228
+ }
229
+
230
+ // Calculate cgo-only changes
231
+ if (oldBa.cgoOnly && ba.cgoOnly) {
232
+ const oldCgoSet = new Set(oldBa.cgoOnly.map(c => `${c.type}:${c.name}`));
233
+ const newCgoSet = new Set(ba.cgoOnly.map(c => `${c.type}:${c.name}`));
234
+
235
+ const newCgoOnly = ba.cgoOnly.filter(c =>
236
+ !oldCgoSet.has(`${c.type}:${c.name}`)
237
+ );
238
+
239
+ const removedCgoOnly = oldBa.cgoOnly.filter(c =>
240
+ !newCgoSet.has(`${c.type}:${c.name}`)
241
+ );
242
+
243
+ result.binaryAnalysis.changes.cgo = {
244
+ newCgoOnly: newCgoOnly.map(c => ({ type: c.type, name: c.name, status: c.status })),
245
+ removedCgoOnly: removedCgoOnly.map(c => ({ type: c.type, name: c.name, status: c.status }))
246
+ };
247
+ }
248
+
249
+ // Include full lists for reference
250
+ result.binaryAnalysis.details = {
251
+ cloudSupported: ba.comparison?.inCloud?.map(c => ({ type: c.type, name: c.name, status: c.status })) || [],
252
+ selfHostedOnly: ba.comparison?.notInCloud?.map(c => ({ type: c.type, name: c.name, status: c.status })) || [],
253
+ cgoOnly: ba.cgoOnly?.map(c => ({ type: c.type, name: c.name, status: c.status })) || []
254
+ };
255
+ }
256
+
257
+ return result;
191
258
  }
192
259
 
193
260
  function discoverComponentKeys(obj) {