@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
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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(
|
|
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 ===
|
|
183
|
+
if (resource === absFilePath)
|
|
143
184
|
return mod;
|
|
144
185
|
const id = normalizeModulePath(typeof mod?.identifier === 'function' ? mod.identifier() : mod?.id);
|
|
145
|
-
if (id ===
|
|
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
|
-
|
|
241
|
-
if (
|
|
242
|
-
queue.push(
|
|
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)
|