aipeek 0.2.7 → 0.2.9
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.
- package/README.md +92 -18
- package/dist/{chunk-STYCUT23.cjs → chunk-7ALIH3JX.cjs} +622 -46
- package/dist/{chunk-37VLLZIU.js → chunk-7NJSWR7E.js} +621 -45
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +2 -2
- package/dist/plugin.js +1 -1
- package/package.json +3 -1
- package/src/babel/line-profiler.ts +190 -0
- package/src/client/client-patch.ts +332 -2
- package/src/client/client.ts +259 -44
- package/src/core/action.ts +199 -22
- package/src/core/compact.ts +2 -0
- package/src/core/detail.ts +3 -1
- package/src/core/diff.ts +55 -1
- package/src/core/emit.ts +14 -2
- package/src/core/perf.ts +250 -0
- package/src/core/types.ts +73 -0
- package/src/core/util.ts +115 -0
- package/src/server/plugin.ts +463 -52
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunk7ALIH3JXcjs = require('./chunk-7ALIH3JX.cjs');
|
|
9
9
|
require('./chunk-Z2Y65YOY.cjs');
|
|
10
10
|
|
|
11
11
|
|
|
@@ -14,4 +14,4 @@ require('./chunk-Z2Y65YOY.cjs');
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
exports.aipeekPlugin =
|
|
17
|
+
exports.aipeekPlugin = _chunk7ALIH3JXcjs.aipeekPlugin; exports.check = _chunk7ALIH3JXcjs.check; exports.diffState = _chunk7ALIH3JXcjs.diffState; exports.emitCheck = _chunk7ALIH3JXcjs.emitCheck; exports.emitDiff = _chunk7ALIH3JXcjs.emitDiff; exports.emitSummary = _chunk7ALIH3JXcjs.emitSummary;
|
package/dist/index.d.cts
CHANGED
|
@@ -28,15 +28,57 @@ interface ErrorEntry {
|
|
|
28
28
|
line?: number;
|
|
29
29
|
column?: number;
|
|
30
30
|
}
|
|
31
|
+
interface ActionEntry {
|
|
32
|
+
type: string;
|
|
33
|
+
target: string;
|
|
34
|
+
value?: string;
|
|
35
|
+
trusted: boolean;
|
|
36
|
+
view?: string;
|
|
37
|
+
modal?: string;
|
|
38
|
+
focus?: string;
|
|
39
|
+
ts: number;
|
|
40
|
+
tab?: string;
|
|
41
|
+
}
|
|
31
42
|
interface RawState {
|
|
43
|
+
tab?: string;
|
|
32
44
|
url: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
visible?: boolean;
|
|
33
47
|
ui: string;
|
|
34
48
|
console: LogEntry[];
|
|
35
49
|
network: NetworkRequest[];
|
|
36
50
|
errors: ErrorEntry[];
|
|
51
|
+
actions?: ActionEntry[];
|
|
37
52
|
state: Record<string, unknown>;
|
|
53
|
+
performance?: PerformanceData;
|
|
38
54
|
timestamp: number;
|
|
39
55
|
}
|
|
56
|
+
interface PerfStat {
|
|
57
|
+
total: number;
|
|
58
|
+
n: number;
|
|
59
|
+
max: number;
|
|
60
|
+
}
|
|
61
|
+
interface PerfBucketData {
|
|
62
|
+
name: string;
|
|
63
|
+
components: Record<string, PerfStat>;
|
|
64
|
+
frames: {
|
|
65
|
+
total: number;
|
|
66
|
+
long: number;
|
|
67
|
+
max: number;
|
|
68
|
+
samples: number[];
|
|
69
|
+
};
|
|
70
|
+
lines: Record<string, PerfStat>;
|
|
71
|
+
}
|
|
72
|
+
interface PerformanceData {
|
|
73
|
+
windowMs: number;
|
|
74
|
+
hiddenFrames: number;
|
|
75
|
+
mobxPatched: boolean;
|
|
76
|
+
buckets: PerfBucketData[];
|
|
77
|
+
longtasks: {
|
|
78
|
+
count: number;
|
|
79
|
+
max: number;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
40
82
|
interface CompactState {
|
|
41
83
|
url: string;
|
|
42
84
|
ui: string;
|
|
@@ -44,6 +86,7 @@ interface CompactState {
|
|
|
44
86
|
network: string;
|
|
45
87
|
errors: string;
|
|
46
88
|
state: string;
|
|
89
|
+
performance?: string;
|
|
47
90
|
timestamp: number;
|
|
48
91
|
counts: {
|
|
49
92
|
console: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,15 +28,57 @@ interface ErrorEntry {
|
|
|
28
28
|
line?: number;
|
|
29
29
|
column?: number;
|
|
30
30
|
}
|
|
31
|
+
interface ActionEntry {
|
|
32
|
+
type: string;
|
|
33
|
+
target: string;
|
|
34
|
+
value?: string;
|
|
35
|
+
trusted: boolean;
|
|
36
|
+
view?: string;
|
|
37
|
+
modal?: string;
|
|
38
|
+
focus?: string;
|
|
39
|
+
ts: number;
|
|
40
|
+
tab?: string;
|
|
41
|
+
}
|
|
31
42
|
interface RawState {
|
|
43
|
+
tab?: string;
|
|
32
44
|
url: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
visible?: boolean;
|
|
33
47
|
ui: string;
|
|
34
48
|
console: LogEntry[];
|
|
35
49
|
network: NetworkRequest[];
|
|
36
50
|
errors: ErrorEntry[];
|
|
51
|
+
actions?: ActionEntry[];
|
|
37
52
|
state: Record<string, unknown>;
|
|
53
|
+
performance?: PerformanceData;
|
|
38
54
|
timestamp: number;
|
|
39
55
|
}
|
|
56
|
+
interface PerfStat {
|
|
57
|
+
total: number;
|
|
58
|
+
n: number;
|
|
59
|
+
max: number;
|
|
60
|
+
}
|
|
61
|
+
interface PerfBucketData {
|
|
62
|
+
name: string;
|
|
63
|
+
components: Record<string, PerfStat>;
|
|
64
|
+
frames: {
|
|
65
|
+
total: number;
|
|
66
|
+
long: number;
|
|
67
|
+
max: number;
|
|
68
|
+
samples: number[];
|
|
69
|
+
};
|
|
70
|
+
lines: Record<string, PerfStat>;
|
|
71
|
+
}
|
|
72
|
+
interface PerformanceData {
|
|
73
|
+
windowMs: number;
|
|
74
|
+
hiddenFrames: number;
|
|
75
|
+
mobxPatched: boolean;
|
|
76
|
+
buckets: PerfBucketData[];
|
|
77
|
+
longtasks: {
|
|
78
|
+
count: number;
|
|
79
|
+
max: number;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
40
82
|
interface CompactState {
|
|
41
83
|
url: string;
|
|
42
84
|
ui: string;
|
|
@@ -44,6 +86,7 @@ interface CompactState {
|
|
|
44
86
|
network: string;
|
|
45
87
|
errors: string;
|
|
46
88
|
state: string;
|
|
89
|
+
performance?: string;
|
|
47
90
|
timestamp: number;
|
|
48
91
|
counts: {
|
|
49
92
|
console: number;
|
package/dist/index.js
CHANGED
package/dist/plugin.cjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunk7ALIH3JXcjs = require('./chunk-7ALIH3JX.cjs');
|
|
8
8
|
require('./chunk-Z2Y65YOY.cjs');
|
|
9
9
|
|
|
10
10
|
|
|
@@ -12,4 +12,4 @@ require('./chunk-Z2Y65YOY.cjs');
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
exports.END_TAG =
|
|
15
|
+
exports.END_TAG = _chunk7ALIH3JXcjs.END_TAG; exports.START_TAG = _chunk7ALIH3JXcjs.START_TAG; exports.aipeekPlugin = _chunk7ALIH3JXcjs.aipeekPlugin; exports.injectClaudeMd = _chunk7ALIH3JXcjs.injectClaudeMd; exports.renderClaudeMd = _chunk7ALIH3JXcjs.renderClaudeMd;
|
package/dist/plugin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aipeek",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Gives AI a peek into your running browser app — UI tree, console, network, errors, state",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
+
"prebuild": "node --import ../../node_modules/tsx/dist/loader.mjs ../../packages/repodex/src/cli.ts",
|
|
25
26
|
"build": "tsup",
|
|
26
27
|
"test": "vitest run",
|
|
27
28
|
"test:watch": "vitest",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"jsdom": "^29.1.1",
|
|
46
|
+
"repodex": "^0.3.7",
|
|
45
47
|
"teact": "file:../teact",
|
|
46
48
|
"tsup": "^8.4.0",
|
|
47
49
|
"typescript": "~5.7.2",
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { PluginObj, NodePath } from '@babel/core'
|
|
2
|
+
import type { Statement, VariableDeclaration, ReturnStatement, ExpressionStatement } from '@babel/types'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Babel plugin: wrap statements with __line(label, () => ...) for line-level profiling.
|
|
8
|
+
*
|
|
9
|
+
* Only wraps statements that have measurable cost:
|
|
10
|
+
* - VariableDeclaration with initializer
|
|
11
|
+
* - ExpressionStatement (function calls, assignments)
|
|
12
|
+
* - ReturnStatement with argument
|
|
13
|
+
*
|
|
14
|
+
* Skips:
|
|
15
|
+
* - import/export (module syntax)
|
|
16
|
+
* - control flow (if/for/while/switch) — their bodies get wrapped, not the structure
|
|
17
|
+
* - empty statements, type declarations
|
|
18
|
+
*/
|
|
19
|
+
export function lineProfilerPlugin(): PluginObj {
|
|
20
|
+
return {
|
|
21
|
+
name: 'aipeek-line-profiler',
|
|
22
|
+
visitor: {
|
|
23
|
+
VariableDeclaration(nodePath: NodePath<VariableDeclaration>, state) {
|
|
24
|
+
if (shouldSkip(nodePath, state)) return
|
|
25
|
+
const decl = nodePath.node.declarations[0]
|
|
26
|
+
if (!decl?.init) return // skip `let x;`
|
|
27
|
+
if (hasAwaitOrYield(decl.init)) return // wrapping in arrow fn breaks async semantics
|
|
28
|
+
|
|
29
|
+
const label = makeLabel(nodePath, state, decl.id.type === 'Identifier' ? decl.id.name : undefined)
|
|
30
|
+
const wrapped = wrapExpr(decl.init, label)
|
|
31
|
+
decl.init = wrapped
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
ExpressionStatement(nodePath: NodePath<ExpressionStatement>, state) {
|
|
35
|
+
if (shouldSkip(nodePath, state)) return
|
|
36
|
+
if (hasAwaitOrYield(nodePath.node.expression)) return
|
|
37
|
+
|
|
38
|
+
const label = makeLabel(nodePath, state)
|
|
39
|
+
const wrapped = wrapExpr(nodePath.node.expression, label)
|
|
40
|
+
nodePath.node.expression = wrapped
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
ReturnStatement(nodePath: NodePath<ReturnStatement>, state) {
|
|
44
|
+
if (shouldSkip(nodePath, state)) return
|
|
45
|
+
if (!nodePath.node.argument) return // skip `return;`
|
|
46
|
+
if (hasAwaitOrYield(nodePath.node.argument)) return
|
|
47
|
+
|
|
48
|
+
const label = makeLabel(nodePath, state)
|
|
49
|
+
const wrapped = wrapExpr(nodePath.node.argument, label)
|
|
50
|
+
nodePath.node.argument = wrapped
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function shouldSkip(nodePath: NodePath<Statement>, state: any): boolean {
|
|
57
|
+
const filename: string = state.file?.opts?.filename ?? ''
|
|
58
|
+
// Skip node_modules — third-party cost isn't the app's to fix.
|
|
59
|
+
if (filename.includes('node_modules')) return true
|
|
60
|
+
// Skip aipeek's own source: the profiler reads state by walking the React fiber tree
|
|
61
|
+
// (client.ts walkFiber/walkChildren) on every /ui|/dom|/screen. That's MEASUREMENT cost,
|
|
62
|
+
// not the app's — instrumenting it lets the observer dominate its own report (290ms self-top).
|
|
63
|
+
if (filename.includes('packages/aipeek/')) return true
|
|
64
|
+
|
|
65
|
+
// Skip module syntax — import/export carry no runtime cost to measure
|
|
66
|
+
const node = nodePath.node
|
|
67
|
+
if (t.isImportDeclaration(node)) return true
|
|
68
|
+
if (t.isExportNamedDeclaration(node)) return true
|
|
69
|
+
if (t.isExportDefaultDeclaration(node)) return true
|
|
70
|
+
if (t.isExportAllDeclaration(node)) return true
|
|
71
|
+
|
|
72
|
+
// Skip if already wrapped
|
|
73
|
+
if (t.isExpressionStatement(node) && t.isCallExpression(node.expression)) {
|
|
74
|
+
const callee = node.expression.callee
|
|
75
|
+
if (t.isIdentifier(callee) && callee.name === '__line') return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function makeLabel(nodePath: NodePath<Statement>, state: any, varName?: string): string {
|
|
82
|
+
const filename = state.file.opts.filename ?? 'unknown'
|
|
83
|
+
const basename = path.basename(filename)
|
|
84
|
+
const line = resolveLine(nodePath)
|
|
85
|
+
|
|
86
|
+
if (varName) {
|
|
87
|
+
return `${basename}:${line}:${varName}`
|
|
88
|
+
}
|
|
89
|
+
return `${basename}:${line}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Resolve the source line a wrapped statement should point to.
|
|
93
|
+
//
|
|
94
|
+
// A node synthesized by an upstream transform (e.g. mobx-react-observer rewriting
|
|
95
|
+
// `function Root(){…}` → `const Root = observer(function Root(){…})`) carries NO loc on the
|
|
96
|
+
// outer declaration. Two recovery moves, in priority order:
|
|
97
|
+
//
|
|
98
|
+
// 1. descend — the synthesized wrapper still contains the user's ORIGINAL node
|
|
99
|
+
// (`function Root()` keeps its loc 59). The nearest loc-bearing descendant is the most
|
|
100
|
+
// specific real source location, so prefer it.
|
|
101
|
+
// 2. climb — only if the whole subtree is synthetic (no loc anywhere inside) do we fall back
|
|
102
|
+
// to the nearest ancestor's loc.
|
|
103
|
+
//
|
|
104
|
+
// Without this, observer-wrapped components — the ones most worth profiling — collapse onto
|
|
105
|
+
// `<file>:1` (climbing all the way to Program) or `<file>:0` (no loc at all).
|
|
106
|
+
function resolveLine(nodePath: NodePath<Statement>): number {
|
|
107
|
+
if (nodePath.node.loc) return nodePath.node.loc.start.line
|
|
108
|
+
|
|
109
|
+
const descend = firstLocLine(nodePath.node)
|
|
110
|
+
if (descend != null) return descend
|
|
111
|
+
|
|
112
|
+
let p: NodePath | null = nodePath.parentPath
|
|
113
|
+
while (p) {
|
|
114
|
+
if (p.node.loc) return p.node.loc.start.line
|
|
115
|
+
p = p.parentPath
|
|
116
|
+
}
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Pre-order DFS for the first node carrying a loc. Skips the root itself (caller already
|
|
121
|
+
// checked it). Source-order traversal so the earliest real line wins.
|
|
122
|
+
function firstLocLine(node: t.Node): number | null {
|
|
123
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
124
|
+
const child = (node as any)[key]
|
|
125
|
+
const children = Array.isArray(child) ? child : [child]
|
|
126
|
+
for (const c of children) {
|
|
127
|
+
if (!c || typeof c.type !== 'string') continue
|
|
128
|
+
if (c.loc) return c.loc.start.line
|
|
129
|
+
const deeper = firstLocLine(c)
|
|
130
|
+
if (deeper != null) return deeper
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Emit: (globalThis.__line || ((_l, f) => f()))(label, () => expr)
|
|
137
|
+
// NOT a bare `__line(...)`: instrumented modules eval their top-level statements at IMPORT time,
|
|
138
|
+
// before client-patch installs window.__line — a bare identifier then throws ReferenceError and
|
|
139
|
+
// kills the app. globalThis.__line is a member access (→ undefined, not a throw); the || fallback
|
|
140
|
+
// just runs the original expr, so the instrumentation is inert until the runtime is present.
|
|
141
|
+
function wrapExpr(expr: t.Expression, label: string): t.CallExpression {
|
|
142
|
+
const runtime = t.logicalExpression(
|
|
143
|
+
'||',
|
|
144
|
+
t.memberExpression(t.identifier('globalThis'), t.identifier('__line')),
|
|
145
|
+
t.arrowFunctionExpression(
|
|
146
|
+
[t.identifier('_l'), t.identifier('f')],
|
|
147
|
+
t.callExpression(t.identifier('f'), []),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
return t.callExpression(
|
|
151
|
+
runtime,
|
|
152
|
+
[
|
|
153
|
+
t.stringLiteral(label),
|
|
154
|
+
t.arrowFunctionExpression([], expr),
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// True if expr contains an await/yield at THIS function level (not inside a nested function).
|
|
160
|
+
// Wrapping such an expr in `() => expr` would either be a syntax error (await in non-async
|
|
161
|
+
// arrow) or silently change semantics (the arrow returns a Promise instead of awaiting).
|
|
162
|
+
// Nested functions are their own scope, so we stop recursing at function boundaries.
|
|
163
|
+
function hasAwaitOrYield(node: t.Node | null | undefined): boolean {
|
|
164
|
+
if (!node || typeof node !== 'object') return false
|
|
165
|
+
if (t.isAwaitExpression(node) || t.isYieldExpression(node)) return true
|
|
166
|
+
// Don't descend into nested functions — their await/yield belongs to their own scope.
|
|
167
|
+
if (
|
|
168
|
+
t.isFunctionExpression(node)
|
|
169
|
+
|| t.isArrowFunctionExpression(node)
|
|
170
|
+
|| t.isFunctionDeclaration(node)
|
|
171
|
+
|| t.isObjectMethod(node)
|
|
172
|
+
|| t.isClassMethod(node)
|
|
173
|
+
) return false
|
|
174
|
+
|
|
175
|
+
for (const key of Object.keys(node)) {
|
|
176
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'leadingComments' || key === 'trailingComments') continue
|
|
177
|
+
const child = (node as any)[key]
|
|
178
|
+
if (Array.isArray(child)) {
|
|
179
|
+
for (const c of child) {
|
|
180
|
+
if (c && typeof c.type === 'string' && hasAwaitOrYield(c)) return true
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (child && typeof child.type === 'string') {
|
|
184
|
+
if (hasAwaitOrYield(child)) return true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default lineProfilerPlugin
|