@vibexdotnew/inspector 0.0.10 → 0.0.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.
@@ -0,0 +1,372 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * FlexApp Element Tagger - Turbopack/Webpack Loader
5
+ *
6
+ * Transforms JSX to inject location attributes for visual editing.
7
+ * Injects: data-flex-loc="filepath:line:col" data-flex-name="tagName"
8
+ *
9
+ * SAFE: Never throws errors - returns original source on any failure
10
+ */
11
+
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Safe imports with fallbacks
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ let parse, MagicString, walk;
17
+
18
+ try {
19
+ parse = require("@babel/parser").parse;
20
+ } catch {
21
+ parse = null;
22
+ }
23
+
24
+ try {
25
+ MagicString = require("magic-string");
26
+ } catch {
27
+ MagicString = null;
28
+ }
29
+
30
+ // estree-walker v3 is ESM-only, try multiple import strategies
31
+ try {
32
+ walk = require("estree-walker").walk;
33
+ } catch {
34
+ try {
35
+ // Try the sync version
36
+ const mod = require("estree-walker/src/sync.js");
37
+ walk = mod.walk || mod.default?.walk;
38
+ } catch {
39
+ walk = null;
40
+ }
41
+ }
42
+
43
+ const path = require("path");
44
+ const fs = require("fs");
45
+
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+ // Element Exclusion Lists
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+
50
+ const EXCLUDED_ELEMENTS = new Set([
51
+ // React Three Fiber primitives
52
+ "primitive", "mesh", "group", "scene", "object3D",
53
+ "camera", "perspectiveCamera", "orthographicCamera", "cubeCamera", "arrayCamera",
54
+ "instancedMesh", "batchedMesh", "skinnedMesh", "sprite", "lOD",
55
+ "lineSegments", "lineLoop", "points", "skeleton", "bone",
56
+ "bufferGeometry", "instancedBufferGeometry",
57
+ "boxGeometry", "sphereGeometry", "planeGeometry", "cylinderGeometry",
58
+ "coneGeometry", "torusGeometry", "torusKnotGeometry", "ringGeometry",
59
+ "circleGeometry", "tubeGeometry", "extrudeGeometry", "latheGeometry",
60
+ "shapeGeometry", "polyhedronGeometry", "icosahedronGeometry",
61
+ "octahedronGeometry", "tetrahedronGeometry", "dodecahedronGeometry",
62
+ "capsuleGeometry", "edgesGeometry", "wireframeGeometry",
63
+ "material", "meshBasicMaterial", "meshStandardMaterial", "meshPhongMaterial",
64
+ "meshLambertMaterial", "meshToonMaterial", "meshNormalMaterial",
65
+ "meshPhysicalMaterial", "meshDepthMaterial", "meshDistanceMaterial",
66
+ "meshMatcapMaterial", "shaderMaterial", "rawShaderMaterial",
67
+ "pointsMaterial", "lineBasicMaterial", "lineDashedMaterial",
68
+ "spriteMaterial", "shadowMaterial",
69
+ "light", "ambientLight", "directionalLight", "pointLight", "spotLight",
70
+ "hemisphereLight", "rectAreaLight", "lightProbe",
71
+ "texture", "videoTexture", "canvasTexture", "cubeTexture",
72
+ "dataTexture", "compressedTexture", "depthTexture",
73
+ "vector2", "vector3", "vector4", "euler", "quaternion",
74
+ "matrix3", "matrix4", "color", "raycaster", "fog", "fogExp2",
75
+ // Drei components
76
+ "Billboard", "Text", "Text3D", "Html", "Trail", "Decal", "Edges", "Outlines",
77
+ "PerspectiveCamera", "OrthographicCamera", "CubeCamera",
78
+ "OrbitControls", "CameraControls", "TransformControls", "PivotControls",
79
+ "Environment", "Sky", "Stars", "Cloud", "Sparkles", "Float", "Stage",
80
+ "RoundedBox", "Line", "Points", "Instances", "Merged",
81
+ "MeshReflectorMaterial", "MeshTransmissionMaterial", "shaderMaterial",
82
+ ]);
83
+
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+ // Simple AST Walker (fallback if estree-walker fails)
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+
88
+ function simpleWalk(node, callbacks, parent = null) {
89
+ if (!node || typeof node !== "object") return;
90
+
91
+ // Add parent reference
92
+ if (parent && !Object.prototype.hasOwnProperty.call(node, "parent")) {
93
+ Object.defineProperty(node, "parent", {
94
+ value: parent,
95
+ enumerable: false,
96
+ writable: true,
97
+ });
98
+ }
99
+
100
+ if (callbacks.enter) {
101
+ callbacks.enter(node, parent);
102
+ }
103
+
104
+ // Walk children
105
+ for (const key of Object.keys(node)) {
106
+ if (key === "parent") continue;
107
+ const child = node[key];
108
+ if (Array.isArray(child)) {
109
+ for (const item of child) {
110
+ if (item && typeof item === "object" && item.type) {
111
+ simpleWalk(item, callbacks, node);
112
+ }
113
+ }
114
+ } else if (child && typeof child === "object" && child.type) {
115
+ simpleWalk(child, callbacks, node);
116
+ }
117
+ }
118
+ }
119
+
120
+ // Use estree-walker if available, otherwise use simple walker
121
+ const walkAst = walk || simpleWalk;
122
+
123
+ // ─────────────────────────────────────────────────────────────────────────────
124
+ // Helpers
125
+ // ─────────────────────────────────────────────────────────────────────────────
126
+
127
+ /**
128
+ * Quick check if source likely contains JSX
129
+ */
130
+ function hasJSX(source) {
131
+ // Look for JSX patterns: <Component, <div, etc.
132
+ return /<[A-Za-z]/.test(source);
133
+ }
134
+
135
+ /**
136
+ * Check if element should receive location attributes
137
+ */
138
+ function shouldInjectAttrs(tagName) {
139
+ if (!tagName) return false;
140
+ if (tagName === "Fragment" || tagName === "React.Fragment") return false;
141
+ if (EXCLUDED_ELEMENTS.has(tagName)) return false;
142
+ return true;
143
+ }
144
+
145
+ /**
146
+ * Extract tag name from JSX opening element
147
+ */
148
+ function getTagName(node) {
149
+ try {
150
+ const { name } = node;
151
+ if (name.type === "JSXIdentifier") {
152
+ return name.name;
153
+ }
154
+ if (name.type === "JSXMemberExpression") {
155
+ return `${name.object.name}.${name.property.name}`;
156
+ }
157
+ } catch {
158
+ return null;
159
+ }
160
+ return null;
161
+ }
162
+
163
+ /**
164
+ * Detect if JSX element is inside a .map() call
165
+ */
166
+ function detectMapContext(node) {
167
+ try {
168
+ let current = node;
169
+ let depth = 0;
170
+
171
+ while (current && depth < 15) {
172
+ if (
173
+ current.type === "CallExpression" &&
174
+ current.callee?.type === "MemberExpression" &&
175
+ current.callee.property?.name === "map"
176
+ ) {
177
+ const arrayRef = current.callee.object?.name;
178
+ const callback = current.arguments?.[0];
179
+
180
+ if (arrayRef && callback?.type === "ArrowFunctionExpression") {
181
+ const indexArg = callback.params?.[1];
182
+ return {
183
+ arrayName: arrayRef,
184
+ indexName: indexArg?.type === "Identifier" ? indexArg.name : null,
185
+ };
186
+ }
187
+ }
188
+ current = current.parent;
189
+ depth++;
190
+ }
191
+ } catch {
192
+ // Ignore errors
193
+ }
194
+ return null;
195
+ }
196
+
197
+ /**
198
+ * Collect Next.js Image import aliases
199
+ */
200
+ function collectImageAliases(ast) {
201
+ const aliases = new Set();
202
+
203
+ try {
204
+ walkAst(ast, {
205
+ enter(node) {
206
+ if (
207
+ node.type === "ImportDeclaration" &&
208
+ node.source?.value === "next/image"
209
+ ) {
210
+ for (const spec of node.specifiers || []) {
211
+ if (spec.local?.name) {
212
+ aliases.add(spec.local.name);
213
+ }
214
+ }
215
+ }
216
+ },
217
+ });
218
+ } catch {
219
+ // Ignore errors
220
+ }
221
+
222
+ return aliases;
223
+ }
224
+
225
+ // ─────────────────────────────────────────────────────────────────────────────
226
+ // Main Loader
227
+ // ─────────────────────────────────────────────────────────────────────────────
228
+
229
+ /**
230
+ * FlexApp Element Tagger Loader
231
+ * SAFE: Returns original source on any error
232
+ */
233
+ function flexElementTagger(source, sourceMap) {
234
+ const callback = this.async();
235
+ // return callback(null, source, sourceMap);
236
+ // Early exit: missing dependencies
237
+ if (!parse || !MagicString) {
238
+ return callback(null, source, sourceMap);
239
+ }
240
+
241
+ // Early exit: no resource path or it's a directory
242
+ if (!this.resourcePath) {
243
+ return callback(null, source, sourceMap);
244
+ }
245
+
246
+ // Early exit: path doesn't look like a file (no extension)
247
+ const ext = path.extname(this.resourcePath);
248
+ if (!ext || !['.tsx', '.jsx', '.ts', '.js'].includes(ext)) {
249
+ return callback(null, source, sourceMap);
250
+ }
251
+
252
+ // Early exit: check if it's actually a file (not a directory)
253
+ try {
254
+ const stat = fs.statSync(this.resourcePath);
255
+ if (!stat.isFile()) {
256
+ return callback(null, source, sourceMap);
257
+ }
258
+ } catch {
259
+ // If stat fails, still try to process (might be virtual file)
260
+ }
261
+
262
+ // Early exit: node_modules
263
+ if (/node_modules/.test(this.resourcePath)) {
264
+ return callback(null, source, sourceMap);
265
+ }
266
+
267
+ // Early exit: no JSX detected
268
+ if (!hasJSX(source)) {
269
+ return callback(null, source, sourceMap);
270
+ }
271
+
272
+ try {
273
+ // Parse source
274
+ const ast = parse(source, {
275
+ sourceType: "module",
276
+ plugins: ["jsx", "typescript"],
277
+ errorRecovery: true, // Don't fail on syntax errors
278
+ });
279
+
280
+ if (!ast || !ast.program) {
281
+ return callback(null, source, sourceMap);
282
+ }
283
+
284
+ const magicStr = new MagicString(source);
285
+ const relativePath = path.relative(process.cwd(), this.resourcePath);
286
+ const imageAliases = collectImageAliases(ast);
287
+ let hasChanges = false;
288
+
289
+ // Add parent references
290
+ walkAst(ast.program, {
291
+ enter() { }, // Just to trigger parent ref addition
292
+ });
293
+
294
+ // Process JSX elements
295
+ walkAst(ast.program, {
296
+ enter(node) {
297
+ try {
298
+ if (node.type !== "JSXOpeningElement") return;
299
+
300
+ let tagName = getTagName(node);
301
+ if (!tagName) return;
302
+
303
+ // Check if already has data-flex-loc
304
+ const hasAttr = node.attributes?.some(
305
+ (attr) =>
306
+ attr.type === "JSXAttribute" &&
307
+ attr.name?.name === "data-flex-loc"
308
+ );
309
+ if (hasAttr) return;
310
+
311
+ // Normalize Next.js Image to img
312
+ const isNextImage = imageAliases.has(tagName.split(".")[0]);
313
+ const displayName = isNextImage ? "img" : tagName;
314
+
315
+ // Check if we should tag this element
316
+ if (!shouldInjectAttrs(tagName) && !isNextImage) return;
317
+
318
+ // Ensure we have location info
319
+ if (!node.loc?.start || !node.name?.end) return;
320
+
321
+ // Build location identifier
322
+ const { line, column } = node.loc.start;
323
+ let locId = `${relativePath}:${line}:${column}`;
324
+
325
+ // Add iteration context if inside .map()
326
+ const mapCtx = detectMapContext(node);
327
+ if (mapCtx?.arrayName) {
328
+ locId += `#${mapCtx.arrayName}`;
329
+ }
330
+
331
+ // Inject data-flex-iter for map index if available
332
+ if (mapCtx?.indexName) {
333
+ magicStr.appendLeft(
334
+ node.name.end,
335
+ ` data-flex-iter=${mapCtx.indexName}`
336
+ );
337
+ }
338
+
339
+ // Inject location and name attributes
340
+ magicStr.appendLeft(
341
+ node.name.end,
342
+ ` data-flex-loc="${locId}" data-flex-name="${displayName}"`
343
+ );
344
+
345
+ hasChanges = true;
346
+ } catch {
347
+ // Ignore errors for individual nodes
348
+ }
349
+ },
350
+ });
351
+
352
+ // Return original if no changes
353
+ if (!hasChanges) {
354
+ return callback(null, source, sourceMap);
355
+ }
356
+
357
+ // Generate output
358
+ const output = magicStr.toString();
359
+ const outputMap = magicStr.generateMap({ hires: true });
360
+
361
+ // Turbopack expects sourcemap as JSON string
362
+ callback(null, output, JSON.stringify(outputMap));
363
+
364
+ } catch (error) {
365
+ // On ANY error, return original source unchanged
366
+ console.warn("[flex-element-tagger] Skipping file due to error:", this.resourcePath);
367
+ callback(null, source, sourceMap);
368
+ }
369
+ }
370
+
371
+ module.exports = flexElementTagger;
372
+ module.exports.default = flexElementTagger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibexdotnew/inspector",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Visual inspection and element tagging for Next.js and Expo applications",
5
5
  "author": "Vibex",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "exports": {
29
29
  "./package.json": "./package.json",
30
- "./loader.js": "./next/element-tagger.js",
30
+ "./loader.js": "./next/element-tagger.cjs",
31
31
  "./transformer.js": "./expo/metro-transformer.cjs",
32
32
  "./next": {
33
33
  "types": "./dist/next/index.d.ts",