@dcoder-x/plugin-shared 0.1.10 → 0.1.12

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.
@@ -19,13 +19,32 @@ class ComponentExtractor {
19
19
  }
20
20
  async extract() {
21
21
  const elements = [];
22
+ // Build a direct filePath → route.path index so that files which are
23
+ // themselves route entries get their own route, not whichever layout or
24
+ // parent route happened to import them first.
25
+ const fileRouteIndex = new Map();
26
+ for (const route of this.routes) {
27
+ const relFwd = route.filePath.replace(/\\/g, '/');
28
+ fileRouteIndex.set(relFwd, route.path);
29
+ // Also index by just the basename-less relative path variations
30
+ fileRouteIndex.set(route.filePath, route.path);
31
+ }
32
+ const seen = new Set(); // deduplicate across routes
22
33
  for (const route of this.routes) {
23
34
  const moduleFiles = this.getModuleFilesForRoute(route.filePath);
24
35
  for (const filePath of moduleFiles) {
36
+ if (seen.has(filePath))
37
+ continue;
38
+ seen.add(filePath);
25
39
  const source = this.readSource(filePath);
26
40
  if (!source)
27
41
  continue;
28
- elements.push(...this.extractFromSource(source, filePath, route.path));
42
+ // Prefer the route this file is directly registered as; fall back to
43
+ // the route whose module graph we're walking (the layout/parent route).
44
+ const assignedRoute = fileRouteIndex.get(filePath) ??
45
+ fileRouteIndex.get(filePath.replace(/\\/g, '/')) ??
46
+ route.path;
47
+ elements.push(...this.extractFromSource(source, filePath, assignedRoute));
29
48
  }
30
49
  }
31
50
  return elements;
@@ -38,12 +57,24 @@ class ComponentExtractor {
38
57
  const components = [];
39
58
  const contextResolver = new ComponentContextResolver_1.ComponentContextResolver();
40
59
  const graphExtractor = new InteractionGraphExtractor_1.InteractionGraphExtractor();
60
+ const fileRouteIndex = new Map();
61
+ for (const route of this.routes) {
62
+ fileRouteIndex.set(route.filePath, route.path);
63
+ fileRouteIndex.set(route.filePath.replace(/\\/g, '/'), route.path);
64
+ }
65
+ const seen = new Set();
41
66
  for (const route of this.routes) {
42
67
  const moduleFiles = this.getModuleFilesForRoute(route.filePath);
43
68
  for (const filePath of moduleFiles) {
69
+ if (seen.has(filePath))
70
+ continue;
71
+ seen.add(filePath);
44
72
  const source = this.readSource(filePath);
45
73
  if (!source)
46
74
  continue;
75
+ const assignedRoute = fileRouteIndex.get(filePath) ??
76
+ fileRouteIndex.get(filePath.replace(/\\/g, '/')) ??
77
+ route.path;
47
78
  let ast;
48
79
  try {
49
80
  ast = (0, parser_1.parse)(source, { sourceType: 'module', plugins: ['typescript', 'jsx'] });
@@ -61,7 +92,7 @@ class ComponentExtractor {
61
92
  components.push({
62
93
  name: componentName,
63
94
  filePath,
64
- route: route.path,
95
+ route: assignedRoute,
65
96
  stateVariables: context.stateVariables,
66
97
  interactions,
67
98
  });
@@ -79,7 +110,7 @@ class ComponentExtractor {
79
110
  components.push({
80
111
  name: componentName,
81
112
  filePath,
82
- route: route.path,
113
+ route: assignedRoute,
83
114
  stateVariables: context.stateVariables,
84
115
  interactions,
85
116
  });
@@ -97,8 +128,13 @@ class ComponentExtractor {
97
128
  return this.walkRollupGraph(entryFilePath, this.source.moduleGraph);
98
129
  }
99
130
  walkWebpackGraph(entryFilePath, compilation) {
131
+ // Normalise to relative forward-slash paths so visited keys and injectedMap
132
+ // keys are consistent across macOS and Windows.
133
+ const toRelFwd = (p) => path_1.default.isAbsolute(p)
134
+ ? path_1.default.relative(process.cwd(), p).replace(/\\/g, '/')
135
+ : p.replace(/\\/g, '/');
100
136
  const visited = new Set();
101
- const queue = [entryFilePath];
137
+ const queue = [toRelFwd(entryFilePath)];
102
138
  while (queue.length > 0) {
103
139
  const current = queue.shift();
104
140
  if (visited.has(current))
@@ -109,26 +145,31 @@ class ComponentExtractor {
109
145
  if (mod) {
110
146
  for (const depPath of this.getWebpackDependencyPaths(compilation, mod)) {
111
147
  if (depPath && !depPath.includes('node_modules')) {
112
- queue.push(depPath);
148
+ queue.push(toRelFwd(depPath));
113
149
  queuedFromWebpackGraph = true;
114
150
  }
115
151
  }
116
152
  }
117
153
  // Fallback for webpack variants where moduleGraph helpers are unavailable.
118
154
  if (!queuedFromWebpackGraph) {
155
+ // getFileImportPaths resolves to absolute paths via resolveImportFile;
156
+ // normalise them before queuing.
119
157
  for (const depPath of this.getFileImportPaths(current)) {
120
158
  if (depPath && !depPath.includes('node_modules')) {
121
- queue.push(depPath);
159
+ queue.push(toRelFwd(depPath));
122
160
  }
123
161
  }
124
162
  }
125
163
  }
126
164
  return Array.from(visited);
127
165
  }
128
- findWebpackModule(compilation, filePath) {
166
+ findWebpackModule(compilation, relFilePath) {
167
+ // relFilePath is already a relative forward-slash path; convert to the
168
+ // absolute path that webpack stores so its graph lookup succeeds.
169
+ const absFilePath = path_1.default.resolve(process.cwd(), relFilePath);
129
170
  const graph = compilation?.moduleGraph;
130
171
  if (graph?.getModuleByIdentifier) {
131
- const byIdentifier = graph.getModuleByIdentifier(filePath);
172
+ const byIdentifier = graph.getModuleByIdentifier(absFilePath);
132
173
  if (byIdentifier)
133
174
  return byIdentifier;
134
175
  }
@@ -139,10 +180,10 @@ class ComponentExtractor {
139
180
  : [];
140
181
  for (const mod of modules) {
141
182
  const resource = normalizeModulePath(mod?.resource || mod?.userRequest);
142
- if (resource === filePath)
183
+ if (resource === absFilePath)
143
184
  return mod;
144
185
  const id = normalizeModulePath(typeof mod?.identifier === 'function' ? mod.identifier() : mod?.id);
145
- if (id === filePath)
186
+ if (id === absFilePath)
146
187
  return mod;
147
188
  }
148
189
  return null;
@@ -226,8 +267,13 @@ class ComponentExtractor {
226
267
  return resolved;
227
268
  }
228
269
  walkRollupGraph(entryFilePath, graph) {
270
+ // Normalise the entry to a relative forward-slash path so it matches the
271
+ // keys stored by ClippyVitePlugin (which also normalises to relative).
272
+ const toRelFwd = (p) => path_1.default.isAbsolute(p)
273
+ ? path_1.default.relative(process.cwd(), p).replace(/\\/g, '/')
274
+ : p.replace(/\\/g, '/');
229
275
  const visited = new Set();
230
- const queue = [entryFilePath];
276
+ const queue = [toRelFwd(entryFilePath)];
231
277
  while (queue.length > 0) {
232
278
  const current = queue.shift();
233
279
  if (visited.has(current))
@@ -237,9 +283,9 @@ class ComponentExtractor {
237
283
  if (!mod)
238
284
  continue;
239
285
  for (const importId of mod.importedIds) {
240
- const importPath = normalizeModulePath(importId, current);
241
- if (importPath && !importPath.includes('node_modules')) {
242
- queue.push(importPath);
286
+ // importedIds are already relative keys after the vite plugin normalised them
287
+ if (importId && !importId.includes('node_modules')) {
288
+ queue.push(toRelFwd(importId));
243
289
  }
244
290
  }
245
291
  }
@@ -401,8 +447,39 @@ function deriveLabel(tag, props, nearbyText) {
401
447
  return props['placeholder'];
402
448
  if (props['name'])
403
449
  return props['name'];
450
+ // Last resort: extract meaningful words from className for icon-only elements
451
+ // (e.g. password-toggle, close-modal buttons that contain only an SVG).
452
+ const className = props['className'] || props['class'] || '';
453
+ if (className) {
454
+ const meaningful = classNameToLabel(className);
455
+ if (meaningful)
456
+ return meaningful;
457
+ }
404
458
  return null;
405
459
  }
460
+ // Utility tokens that describe layout/spacing/color but not element purpose.
461
+ const TAILWIND_NOISE = new Set([
462
+ 'flex', 'grid', 'block', 'inline', 'hidden', 'relative', 'absolute', 'fixed', 'sticky',
463
+ 'items', 'justify', 'content', 'self', 'grow', 'shrink', 'order',
464
+ 'inset', 'top', 'right', 'bottom', 'left', 'z',
465
+ 'w', 'h', 'min', 'max', 'p', 'px', 'py', 'pt', 'pb', 'pl', 'pr', 'm', 'mx', 'my', 'mt', 'mb', 'ml', 'mr',
466
+ 'gap', 'space', 'divide', 'overflow', 'truncate', 'whitespace', 'break',
467
+ 'text', 'font', 'leading', 'tracking', 'uppercase', 'lowercase', 'capitalize', 'normal',
468
+ 'bg', 'border', 'rounded', 'shadow', 'ring', 'outline', 'opacity', 'transition', 'duration',
469
+ 'hover', 'focus', 'active', 'disabled', 'group', 'peer', 'placeholder',
470
+ 'sr', 'not', 'dark', 'sm', 'md', 'lg', 'xl', '2xl',
471
+ ]);
472
+ function classNameToLabel(className) {
473
+ const words = className
474
+ .split(/[\s\-_/]+/)
475
+ .map((w) => w.replace(/[^a-zA-Z]/g, '').toLowerCase())
476
+ .filter((w) => w.length > 2 && !TAILWIND_NOISE.has(w));
477
+ if (words.length === 0)
478
+ return null;
479
+ // Deduplicate, keep first 3 meaningful words
480
+ const unique = [...new Set(words)].slice(0, 3);
481
+ return unique.join(' ');
482
+ }
406
483
  function inferSemanticRole(tag, props, nearbyText) {
407
484
  const label = props['aria-label'] || props['placeholder'] || nearbyText[0] || '';
408
485
  return `${label} ${tag}`.trim().toLowerCase();
package/dist/types.d.ts CHANGED
@@ -96,6 +96,7 @@ export interface PolicySelectorEntry {
96
96
  tag: string;
97
97
  component: string;
98
98
  label?: string;
99
+ nearbyText?: string[];
99
100
  route: string;
100
101
  filePath: string;
101
102
  attributes: Array<{
@@ -142,6 +143,7 @@ export interface SelectorManifestEntry {
142
143
  component: string;
143
144
  tag: string;
144
145
  label?: string;
146
+ nearbyText?: string[];
145
147
  routes: string[];
146
148
  }
147
149
  export interface SelectorManifest {
@@ -83,6 +83,7 @@ class PackageBuilder {
83
83
  component: entry.component,
84
84
  tag: entry.tag,
85
85
  label: entry.label,
86
+ nearbyText: entry.nearbyText,
86
87
  routes: [entry.route],
87
88
  });
88
89
  }
@@ -180,6 +181,7 @@ class PackageBuilder {
180
181
  tag: element.tag,
181
182
  component,
182
183
  label: element.label,
184
+ nearbyText: element.nearbyText?.length ? element.nearbyText : undefined,
183
185
  route: element.route,
184
186
  filePath: projectRoot
185
187
  ? this.normalizePath(element.filePath, projectRoot)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcoder-x/plugin-shared",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",