hale-commenting-system 2.2.92 → 2.2.95

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 (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/integrate.js +252 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hale-commenting-system",
3
- "version": "2.2.92",
3
+ "version": "2.2.95",
4
4
  "description": "A commenting system for PatternFly React applications that allows designers and developers to add comments directly on design pages, sync with GitHub Issues, and link Jira tickets.",
5
5
  "repository": "https://github.com/patternfly/patternfly-react-seed.git",
6
6
  "homepage": "https://www.npmjs.com/package/hale-commenting-system",
@@ -1170,115 +1170,282 @@ function modifyRoutesTsx(filePath) {
1170
1170
  }
1171
1171
 
1172
1172
  function modifyAppLayoutTsx(filePath) {
1173
- const content = fs.readFileSync(filePath, 'utf8');
1174
-
1175
- // Check if already integrated
1176
- if (content.includes('CommentPanel') && content.includes('CommentOverlay')) {
1177
- console.log(' ⚠️ Already integrated (CommentPanel/CommentOverlay found)');
1173
+ let content = fs.readFileSync(filePath, 'utf8');
1174
+
1175
+ // Check if already integrated - look for the comprehensive integration
1176
+ if (content.includes('useComments') && content.includes('useGitHubAuth') &&
1177
+ content.includes('setCommentsEnabled') && content.includes('setFloatingWidgetMode')) {
1178
+ console.log(' ⚠️ Already integrated (full commenting system controls found)');
1178
1179
  return false;
1179
1180
  }
1180
1181
 
1181
1182
  try {
1182
- const ast = parser(content, {
1183
- sourceType: 'module',
1184
- plugins: ['typescript', 'jsx', 'decorators-legacy', 'classProperties']
1185
- });
1186
-
1187
- let hasCommentImport = false;
1188
- let pageElement = null;
1189
-
1190
- // Check imports and find Page element
1191
- traverse(ast, {
1192
- ImportDeclaration(path) {
1193
- const source = path.node.source.value;
1194
- if (source.includes('commenting-system') || source.includes('@app/commenting-system') || source.includes('hale-commenting-system')) {
1195
- hasCommentImport = true;
1196
- // Check if components are imported
1197
- const specifiers = path.node.specifiers || [];
1198
- const hasCommentPanel = specifiers.some(s =>
1199
- s.imported && (s.imported.name === 'CommentPanel' || s.local.name === 'CommentPanel')
1200
- );
1201
- const hasCommentOverlay = specifiers.some(s =>
1202
- s.imported && (s.imported.name === 'CommentOverlay' || s.local.name === 'CommentOverlay')
1183
+ // Step 1: Add imports using string manipulation (more reliable for complex imports)
1184
+
1185
+ // Check and add PatternFly imports
1186
+ const patternflyImportRegex = /from\s+['"]@patternfly\/react-core['"]/;
1187
+ const patternflyMatch = content.match(patternflyImportRegex);
1188
+
1189
+ if (patternflyMatch) {
1190
+ // Find the import statement
1191
+ const importMatch = content.match(/import\s+\{([^}]+)\}\s+from\s+['"]@patternfly\/react-core['"]/);
1192
+ if (importMatch) {
1193
+ const imports = importMatch[1];
1194
+ // Add Switch if not present
1195
+ if (!imports.includes('Switch')) {
1196
+ const newImports = imports.trim() + ',\n Switch';
1197
+ content = content.replace(
1198
+ /import\s+\{([^}]+)\}\s+from\s+['"]@patternfly\/react-core['"]/,
1199
+ `import {${newImports}\n} from '@patternfly/react-core'`
1203
1200
  );
1201
+ }
1202
+ }
1203
+ }
1204
1204
 
1205
- if (!hasCommentPanel) {
1206
- specifiers.push(types.importSpecifier(types.identifier('CommentPanel'), types.identifier('CommentPanel')));
1207
- }
1208
- if (!hasCommentOverlay) {
1209
- specifiers.push(types.importSpecifier(types.identifier('CommentOverlay'), types.identifier('CommentOverlay')));
1210
- }
1211
- path.node.specifiers = specifiers;
1205
+ // Check and add PatternFly icons imports
1206
+ const iconsImportRegex = /from\s+['"]@patternfly\/react-icons['"]/;
1207
+ const iconsMatch = content.match(iconsImportRegex);
1208
+
1209
+ if (iconsMatch) {
1210
+ const importMatch = content.match(/import\s+\{([^}]+)\}\s+from\s+['"]@patternfly\/react-icons['"]/);
1211
+ if (importMatch) {
1212
+ const imports = importMatch[1];
1213
+ // Add icons if not present
1214
+ let newImports = imports.trim();
1215
+ if (!imports.includes('ExternalLinkAltIcon')) {
1216
+ newImports += ', ExternalLinkAltIcon';
1212
1217
  }
1213
- },
1214
- JSXElement(path) {
1215
- if (path.node.openingElement.name.name === 'Page') {
1216
- pageElement = path;
1218
+ if (!imports.includes('GithubIcon')) {
1219
+ newImports += ', GithubIcon';
1220
+ }
1221
+ if (newImports !== imports.trim()) {
1222
+ content = content.replace(
1223
+ /import\s+\{([^}]+)\}\s+from\s+['"]@patternfly\/react-icons['"]/,
1224
+ `import { ${newImports} } from '@patternfly/react-icons'`
1225
+ );
1217
1226
  }
1218
1227
  }
1219
- });
1228
+ }
1220
1229
 
1221
- // Add imports if missing
1222
- if (!hasCommentImport) {
1223
- // Find last import declaration
1224
- let lastImportIndex = -1;
1225
- for (let i = ast.program.body.length - 1; i >= 0; i--) {
1226
- if (ast.program.body[i].type === 'ImportDeclaration') {
1227
- lastImportIndex = i;
1228
- break;
1229
- }
1230
+ // Add commenting system imports
1231
+ if (!content.includes('hale-commenting-system')) {
1232
+ // Find where to insert (after other imports)
1233
+ const lastImportMatch = content.match(/import[^;]*;(?=\s*(?:interface|const|export|function))/g);
1234
+ if (lastImportMatch) {
1235
+ const lastImport = lastImportMatch[lastImportMatch.length - 1];
1236
+ const insertPos = content.indexOf(lastImport) + lastImport.length;
1237
+ const commentingImport = `\nimport { CommentOverlay, CommentPanel, useComments, useGitHubAuth } from "hale-commenting-system";`;
1238
+ content = content.slice(0, insertPos) + commentingImport + content.slice(insertPos);
1239
+ }
1240
+ } else {
1241
+ // Update existing import to include all needed items
1242
+ const commentingImportMatch = content.match(/import\s+\{([^}]+)\}\s+from\s+["']hale-commenting-system["']/);
1243
+ if (commentingImportMatch) {
1244
+ const imports = commentingImportMatch[1];
1245
+ let newImports = imports.split(',').map(i => i.trim());
1246
+
1247
+ const needed = ['CommentOverlay', 'CommentPanel', 'useComments', 'useGitHubAuth'];
1248
+ needed.forEach(item => {
1249
+ if (!newImports.includes(item)) {
1250
+ newImports.push(item);
1251
+ }
1252
+ });
1253
+
1254
+ content = content.replace(
1255
+ /import\s+\{[^}]+\}\s+from\s+["']hale-commenting-system["']/,
1256
+ `import { ${newImports.join(', ')} } from "hale-commenting-system"`
1257
+ );
1230
1258
  }
1231
- const importIndex = lastImportIndex >= 0 ? lastImportIndex + 1 : 0;
1232
-
1233
- const componentImports = types.importDeclaration(
1234
- [
1235
- types.importSpecifier(types.identifier('CommentPanel'), types.identifier('CommentPanel')),
1236
- types.importSpecifier(types.identifier('CommentOverlay'), types.identifier('CommentOverlay'))
1237
- ],
1238
- types.stringLiteral('hale-commenting-system')
1239
- );
1240
-
1241
- ast.program.body.splice(importIndex, 0, componentImports);
1242
1259
  }
1243
1260
 
1244
- // Wrap Page children with CommentPanel and CommentOverlay
1245
- if (pageElement) {
1246
- const pageChildren = pageElement.node.children;
1247
-
1248
- // Check if already wrapped
1249
- if (pageChildren.length > 0 &&
1250
- pageChildren[0].type === 'JSXElement' &&
1251
- pageChildren[0].openingElement.name.name === 'CommentPanel') {
1252
- console.log(' ⚠️ Already integrated (CommentPanel found in JSX)');
1253
- return false;
1261
+ // Step 2: Add hooks to the component
1262
+ // Find the AppLayout function/component
1263
+ const componentMatch = content.match(/(const\s+AppLayout[^=]+=\s*\([^)]*\)\s*=>\s*\{)/);
1264
+ if (componentMatch) {
1265
+ const componentStart = content.indexOf(componentMatch[0]);
1266
+ const afterComponentStart = componentStart + componentMatch[0].length;
1267
+
1268
+ // Check if hooks are already added
1269
+ if (!content.includes('const { commentsEnabled, setCommentsEnabled')) {
1270
+ // Find where to insert hooks (after existing useState declarations)
1271
+ const stateMatch = content.slice(afterComponentStart).match(/const\s+\[[^\]]+\]\s*=\s*React\.useState/);
1272
+ let hookInsertPos;
1273
+
1274
+ if (stateMatch) {
1275
+ const statePos = content.indexOf(stateMatch[0], afterComponentStart);
1276
+ const semicolonPos = content.indexOf(';', statePos);
1277
+ hookInsertPos = semicolonPos + 1;
1278
+ } else {
1279
+ hookInsertPos = afterComponentStart;
1280
+ }
1281
+
1282
+ const hooks = `
1283
+ const { commentsEnabled, setCommentsEnabled, drawerPinnedOpen, setDrawerPinnedOpen, floatingWidgetMode, setFloatingWidgetMode } = useComments();
1284
+ const { isAuthenticated, user, login, logout } = useGitHubAuth();
1285
+ `;
1286
+ content = content.slice(0, hookInsertPos) + hooks + content.slice(hookInsertPos);
1254
1287
  }
1288
+ }
1255
1289
 
1256
- // Create CommentOverlay
1257
- const commentOverlay = types.jsxElement(
1258
- types.jsxOpeningElement(types.jsxIdentifier('CommentOverlay'), [], true),
1259
- null,
1260
- []
1261
- );
1290
+ // Step 3: Add the special renderNavGroup logic
1291
+ // Replace the simple arrow function with a block function that has special Comments handling
1292
+ if (!content.includes("group.label === 'Comments'")) {
1293
+ // Find the renderNavGroup function - handle both arrow expression () => (...) and block () => {...}
1294
+ const arrowFuncMatch = content.match(/const\s+renderNavGroup\s*=\s*\(([^)]+)\)\s*=>\s*\(/);
1295
+
1296
+ if (arrowFuncMatch) {
1297
+ const params = arrowFuncMatch[1];
1298
+
1299
+ // Find the entire function including the closing parenthesis and semicolon
1300
+ const funcStart = content.indexOf(arrowFuncMatch[0]);
1301
+ const afterArrow = funcStart + arrowFuncMatch[0].length;
1302
+
1303
+ // Find matching closing paren and semicolon
1304
+ let depth = 1;
1305
+ let endPos = afterArrow;
1306
+ for (let i = afterArrow; i < content.length; i++) {
1307
+ if (content.charAt(i) === '(') depth++;
1308
+ if (content.charAt(i) === ')') {
1309
+ depth--;
1310
+ if (depth === 0) {
1311
+ // Found the closing paren, now find the semicolon
1312
+ endPos = i + 1;
1313
+ while (endPos < content.length && content.charAt(endPos).trim() === '') endPos++;
1314
+ if (content.charAt(endPos) === ';') endPos++;
1315
+ break;
1316
+ }
1317
+ }
1318
+ }
1262
1319
 
1263
- // Create CommentPanel wrapping children and CommentOverlay
1264
- const commentPanel = types.jsxElement(
1265
- types.jsxOpeningElement(types.jsxIdentifier('CommentPanel'), []),
1266
- types.jsxClosingElement(types.jsxIdentifier('CommentPanel')),
1267
- [commentOverlay, ...pageChildren]
1320
+ // Extract the original NavExpandable JSX (we'll use it as the default case)
1321
+ const originalBody = content.slice(funcStart + arrowFuncMatch[0].length - 1, endPos - 1); // Remove opening ( and closing );
1322
+
1323
+ // Create the new block function
1324
+ const newFunction = `const renderNavGroup = (${params}) => {
1325
+ // Special handling for Comments group
1326
+ if (group.label === 'Comments') {
1327
+ return (
1328
+ <NavExpandable
1329
+ key={\`\${group.label}-\${groupIndex}\`}
1330
+ id={\`\${group.label}-\${groupIndex}\`}
1331
+ title="Hale Commenting System"
1332
+ isActive={group.routes.some((route) => route.path === location.pathname)}
1333
+ >
1334
+ <NavItem
1335
+ onClick={(e) => {
1336
+ e.stopPropagation();
1337
+ setFloatingWidgetMode(!floatingWidgetMode);
1338
+ if (!floatingWidgetMode) {
1339
+ setDrawerPinnedOpen(false);
1340
+ }
1341
+ }}
1342
+ style={{ cursor: 'pointer' }}
1343
+ >
1344
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
1345
+ <ExternalLinkAltIcon />
1346
+ <span>{floatingWidgetMode ? 'Close widget' : 'Pop out'}</span>
1347
+ </div>
1348
+ </NavItem>
1349
+ <NavItem>
1350
+ <div
1351
+ data-comment-controls
1352
+ style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', paddingRight: '1rem' }}
1353
+ >
1354
+ <span>Enable Comments</span>
1355
+ <Switch
1356
+ id="comments-enabled-switch"
1357
+ isChecked={commentsEnabled}
1358
+ onChange={(_event, checked) => {
1359
+ setCommentsEnabled(checked);
1360
+ if (checked) {
1361
+ setDrawerPinnedOpen(true);
1362
+ }
1363
+ }}
1364
+ aria-label="Enable or disable comments"
1365
+ />
1366
+ </div>
1367
+ </NavItem>
1368
+ <NavItem>
1369
+ <div
1370
+ data-comment-controls
1371
+ style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', paddingRight: '1rem' }}
1372
+ >
1373
+ <span>Page info drawer</span>
1374
+ <Switch
1375
+ id="page-info-drawer-switch"
1376
+ isChecked={drawerPinnedOpen}
1377
+ onChange={(_event, checked) => setDrawerPinnedOpen(checked)}
1378
+ aria-label="Pin page info drawer open"
1379
+ />
1380
+ </div>
1381
+ </NavItem>
1382
+ <NavItem>
1383
+ <div
1384
+ data-comment-controls
1385
+ style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', paddingRight: '1rem' }}
1386
+ >
1387
+ {isAuthenticated ? (
1388
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
1389
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
1390
+ <GithubIcon />
1391
+ {user?.login ? \`@\${user.login}\` : 'Signed in'}
1392
+ </span>
1393
+ <Button variant="link" isInline onClick={logout}>
1394
+ Sign out
1395
+ </Button>
1396
+ </div>
1397
+ ) : (
1398
+ <Button variant="link" isInline icon={<GithubIcon />} onClick={login}>
1399
+ Sign in with GitHub
1400
+ </Button>
1401
+ )}
1402
+ </div>
1403
+ </NavItem>
1404
+ {group.routes.map((route, idx) => route.label && renderNavItem(route, idx))}
1405
+ </NavExpandable>
1268
1406
  );
1407
+ }
1408
+
1409
+ // Default handling for other groups
1410
+ return ${originalBody};
1411
+ };`;
1269
1412
 
1270
- pageElement.node.children = [commentPanel];
1413
+ // Replace the old function with the new one
1414
+ content = content.slice(0, funcStart) + newFunction + content.slice(endPos);
1415
+ }
1271
1416
  }
1272
1417
 
1273
- const output = generate(ast, {
1274
- retainLines: false,
1275
- compact: false
1276
- }, content);
1418
+ // Step 4: Wrap Page children if not already done
1419
+ if (!content.includes('<CommentPanel>')) {
1420
+ // Find the return statement with Page
1421
+ const pageMatch = content.match(/<Page[^>]*>/);
1422
+ if (pageMatch) {
1423
+ const pageStart = content.indexOf(pageMatch[0]);
1424
+ const pageEnd = content.indexOf('</Page>', pageStart);
1425
+
1426
+ if (pageEnd > pageStart) {
1427
+ // Extract children between Page tags
1428
+ const pageOpenTagEnd = content.indexOf('>', pageStart) + 1;
1429
+ const children = content.slice(pageOpenTagEnd, pageEnd);
1430
+
1431
+ // Wrap with CommentPanel and add CommentOverlay
1432
+ const wrappedChildren = `
1433
+ <CommentPanel>
1434
+ <CommentOverlay />
1435
+ ${children.trim()}
1436
+ </CommentPanel>
1437
+ `;
1438
+
1439
+ content = content.slice(0, pageOpenTagEnd) + wrappedChildren + content.slice(pageEnd);
1440
+ }
1441
+ }
1442
+ }
1277
1443
 
1278
- fs.writeFileSync(filePath, output.code);
1444
+ fs.writeFileSync(filePath, content);
1279
1445
  return true;
1280
1446
  } catch (error) {
1281
1447
  console.error(` ❌ Error modifying ${filePath}:`, error.message);
1448
+ console.error(error.stack);
1282
1449
  return false;
1283
1450
  }
1284
1451
  }