@dcoder-x/plugin-shared 0.1.14 → 0.1.15

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.
@@ -14,17 +14,21 @@ const HtmlTagGuards_1 = require("../injection/HtmlTagGuards");
14
14
  const IdStrategy_1 = require("../injection/IdStrategy");
15
15
  /**
16
16
  * A route is a usable module-graph entry point only when its filePath
17
- * points to an actual page component, not a layout/guard/wrapper.
18
- * Score 0 = structural wrapper → skip as entry point.
17
+ * points to an actual page component, not a layout/guard/wrapper/router file.
19
18
  */
20
19
  function isPageRoute(route) {
21
20
  if (route.isLayoutRoute)
22
21
  return false;
23
22
  const p = route.filePath.replace(/\\/g, '/').toLowerCase();
23
+ const base = path_1.default.basename(p).replace(/\.(tsx?|jsx?)$/, '');
24
24
  if (p.includes('/pages/') || p.includes('/views/') || p.includes('/screens/'))
25
25
  return true;
26
- if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p) ||
27
- /(provider|context|guard|layout|redirect|catchall|catch-all)/.test(path_1.default.basename(p)))
26
+ // Directory-based structural patterns
27
+ if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p))
28
+ return false;
29
+ // Basename-based structural patterns: wrapper, router config, and provider files
30
+ if (/(provider|context|guard|layout|redirect|catchall|catch-all)/.test(base) ||
31
+ /^(router|routes|routerconfig|approuter|routesconfig|routingconfig|routeconfig|approviders|appwrapper)$/i.test(base))
28
32
  return false;
29
33
  return true;
30
34
  }
@@ -413,10 +417,21 @@ function extractStaticProps(attributes) {
413
417
  continue;
414
418
  if (attr.value?.type === 'StringLiteral') {
415
419
  props[key] = attr.value.value;
420
+ continue;
416
421
  }
417
- if (attr.value?.type === 'JSXExpressionContainer' &&
418
- attr.value.expression?.type === 'StringLiteral') {
419
- props[key] = attr.value.expression.value;
422
+ if (attr.value?.type === 'JSXExpressionContainer') {
423
+ const expr = attr.value.expression;
424
+ if (expr?.type === 'StringLiteral') {
425
+ props[key] = expr.value;
426
+ }
427
+ else if (expr?.type === 'TemplateLiteral' &&
428
+ expr.quasis?.length === 1 &&
429
+ expr.expressions?.length === 0) {
430
+ // Pure template literal with no interpolations: `to="/some/path"`
431
+ const raw = expr.quasis[0].value.cooked ?? expr.quasis[0].value.raw;
432
+ if (raw)
433
+ props[key] = raw;
434
+ }
420
435
  }
421
436
  }
422
437
  return props;
@@ -14,6 +14,12 @@ export declare class FlowInferrer {
14
14
  */
15
15
  private isCoherentChain;
16
16
  private detectInteractionFlows;
17
+ /**
18
+ * Deduplicate interaction flows by (page, trigger event, effect target).
19
+ * Shared components used on multiple routes produce one flow per component
20
+ * interaction. After dedup, each distinct interaction appears once.
21
+ */
22
+ private deduplicateInteractionFlows;
17
23
  private buildChain;
18
24
  private generateIntentPatterns;
19
25
  /**
@@ -45,10 +45,9 @@ class FlowInferrer {
45
45
  infer() {
46
46
  const edges = this.buildEdgeGraph();
47
47
  const routeFlows = this.detectLinearFlows(edges);
48
- const interactionFlows = this.detectInteractionFlows();
48
+ const interactionFlows = this.deduplicateInteractionFlows(this.detectInteractionFlows());
49
49
  const combined = [...routeFlows, ...interactionFlows];
50
- // Fix 4: remove patterns that appear in too many flows — they are structural
51
- // navigation chrome (sidebars, footers) rather than user intent signals
50
+ // Remove patterns that appear in too many flows — structural nav chrome
52
51
  return this.deduplicateCrossFlowNoise(combined);
53
52
  }
54
53
  // ---------------------------------------------------------------------------
@@ -56,13 +55,21 @@ class FlowInferrer {
56
55
  // ---------------------------------------------------------------------------
57
56
  buildEdgeGraph() {
58
57
  const edges = [];
58
+ // Index routes both with and without leading slash to handle mixed conventions
59
59
  const routePaths = new Set(this.routes.map((r) => r.path));
60
+ const normalizeHref = (href) => href.replace(/^\//, '');
60
61
  for (const el of this.elements) {
61
62
  const isNav = el.tag === 'a' || el.isNavigationLink === true;
62
- const href = el.staticProps['href'] || el.staticProps['to'];
63
- if (!isNav || !href)
63
+ const rawHref = el.staticProps['href'] || el.staticProps['to'];
64
+ if (!isNav || !rawHref)
64
65
  continue;
65
- if (!routePaths.has(href))
66
+ // Match against both the raw href and the normalized (no leading slash) form
67
+ const href = routePaths.has(rawHref)
68
+ ? rawHref
69
+ : routePaths.has(normalizeHref(rawHref))
70
+ ? normalizeHref(rawHref)
71
+ : null;
72
+ if (!href)
66
73
  continue;
67
74
  const triggerText = el.staticProps['aria-label'] ||
68
75
  el.label ||
@@ -162,6 +169,26 @@ class FlowInferrer {
162
169
  return flows;
163
170
  }
164
171
  // ---------------------------------------------------------------------------
172
+ // Interaction flow deduplication
173
+ // ---------------------------------------------------------------------------
174
+ /**
175
+ * Deduplicate interaction flows by (page, trigger event, effect target).
176
+ * Shared components used on multiple routes produce one flow per component
177
+ * interaction. After dedup, each distinct interaction appears once.
178
+ */
179
+ deduplicateInteractionFlows(flows) {
180
+ const seen = new Set();
181
+ return flows.filter((f) => {
182
+ const trigger = f.edges[0]?.trigger ?? '';
183
+ const step = f.steps[0] ?? '';
184
+ const sig = `${f.page}::${trigger}::${step}`;
185
+ if (seen.has(sig))
186
+ return false;
187
+ seen.add(sig);
188
+ return true;
189
+ });
190
+ }
191
+ // ---------------------------------------------------------------------------
165
192
  // Chain builder
166
193
  // ---------------------------------------------------------------------------
167
194
  buildChain(start, adjacency, seen) {
@@ -18,16 +18,20 @@ const traverse = typeof traverse_1.default === 'function' ? traverse_1.default :
18
18
  /**
19
19
  * Score a resolved file path: higher = more likely to be an actual page component.
20
20
  * 3 = pages/views/screens directory → definitely a page
21
- * 2 = named after a recognisable page pattern but not in a pages dir
22
21
  * 1 = unknown / ambiguous
23
- * 0 = structural wrapper (provider, guard, layout, context, …)
22
+ * 0 = structural file (provider, guard, layout, context, router config, …)
24
23
  */
25
24
  function scoreFilePath(filePath) {
26
25
  const p = filePath.replace(/\\/g, '/').toLowerCase();
26
+ const base = path_1.default.basename(p).replace(/\.(tsx?|jsx?)$/, '');
27
27
  if (p.includes('/pages/') || p.includes('/views/') || p.includes('/screens/'))
28
28
  return 3;
29
- if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p) ||
30
- /(provider|context|guard|layout|redirect|catchall|catch-all)/.test(path_1.default.basename(p)))
29
+ // Directory-based structural patterns
30
+ if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p))
31
+ return 0;
32
+ // Basename-based structural patterns: wrapper/config/router files
33
+ if (/(provider|context|guard|layout|redirect|catchall|catch-all)/.test(base) ||
34
+ /^(router|routes|routerconfig|approuter|routesconfig|routingconfig|routeconfig|approviders|appwrapper)$/i.test(base))
31
35
  return 0;
32
36
  return 1;
33
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcoder-x/plugin-shared",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",