react-detective 0.0.0-stage → 0.1.0

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,105 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }require('./chunk-Z2Y65YOY.cjs');
2
+
3
+ // src/transform.ts
4
+ var _parser = require('@babel/parser');
5
+ var _traverse2 = require('@babel/traverse'); var _traverse3 = _interopRequireDefault(_traverse2);
6
+ var _magicstring = require('magic-string'); var _magicstring2 = _interopRequireDefault(_magicstring);
7
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
8
+ var traverse = _nullishCoalesce(_traverse3.default.default, () => ( _traverse3.default));
9
+ var LOC_ATTR = "data-rd-loc";
10
+ var COMP_ATTR = "data-rd-comp";
11
+ var FILE_RE = /\.[cm]?[jt]sx?$/;
12
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["html", "head", "meta", "title", "script", "style", "link", "base", "noscript"]);
13
+ function transformJsx(code, id, root) {
14
+ const filename = id.split("?")[0];
15
+ if (!FILE_RE.test(filename) || filename.includes("node_modules")) return null;
16
+ if (!code.includes("<")) return null;
17
+ const isTs = /\.[cm]?tsx?$/.test(filename);
18
+ if (isTs && !filename.endsWith("x")) return null;
19
+ let ast;
20
+ try {
21
+ ast = _parser.parse.call(void 0, code, {
22
+ sourceType: "module",
23
+ errorRecovery: true,
24
+ plugins: isTs ? ["jsx", "typescript", "decorators-legacy"] : ["jsx", "decorators-legacy"]
25
+ });
26
+ } catch (e) {
27
+ return null;
28
+ }
29
+ const rel = _path2.default.relative(root, filename).split(_path2.default.sep).join("/");
30
+ const s = new (0, _magicstring2.default)(code);
31
+ let changed = false;
32
+ traverse(ast, {
33
+ JSXOpeningElement(p) {
34
+ const name = p.node.name;
35
+ if (name.type !== "JSXIdentifier") return;
36
+ const tag = name.name;
37
+ if (!/^[a-z]/.test(tag) || SKIP_TAGS.has(tag)) return;
38
+ const hasAttr = p.node.attributes.some(
39
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === LOC_ATTR
40
+ );
41
+ if (hasAttr || name.end == null || !p.node.loc) return;
42
+ const comp = findComponentName(p, filename);
43
+ const { line, column } = p.node.loc.start;
44
+ let attrs = ` ${LOC_ATTR}="${rel}:${line}:${column + 1}"`;
45
+ if (comp) attrs += ` ${COMP_ATTR}="${comp}"`;
46
+ s.appendLeft(name.end, attrs);
47
+ changed = true;
48
+ }
49
+ });
50
+ if (!changed) return null;
51
+ return { code: s.toString(), map: s.generateMap({ source: filename, hires: true, includeContent: true }) };
52
+ }
53
+ function findComponentName(p, filename) {
54
+ let cur = p.getFunctionParent();
55
+ while (cur) {
56
+ const name = functionName(cur);
57
+ if (name && /^[A-Z]/.test(name)) return name;
58
+ if (!name && isDefaultExport(cur)) return componentNameFromFile(filename);
59
+ cur = _nullishCoalesce(_optionalChain([cur, 'access', _2 => _2.parentPath, 'optionalAccess', _3 => _3.getFunctionParent, 'call', _4 => _4()]), () => ( null));
60
+ }
61
+ return null;
62
+ }
63
+ function functionName(fn) {
64
+ const node = fn.node;
65
+ if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression") && node.id) return node.id.name;
66
+ if (node.type === "ClassMethod" || node.type === "ClassPrivateMethod") {
67
+ const cls = _optionalChain([fn, 'access', _5 => _5.parentPath, 'optionalAccess', _6 => _6.parentPath, 'optionalAccess', _7 => _7.node]);
68
+ if (cls && (cls.type === "ClassDeclaration" || cls.type === "ClassExpression") && cls.id) return cls.id.name;
69
+ }
70
+ let parent = fn.parentPath;
71
+ while (parent && parent.node.type === "CallExpression") parent = parent.parentPath;
72
+ if (_optionalChain([parent, 'optionalAccess', _8 => _8.node, 'access', _9 => _9.type]) === "VariableDeclarator" && parent.node.id.type === "Identifier") return parent.node.id.name;
73
+ return null;
74
+ }
75
+ function isDefaultExport(fn) {
76
+ let parent = fn.parentPath;
77
+ while (parent && parent.node.type === "CallExpression") parent = parent.parentPath;
78
+ return _optionalChain([parent, 'optionalAccess', _10 => _10.node, 'access', _11 => _11.type]) === "ExportDefaultDeclaration";
79
+ }
80
+ function componentNameFromFile(filename) {
81
+ let base = _path2.default.basename(filename).replace(FILE_RE, "");
82
+ if (["page", "layout", "index", "template", "loading", "error", "not-found"].includes(base)) {
83
+ const dir = _path2.default.basename(_path2.default.dirname(filename));
84
+ const label = base[0].toUpperCase() + base.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
85
+ return dir && !dir.startsWith("(") && dir !== "app" && dir !== "pages" ? `${pascal(dir)}${label}` : label;
86
+ }
87
+ return pascal(base);
88
+ }
89
+ function pascal(s) {
90
+ return s.replace(/[[\]]/g, "").replace(/(^|[-_.\s]+)(\w)/g, (_, __, c) => c.toUpperCase());
91
+ }
92
+
93
+ // src/loader.ts
94
+ function detectiveLoader(source, inputMap) {
95
+ const opts = _nullishCoalesce(_optionalChain([this, 'access', _12 => _12.getOptions, 'optionalCall', _13 => _13()]), () => ( {}));
96
+ const root = _nullishCoalesce(_nullishCoalesce(opts.root, () => ( this.rootContext)), () => ( process.cwd()));
97
+ const result = transformJsx(source, this.resourcePath, root);
98
+ if (!result) return this.callback(null, source, inputMap);
99
+ this.callback(null, result.code, result.map);
100
+ }
101
+
102
+
103
+ exports.default = detectiveLoader;
104
+
105
+ module.exports = exports.default;
package/dist/loader.js ADDED
@@ -0,0 +1,101 @@
1
+ // src/transform.ts
2
+ import { parse } from "@babel/parser";
3
+ import _traverse from "@babel/traverse";
4
+ import MagicString from "magic-string";
5
+ import path from "path";
6
+ var traverse = _traverse.default ?? _traverse;
7
+ var LOC_ATTR = "data-rd-loc";
8
+ var COMP_ATTR = "data-rd-comp";
9
+ var FILE_RE = /\.[cm]?[jt]sx?$/;
10
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["html", "head", "meta", "title", "script", "style", "link", "base", "noscript"]);
11
+ function transformJsx(code, id, root) {
12
+ const filename = id.split("?")[0];
13
+ if (!FILE_RE.test(filename) || filename.includes("node_modules")) return null;
14
+ if (!code.includes("<")) return null;
15
+ const isTs = /\.[cm]?tsx?$/.test(filename);
16
+ if (isTs && !filename.endsWith("x")) return null;
17
+ let ast;
18
+ try {
19
+ ast = parse(code, {
20
+ sourceType: "module",
21
+ errorRecovery: true,
22
+ plugins: isTs ? ["jsx", "typescript", "decorators-legacy"] : ["jsx", "decorators-legacy"]
23
+ });
24
+ } catch {
25
+ return null;
26
+ }
27
+ const rel = path.relative(root, filename).split(path.sep).join("/");
28
+ const s = new MagicString(code);
29
+ let changed = false;
30
+ traverse(ast, {
31
+ JSXOpeningElement(p) {
32
+ const name = p.node.name;
33
+ if (name.type !== "JSXIdentifier") return;
34
+ const tag = name.name;
35
+ if (!/^[a-z]/.test(tag) || SKIP_TAGS.has(tag)) return;
36
+ const hasAttr = p.node.attributes.some(
37
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === LOC_ATTR
38
+ );
39
+ if (hasAttr || name.end == null || !p.node.loc) return;
40
+ const comp = findComponentName(p, filename);
41
+ const { line, column } = p.node.loc.start;
42
+ let attrs = ` ${LOC_ATTR}="${rel}:${line}:${column + 1}"`;
43
+ if (comp) attrs += ` ${COMP_ATTR}="${comp}"`;
44
+ s.appendLeft(name.end, attrs);
45
+ changed = true;
46
+ }
47
+ });
48
+ if (!changed) return null;
49
+ return { code: s.toString(), map: s.generateMap({ source: filename, hires: true, includeContent: true }) };
50
+ }
51
+ function findComponentName(p, filename) {
52
+ let cur = p.getFunctionParent();
53
+ while (cur) {
54
+ const name = functionName(cur);
55
+ if (name && /^[A-Z]/.test(name)) return name;
56
+ if (!name && isDefaultExport(cur)) return componentNameFromFile(filename);
57
+ cur = cur.parentPath?.getFunctionParent() ?? null;
58
+ }
59
+ return null;
60
+ }
61
+ function functionName(fn) {
62
+ const node = fn.node;
63
+ if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression") && node.id) return node.id.name;
64
+ if (node.type === "ClassMethod" || node.type === "ClassPrivateMethod") {
65
+ const cls = fn.parentPath?.parentPath?.node;
66
+ if (cls && (cls.type === "ClassDeclaration" || cls.type === "ClassExpression") && cls.id) return cls.id.name;
67
+ }
68
+ let parent = fn.parentPath;
69
+ while (parent && parent.node.type === "CallExpression") parent = parent.parentPath;
70
+ if (parent?.node.type === "VariableDeclarator" && parent.node.id.type === "Identifier") return parent.node.id.name;
71
+ return null;
72
+ }
73
+ function isDefaultExport(fn) {
74
+ let parent = fn.parentPath;
75
+ while (parent && parent.node.type === "CallExpression") parent = parent.parentPath;
76
+ return parent?.node.type === "ExportDefaultDeclaration";
77
+ }
78
+ function componentNameFromFile(filename) {
79
+ let base = path.basename(filename).replace(FILE_RE, "");
80
+ if (["page", "layout", "index", "template", "loading", "error", "not-found"].includes(base)) {
81
+ const dir = path.basename(path.dirname(filename));
82
+ const label = base[0].toUpperCase() + base.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
83
+ return dir && !dir.startsWith("(") && dir !== "app" && dir !== "pages" ? `${pascal(dir)}${label}` : label;
84
+ }
85
+ return pascal(base);
86
+ }
87
+ function pascal(s) {
88
+ return s.replace(/[[\]]/g, "").replace(/(^|[-_.\s]+)(\w)/g, (_, __, c) => c.toUpperCase());
89
+ }
90
+
91
+ // src/loader.ts
92
+ function detectiveLoader(source, inputMap) {
93
+ const opts = this.getOptions?.() ?? {};
94
+ const root = opts.root ?? this.rootContext ?? process.cwd();
95
+ const result = transformJsx(source, this.resourcePath, root);
96
+ if (!result) return this.callback(null, source, inputMap);
97
+ this.callback(null, result.code, result.map);
98
+ }
99
+ export {
100
+ detectiveLoader as default
101
+ };
package/dist/next.cjs ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
+
3
+ var _chunkZ2Y65YOYcjs = require('./chunk-Z2Y65YOY.cjs');
4
+
5
+ // src/next.ts
6
+ var _module = require('module');
7
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
8
+ var DEFAULT_PORT = 5899;
9
+ var PHASE_DEVELOPMENT_SERVER = "phase-development-server";
10
+ var JSX_FILES = ["*.jsx", "*.tsx", "*.js"];
11
+ function withDetective(nextConfig = {}, options = {}) {
12
+ return async (phase, ctx) => {
13
+ const config = typeof nextConfig === "function" ? await nextConfig(phase, ctx) : { ...nextConfig };
14
+ if (phase !== PHASE_DEVELOPMENT_SERVER) return config;
15
+ const root = process.cwd();
16
+ const loader = resolveLoader();
17
+ const loaderEntry = { loader, options: { root } };
18
+ const turbopack = _nullishCoalesce(config.turbopack, () => ( {}));
19
+ const rules = { ..._nullishCoalesce(turbopack.rules, () => ( {})) };
20
+ for (const glob of JSX_FILES) {
21
+ if (rules[glob]) continue;
22
+ rules[glob] = { condition: { not: "foreign" }, loaders: [loaderEntry] };
23
+ }
24
+ config.turbopack = { ...turbopack, rules };
25
+ const userWebpack = config.webpack;
26
+ config.webpack = (wpConfig, wpCtx) => {
27
+ const out = userWebpack ? userWebpack(wpConfig, wpCtx) : wpConfig;
28
+ out.module.rules.unshift({
29
+ test: /\.[cm]?[jt]sx?$/,
30
+ exclude: /node_modules/,
31
+ enforce: "pre",
32
+ use: [loaderEntry]
33
+ });
34
+ return out;
35
+ };
36
+ const port = _nullishCoalesce(options.port, () => ( DEFAULT_PORT));
37
+ config.env = {
38
+ ...config.env,
39
+ REACT_DETECTIVE_PORT: String(port),
40
+ REACT_DETECTIVE_LAUNCHER: options.showLauncher === false ? "0" : "1"
41
+ };
42
+ const { startDetectiveServer } = await Promise.resolve().then(() => _interopRequireWildcard(require("react-detective/server")));
43
+ await startDetectiveServer({ root, port, model: options.model, maxTurns: options.maxTurns, storeDir: options.storeDir });
44
+ return config;
45
+ };
46
+ }
47
+ function resolveLoader() {
48
+ try {
49
+ return _module.createRequire.call(void 0, _path2.default.join(process.cwd(), "package.json")).resolve("react-detective/next/loader");
50
+ } catch (e) {
51
+ return _module.createRequire.call(void 0, _chunkZ2Y65YOYcjs.importMetaUrl).resolve("react-detective/next/loader");
52
+ }
53
+ }
54
+ var next_default = withDetective;
55
+
56
+
57
+
58
+ exports.default = next_default; exports.withDetective = withDetective;
@@ -0,0 +1,24 @@
1
+ interface DetectiveNextOptions {
2
+ /** Port for the local explain server. Default 5899. */
3
+ port?: number;
4
+ /** Model for explanations: an alias (`"sonnet"`, `"haiku"`, `"opus"`) or a full model ID. Default `"sonnet"`. */
5
+ model?: string;
6
+ maxTurns?: number;
7
+ /** Knowledge folder inside the project root. Default `.react-detective`. */
8
+ storeDir?: string;
9
+ /** Show the Detective badge in the corner. Default true. */
10
+ showLauncher?: boolean;
11
+ }
12
+ type NextConfig = Record<string, any>;
13
+ type NextConfigInput = NextConfig | ((phase: string, ctx: any) => NextConfig | Promise<NextConfig>);
14
+ /**
15
+ * Wraps your Next.js config. In `next dev` it:
16
+ * - adds a loader (webpack and Turbopack) that tags DOM elements with their source location
17
+ * - starts the local explain server on 127.0.0.1
18
+ * In every other phase (build, start) your config is returned untouched.
19
+ */
20
+ declare function withDetective(nextConfig?: NextConfigInput, options?: DetectiveNextOptions): (phase: string, ctx: any) => Promise<NextConfig>;
21
+
22
+ // @ts-ignore
23
+ export = withDetective;
24
+ export { type DetectiveNextOptions, withDetective };
package/dist/next.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ interface DetectiveNextOptions {
2
+ /** Port for the local explain server. Default 5899. */
3
+ port?: number;
4
+ /** Model for explanations: an alias (`"sonnet"`, `"haiku"`, `"opus"`) or a full model ID. Default `"sonnet"`. */
5
+ model?: string;
6
+ maxTurns?: number;
7
+ /** Knowledge folder inside the project root. Default `.react-detective`. */
8
+ storeDir?: string;
9
+ /** Show the Detective badge in the corner. Default true. */
10
+ showLauncher?: boolean;
11
+ }
12
+ type NextConfig = Record<string, any>;
13
+ type NextConfigInput = NextConfig | ((phase: string, ctx: any) => NextConfig | Promise<NextConfig>);
14
+ /**
15
+ * Wraps your Next.js config. In `next dev` it:
16
+ * - adds a loader (webpack and Turbopack) that tags DOM elements with their source location
17
+ * - starts the local explain server on 127.0.0.1
18
+ * In every other phase (build, start) your config is returned untouched.
19
+ */
20
+ declare function withDetective(nextConfig?: NextConfigInput, options?: DetectiveNextOptions): (phase: string, ctx: any) => Promise<NextConfig>;
21
+
22
+ export { type DetectiveNextOptions, withDetective as default, withDetective };
package/dist/next.js ADDED
@@ -0,0 +1,54 @@
1
+ // src/next.ts
2
+ import { createRequire } from "module";
3
+ import path from "path";
4
+ var DEFAULT_PORT = 5899;
5
+ var PHASE_DEVELOPMENT_SERVER = "phase-development-server";
6
+ var JSX_FILES = ["*.jsx", "*.tsx", "*.js"];
7
+ function withDetective(nextConfig = {}, options = {}) {
8
+ return async (phase, ctx) => {
9
+ const config = typeof nextConfig === "function" ? await nextConfig(phase, ctx) : { ...nextConfig };
10
+ if (phase !== PHASE_DEVELOPMENT_SERVER) return config;
11
+ const root = process.cwd();
12
+ const loader = resolveLoader();
13
+ const loaderEntry = { loader, options: { root } };
14
+ const turbopack = config.turbopack ?? {};
15
+ const rules = { ...turbopack.rules ?? {} };
16
+ for (const glob of JSX_FILES) {
17
+ if (rules[glob]) continue;
18
+ rules[glob] = { condition: { not: "foreign" }, loaders: [loaderEntry] };
19
+ }
20
+ config.turbopack = { ...turbopack, rules };
21
+ const userWebpack = config.webpack;
22
+ config.webpack = (wpConfig, wpCtx) => {
23
+ const out = userWebpack ? userWebpack(wpConfig, wpCtx) : wpConfig;
24
+ out.module.rules.unshift({
25
+ test: /\.[cm]?[jt]sx?$/,
26
+ exclude: /node_modules/,
27
+ enforce: "pre",
28
+ use: [loaderEntry]
29
+ });
30
+ return out;
31
+ };
32
+ const port = options.port ?? DEFAULT_PORT;
33
+ config.env = {
34
+ ...config.env,
35
+ REACT_DETECTIVE_PORT: String(port),
36
+ REACT_DETECTIVE_LAUNCHER: options.showLauncher === false ? "0" : "1"
37
+ };
38
+ const { startDetectiveServer } = await import("react-detective/server");
39
+ await startDetectiveServer({ root, port, model: options.model, maxTurns: options.maxTurns, storeDir: options.storeDir });
40
+ return config;
41
+ };
42
+ }
43
+ function resolveLoader() {
44
+ try {
45
+ return createRequire(path.join(process.cwd(), "package.json")).resolve("react-detective/next/loader");
46
+ } catch {
47
+ return createRequire(import.meta.url).resolve("react-detective/next/loader");
48
+ }
49
+ }
50
+ var next_default = withDetective;
51
+ export {
52
+ next_default as default,
53
+ withDetective
54
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Mounts the inspector overlay. Renders nothing and loads nothing outside
3
+ * development, so it is safe to leave in your root layout. It takes no props:
4
+ * the port and badge setting come from `withDetective(config, options)`.
5
+ *
6
+ * // app/layout.tsx
7
+ * <body>{children}<Detective /></body>
8
+ */
9
+ declare function Detective(): null;
10
+
11
+ export { Detective, Detective as default };
package/dist/react.js ADDED
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ // src/react.tsx
4
+ import { useEffect } from "react";
5
+ function Detective() {
6
+ useEffect(() => {
7
+ if (process.env.NODE_ENV !== "development") return;
8
+ const port = process.env.REACT_DETECTIVE_PORT || "5899";
9
+ const showLauncher = process.env.REACT_DETECTIVE_LAUNCHER !== "0";
10
+ let cleanup;
11
+ let cancelled = false;
12
+ import("react-detective/client").then(({ init }) => {
13
+ if (!cancelled) cleanup = init({ endpoint: `http://127.0.0.1:${port}/__detective`, showLauncher });
14
+ });
15
+ return () => {
16
+ cancelled = true;
17
+ cleanup?.();
18
+ };
19
+ }, []);
20
+ return null;
21
+ }
22
+ var react_default = Detective;
23
+ export {
24
+ Detective,
25
+ react_default as default
26
+ };