@dcoder-x/plugin-shared 0.1.8 → 0.1.10
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.
|
@@ -7,6 +7,10 @@ export declare class RouteExtractor {
|
|
|
7
7
|
private walkPagesDir;
|
|
8
8
|
private walkTanStackDir;
|
|
9
9
|
private extractReactRouterRoutes;
|
|
10
|
+
private buildImportMap;
|
|
11
|
+
private resolveRouteComponentFile;
|
|
12
|
+
private collectJSXComponentNames;
|
|
13
|
+
private resolveImportToFile;
|
|
10
14
|
private findFilesContaining;
|
|
11
15
|
private extractParams;
|
|
12
16
|
private findNearestLayout;
|
|
@@ -131,6 +131,7 @@ class RouteExtractor {
|
|
|
131
131
|
catch {
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
|
+
const importMap = this.buildImportMap(ast, filePath);
|
|
134
135
|
traverse(ast, {
|
|
135
136
|
JSXOpeningElement: (nodePath) => {
|
|
136
137
|
const name = nodePath.node.name?.name;
|
|
@@ -138,21 +139,110 @@ class RouteExtractor {
|
|
|
138
139
|
return;
|
|
139
140
|
const pathAttr = nodePath.node.attributes.find((a) => a.name?.name === 'path');
|
|
140
141
|
const routePath = pathAttr?.value?.value;
|
|
141
|
-
if (routePath)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
if (!routePath)
|
|
143
|
+
return;
|
|
144
|
+
const resolvedFilePath = this.resolveRouteComponentFile(nodePath, importMap) ?? filePath;
|
|
145
|
+
results.push({
|
|
146
|
+
path: routePath,
|
|
147
|
+
filePath: resolvedFilePath,
|
|
148
|
+
isDynamic: routePath.includes(':'),
|
|
149
|
+
params: (routePath.match(/:(\w+)/g) || []).map((p) => p.slice(1)),
|
|
150
|
+
layout: null,
|
|
151
|
+
routerType: 'react-router',
|
|
152
|
+
semantic: this.deriveSemanticMeaning(routePath),
|
|
153
|
+
});
|
|
152
154
|
},
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
|
-
|
|
157
|
+
// Deduplicate: same path + filePath can appear if multiple router files
|
|
158
|
+
// re-export or nest the same routes (e.g. AppProviders wrapping routerConfig).
|
|
159
|
+
const seen = new Set();
|
|
160
|
+
return results.filter((r) => {
|
|
161
|
+
const key = `${r.path}::${r.filePath}`;
|
|
162
|
+
if (seen.has(key))
|
|
163
|
+
return false;
|
|
164
|
+
seen.add(key);
|
|
165
|
+
return true;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
buildImportMap(ast, fromFile) {
|
|
169
|
+
const map = new Map();
|
|
170
|
+
for (const node of ast.program.body) {
|
|
171
|
+
if (node.type !== 'ImportDeclaration')
|
|
172
|
+
continue;
|
|
173
|
+
const importSource = node.source?.value;
|
|
174
|
+
if (!importSource)
|
|
175
|
+
continue;
|
|
176
|
+
const resolved = this.resolveImportToFile(importSource, fromFile);
|
|
177
|
+
if (!resolved)
|
|
178
|
+
continue;
|
|
179
|
+
for (const specifier of node.specifiers ?? []) {
|
|
180
|
+
if (specifier.type === 'ImportDefaultSpecifier' ||
|
|
181
|
+
specifier.type === 'ImportSpecifier') {
|
|
182
|
+
const localName = specifier.local?.name;
|
|
183
|
+
if (localName)
|
|
184
|
+
map.set(localName, resolved);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return map;
|
|
189
|
+
}
|
|
190
|
+
resolveRouteComponentFile(routeOpeningPath, importMap) {
|
|
191
|
+
const elementAttr = routeOpeningPath.node.attributes.find((a) => a.name?.name === 'element');
|
|
192
|
+
if (!elementAttr)
|
|
193
|
+
return null;
|
|
194
|
+
const expr = elementAttr.value?.expression;
|
|
195
|
+
if (!expr)
|
|
196
|
+
return null;
|
|
197
|
+
// Try each JSX component name in document order; first match in the import map wins.
|
|
198
|
+
// Wrapper components like <Can> or <RouteGuard> won't be in the map, so we fall
|
|
199
|
+
// through to the actual page component nested inside them.
|
|
200
|
+
const componentNames = this.collectJSXComponentNames(expr);
|
|
201
|
+
for (const componentName of componentNames) {
|
|
202
|
+
const resolved = importMap.get(componentName);
|
|
203
|
+
if (resolved)
|
|
204
|
+
return resolved;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
collectJSXComponentNames(node) {
|
|
209
|
+
if (!node)
|
|
210
|
+
return [];
|
|
211
|
+
if (node.type === 'JSXElement') {
|
|
212
|
+
const tagName = node.openingElement?.name?.name ?? '';
|
|
213
|
+
const names = [];
|
|
214
|
+
if (tagName && /^[A-Z]/.test(tagName))
|
|
215
|
+
names.push(tagName);
|
|
216
|
+
for (const child of node.children ?? []) {
|
|
217
|
+
names.push(...this.collectJSXComponentNames(child));
|
|
218
|
+
}
|
|
219
|
+
return names;
|
|
220
|
+
}
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
resolveImportToFile(importSource, fromFile) {
|
|
224
|
+
if (!importSource.startsWith('.') && !importSource.startsWith('/'))
|
|
225
|
+
return null;
|
|
226
|
+
const base = importSource.startsWith('/')
|
|
227
|
+
? path_1.default.normalize(importSource)
|
|
228
|
+
: path_1.default.resolve(path_1.default.dirname(fromFile), importSource);
|
|
229
|
+
const candidates = [
|
|
230
|
+
base,
|
|
231
|
+
`${base}.ts`,
|
|
232
|
+
`${base}.tsx`,
|
|
233
|
+
`${base}.js`,
|
|
234
|
+
`${base}.jsx`,
|
|
235
|
+
path_1.default.join(base, 'index.ts'),
|
|
236
|
+
path_1.default.join(base, 'index.tsx'),
|
|
237
|
+
path_1.default.join(base, 'index.js'),
|
|
238
|
+
path_1.default.join(base, 'index.jsx'),
|
|
239
|
+
];
|
|
240
|
+
for (const candidate of candidates) {
|
|
241
|
+
if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile()) {
|
|
242
|
+
return path_1.default.normalize(candidate);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
156
246
|
}
|
|
157
247
|
findFilesContaining(dir, pattern) {
|
|
158
248
|
const results = [];
|
|
@@ -157,10 +157,10 @@ function findAttributeInsertIndex(source, range) {
|
|
|
157
157
|
if (!range)
|
|
158
158
|
return null;
|
|
159
159
|
const end = range[1];
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
160
|
+
// Avoid searching backwards with lastIndexOf('>') — it hits '>' inside JSX expression
|
|
161
|
+
// containers (e.g. onClick={() => navigate('/')}). Use the AST range end directly.
|
|
162
|
+
const isSelfClosing = source[end - 2] === '/';
|
|
163
|
+
return isSelfClosing ? end - 2 : end - 1;
|
|
164
164
|
}
|
|
165
165
|
function applyEdits(source, edits) {
|
|
166
166
|
const sorted = edits.slice().sort((a, b) => b.index - a.index);
|
|
@@ -24,6 +24,13 @@ export interface SiteComponentRegistryUpload {
|
|
|
24
24
|
effects?: any[];
|
|
25
25
|
contextUsages?: any[];
|
|
26
26
|
}>;
|
|
27
|
+
flows?: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
start_component: string;
|
|
30
|
+
end_component: string;
|
|
31
|
+
steps: string[];
|
|
32
|
+
user_intent: string;
|
|
33
|
+
}>;
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
export declare function mapArtifactsToSiteRegistry(artifacts: PolicyArtifacts, domain: string): SiteComponentRegistryUpload;
|
|
@@ -20,15 +20,28 @@ function mapArtifactsToSiteRegistry(artifacts, domain) {
|
|
|
20
20
|
file: c.filePath,
|
|
21
21
|
stateVariables: c.stateVariables || [],
|
|
22
22
|
interactions: c.interactions || [],
|
|
23
|
-
// Best-effort placeholders: preserve array shape expected by backend.
|
|
24
23
|
conditionalRenders: [],
|
|
25
|
-
effects: (c.interactions || []).map((
|
|
24
|
+
effects: (c.interactions || []).map(() => ({
|
|
26
25
|
dependencies: [],
|
|
27
26
|
asyncCalls: [],
|
|
28
27
|
runsOnMount: false,
|
|
29
28
|
})),
|
|
30
29
|
contextUsages: [],
|
|
31
30
|
}));
|
|
31
|
+
const flows = (artifacts.policy.flows || []).map((f) => {
|
|
32
|
+
// Map internal PolicyFlow -> spec FlowEntry shape.
|
|
33
|
+
// steps[] in PolicyFlow are route paths; use first/last as start/end component.
|
|
34
|
+
const stepTargets = f.steps.map((s) => s.target);
|
|
35
|
+
const startComponent = f.steps[0]?.target ?? f.page;
|
|
36
|
+
const endComponent = f.steps[f.steps.length - 1]?.target ?? f.page;
|
|
37
|
+
return {
|
|
38
|
+
name: f.flowId,
|
|
39
|
+
start_component: startComponent,
|
|
40
|
+
end_component: endComponent,
|
|
41
|
+
steps: stepTargets,
|
|
42
|
+
user_intent: f.intentPatterns?.[0] ?? '',
|
|
43
|
+
};
|
|
44
|
+
});
|
|
32
45
|
return {
|
|
33
46
|
domain,
|
|
34
47
|
components: {
|
|
@@ -36,6 +49,7 @@ function mapArtifactsToSiteRegistry(artifacts, domain) {
|
|
|
36
49
|
generatedAt,
|
|
37
50
|
selectors,
|
|
38
51
|
components,
|
|
52
|
+
...(flows.length > 0 ? { flows } : {}),
|
|
39
53
|
},
|
|
40
54
|
};
|
|
41
55
|
}
|