@test328932/test328933 1.0.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,248 @@
1
+ import { parse } from '@babel/parser'
2
+ import traverse from '@babel/traverse'
3
+ import generate from '@babel/generator'
4
+ import * as t from '@babel/types'
5
+
6
+ const traverseAst = traverse.default ?? traverse
7
+ const generateCode = generate.default ?? generate
8
+
9
+ function isComponentName(name) {
10
+ return typeof name === 'string' && /^[A-Z]/.test(name)
11
+ }
12
+
13
+ function isTopLevelDeclaration(path) {
14
+ const parent = path.parentPath
15
+ if (!parent) return false
16
+ if (parent.isProgram()) return true
17
+ if (parent.isExportNamedDeclaration() || parent.isExportDefaultDeclaration()) {
18
+ return parent.parentPath?.isProgram() ?? false
19
+ }
20
+ return false
21
+ }
22
+
23
+ function ensureBlockBody(functionNode) {
24
+ if (t.isBlockStatement(functionNode.body)) return
25
+ functionNode.body = t.blockStatement([t.returnStatement(functionNode.body)])
26
+ }
27
+
28
+ function isUseStateCall(callee) {
29
+ if (t.isIdentifier(callee, { name: 'useState' })) return true
30
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property, { name: 'useState' })) {
31
+ return true
32
+ }
33
+ return false
34
+ }
35
+
36
+ function collectPatternIdentifiers(pattern, output) {
37
+ if (!pattern) return
38
+
39
+ if (t.isIdentifier(pattern)) {
40
+ output.push(pattern.name)
41
+ return
42
+ }
43
+
44
+ if (t.isAssignmentPattern(pattern)) {
45
+ collectPatternIdentifiers(pattern.left, output)
46
+ return
47
+ }
48
+
49
+ if (t.isRestElement(pattern)) {
50
+ collectPatternIdentifiers(pattern.argument, output)
51
+ return
52
+ }
53
+
54
+ if (t.isObjectPattern(pattern)) {
55
+ pattern.properties.forEach(prop => {
56
+ if (t.isRestElement(prop)) {
57
+ collectPatternIdentifiers(prop.argument, output)
58
+ return
59
+ }
60
+ if (t.isObjectProperty(prop)) {
61
+ collectPatternIdentifiers(prop.value, output)
62
+ }
63
+ })
64
+ return
65
+ }
66
+
67
+ if (t.isArrayPattern(pattern)) {
68
+ pattern.elements.forEach(element => {
69
+ collectPatternIdentifiers(element, output)
70
+ })
71
+ }
72
+ }
73
+
74
+ function buildPropsExpression(paramNode) {
75
+ if (!paramNode) return t.nullLiteral()
76
+
77
+ if (t.isIdentifier(paramNode)) {
78
+ return t.identifier(paramNode.name)
79
+ }
80
+
81
+ const names = []
82
+ collectPatternIdentifiers(paramNode, names)
83
+ const uniqueNames = [...new Set(names)]
84
+
85
+ return t.objectExpression(
86
+ uniqueNames.map(name => t.objectProperty(t.identifier(name), t.identifier(name), false, true)),
87
+ )
88
+ }
89
+
90
+ function collectStateNames(functionPath) {
91
+ const stateNames = []
92
+
93
+ functionPath.get('body').traverse({
94
+ VariableDeclarator(variablePath) {
95
+ if (variablePath.getFunctionParent() !== functionPath) return
96
+
97
+ const { id, init } = variablePath.node
98
+ if (!t.isArrayPattern(id) || !t.isCallExpression(init)) return
99
+ if (!isUseStateCall(init.callee)) return
100
+
101
+ const firstElement = id.elements[0]
102
+ if (t.isIdentifier(firstElement)) {
103
+ stateNames.push(firstElement.name)
104
+ }
105
+ },
106
+ })
107
+
108
+ return [...new Set(stateNames)]
109
+ }
110
+
111
+ function buildInspectorTextarea(snapshotExpression) {
112
+ const textareaStyle = t.objectExpression([
113
+ t.objectProperty(t.identifier('width'), t.stringLiteral('100%')),
114
+ t.objectProperty(t.identifier('minHeight'), t.stringLiteral('130px')),
115
+ t.objectProperty(t.identifier('marginTop'), t.stringLiteral('10px')),
116
+ t.objectProperty(t.identifier('padding'), t.stringLiteral('8px')),
117
+ t.objectProperty(t.identifier('fontFamily'), t.stringLiteral('ui-monospace, SFMono-Regular, Menlo, monospace')),
118
+ t.objectProperty(t.identifier('fontSize'), t.stringLiteral('12px')),
119
+ t.objectProperty(t.identifier('whiteSpace'), t.stringLiteral('pre')),
120
+ t.objectProperty(t.identifier('background'), t.stringLiteral('#fbfbff')),
121
+ t.objectProperty(t.identifier('border'), t.stringLiteral('1px solid #d3d8eb')),
122
+ t.objectProperty(t.identifier('borderRadius'), t.stringLiteral('6px')),
123
+ ])
124
+
125
+ return t.jsxElement(
126
+ t.jsxOpeningElement(
127
+ t.jsxIdentifier('textarea'),
128
+ [
129
+ t.jsxAttribute(t.jsxIdentifier('readOnly'), null),
130
+ t.jsxAttribute(
131
+ t.jsxIdentifier('value'),
132
+ t.jsxExpressionContainer(
133
+ t.callExpression(
134
+ t.memberExpression(t.identifier('JSON'), t.identifier('stringify')),
135
+ [t.cloneNode(snapshotExpression), t.nullLiteral(), t.numericLiteral(2)],
136
+ ),
137
+ ),
138
+ ),
139
+ t.jsxAttribute(
140
+ t.jsxIdentifier('style'),
141
+ t.jsxExpressionContainer(textareaStyle),
142
+ ),
143
+ ],
144
+ false,
145
+ ),
146
+ t.jsxClosingElement(t.jsxIdentifier('textarea')),
147
+ [],
148
+ false,
149
+ )
150
+ }
151
+
152
+ function wrapWithInspector(jsxNode, snapshotExpression) {
153
+ return t.jsxFragment(
154
+ t.jsxOpeningFragment(),
155
+ t.jsxClosingFragment(),
156
+ [buildInspectorTextarea(snapshotExpression), jsxNode],
157
+ )
158
+ }
159
+
160
+ function transformComponentFunction(functionPath) {
161
+ ensureBlockBody(functionPath.node)
162
+
163
+ const returnPaths = []
164
+ functionPath.get('body').traverse({
165
+ ReturnStatement(returnPath) {
166
+ if (returnPath.getFunctionParent() !== functionPath) return
167
+ const arg = returnPath.node.argument
168
+ if (t.isJSXElement(arg) || t.isJSXFragment(arg)) {
169
+ returnPaths.push(returnPath)
170
+ }
171
+ },
172
+ })
173
+
174
+ if (returnPaths.length === 0) return false
175
+
176
+ const stateNames = collectStateNames(functionPath)
177
+ const propsExpression = buildPropsExpression(functionPath.node.params[0])
178
+
179
+ const stateExpression = t.objectExpression(
180
+ stateNames.map(name => t.objectProperty(t.identifier(name), t.identifier(name), false, true)),
181
+ )
182
+
183
+ const snapshotObject = t.objectExpression([
184
+ t.objectProperty(t.identifier('props'), propsExpression),
185
+ t.objectProperty(t.identifier('state'), stateExpression),
186
+ ])
187
+
188
+ returnPaths.forEach(returnPath => {
189
+ const arg = returnPath.node.argument
190
+ if (!arg) return
191
+ returnPath.node.argument = wrapWithInspector(arg, snapshotObject)
192
+ })
193
+
194
+ return true
195
+ }
196
+
197
+ export default function componentValuesPlugin() {
198
+ return {
199
+ name: 'vite-plugin-component-values',
200
+ enforce: 'pre',
201
+ transform(code, id) {
202
+ if (!/\.[jt]sx?$/.test(id) || id.includes('node_modules')) return
203
+ if (!code.includes('jsx') && !/<[A-Z]/.test(code) && !/<[a-z]/.test(code)) return
204
+
205
+ let ast
206
+ try {
207
+ ast = parse(code, {
208
+ sourceType: 'module',
209
+ plugins: ['jsx', 'typescript'],
210
+ })
211
+ } catch {
212
+ return
213
+ }
214
+
215
+ let modified = false
216
+
217
+ traverseAst(ast, {
218
+ VariableDeclarator(path) {
219
+ if (!t.isIdentifier(path.node.id) || !isComponentName(path.node.id.name)) return
220
+ if (!isTopLevelDeclaration(path.parentPath)) return
221
+
222
+ const { init } = path.node
223
+ if (!init) return
224
+ if (!t.isArrowFunctionExpression(init) && !t.isFunctionExpression(init)) return
225
+
226
+ const initPath = path.get('init')
227
+ if (transformComponentFunction(initPath)) {
228
+ modified = true
229
+ }
230
+ },
231
+
232
+ FunctionDeclaration(path) {
233
+ if (!path.node.id || !isComponentName(path.node.id.name)) return
234
+ if (!isTopLevelDeclaration(path)) return
235
+
236
+ if (transformComponentFunction(path)) {
237
+ modified = true
238
+ }
239
+ },
240
+ })
241
+
242
+ if (!modified) return
243
+
244
+ const output = generateCode(ast, { retainLines: true }, code)
245
+ return { code: output.code, map: output.map }
246
+ },
247
+ }
248
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export function componentBorder(): any;
2
+ export function componentValues(): any;
3
+ export function saveFile(): any;
4
+ export function sourceExplorer(): any;
5
+ export function llmExplainInject(options?: {
6
+ targetFile?: string;
7
+ workspaceFunction?: string;
8
+ sourcePath?: string;
9
+ }): any;
10
+ export function sourceCompressor(): any;
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { default as componentBorder } from './component-border.js'
2
+ export { default as componentValues } from './component-values.js'
3
+ export { default as saveFile } from './save-file.js'
4
+ export { default as sourceExplorer } from './source-explorer.js'
5
+ export { default as llmExplainInject } from './llm-explain-inject.js'
6
+ export { default as sourceCompressor } from './source-compressor.js'
@@ -0,0 +1,218 @@
1
+ import path from 'node:path'
2
+ import { parse } from '@babel/parser'
3
+ import traverse from '@babel/traverse'
4
+ import generate from '@babel/generator'
5
+ import * as t from '@babel/types'
6
+
7
+ const traverseAst = traverse.default ?? traverse
8
+ const generateCode = generate.default ?? generate
9
+
10
+ const RUNTIME_IMPORT_SOURCE = '@test328932/test328933/runtime'
11
+ const PANEL_COMPONENT_NAME = 'LlmExplainPanel'
12
+
13
+ function ensureBlockBody(functionNode) {
14
+ if (t.isBlockStatement(functionNode.body)) return
15
+ functionNode.body = t.blockStatement([t.returnStatement(functionNode.body)])
16
+ }
17
+
18
+ function buildPanelElement(sourcePath) {
19
+ return t.jsxElement(
20
+ t.jsxOpeningElement(
21
+ t.jsxIdentifier(PANEL_COMPONENT_NAME),
22
+ [
23
+ t.jsxAttribute(
24
+ t.jsxIdentifier('sourcePath'),
25
+ t.stringLiteral(sourcePath),
26
+ ),
27
+ ],
28
+ true,
29
+ ),
30
+ null,
31
+ [],
32
+ true,
33
+ )
34
+ }
35
+
36
+ function hasPanelInJSX(node) {
37
+ let found = false
38
+
39
+ traverseAst(
40
+ t.file(t.program([t.expressionStatement(node)])),
41
+ {
42
+ JSXOpeningElement(path) {
43
+ if (t.isJSXIdentifier(path.node.name, { name: PANEL_COMPONENT_NAME })) {
44
+ found = true
45
+ path.stop()
46
+ }
47
+ },
48
+ },
49
+ )
50
+
51
+ return found
52
+ }
53
+
54
+ function ensureRuntimeImport(ast) {
55
+ const body = ast.program.body
56
+ let runtimeImport = null
57
+
58
+ for (const statement of body) {
59
+ if (t.isImportDeclaration(statement) && statement.source.value === RUNTIME_IMPORT_SOURCE) {
60
+ runtimeImport = statement
61
+ break
62
+ }
63
+ }
64
+
65
+ if (runtimeImport) {
66
+ const hasSpecifier = runtimeImport.specifiers.some(specifier => (
67
+ t.isImportSpecifier(specifier) &&
68
+ t.isIdentifier(specifier.imported, { name: PANEL_COMPONENT_NAME })
69
+ ))
70
+
71
+ if (!hasSpecifier) {
72
+ runtimeImport.specifiers.push(
73
+ t.importSpecifier(
74
+ t.identifier(PANEL_COMPONENT_NAME),
75
+ t.identifier(PANEL_COMPONENT_NAME),
76
+ ),
77
+ )
78
+ return true
79
+ }
80
+
81
+ return false
82
+ }
83
+
84
+ const lastImportIndex = [...body].reverse().findIndex(statement => t.isImportDeclaration(statement))
85
+ const insertIndex = lastImportIndex === -1 ? 0 : body.length - lastImportIndex
86
+
87
+ body.splice(
88
+ insertIndex,
89
+ 0,
90
+ t.importDeclaration(
91
+ [
92
+ t.importSpecifier(
93
+ t.identifier(PANEL_COMPONENT_NAME),
94
+ t.identifier(PANEL_COMPONENT_NAME),
95
+ ),
96
+ ],
97
+ t.stringLiteral(RUNTIME_IMPORT_SOURCE),
98
+ ),
99
+ )
100
+
101
+ return true
102
+ }
103
+
104
+ function findWorkspaceFunctionPath(ast, workspaceFunction) {
105
+ let workspacePath = null
106
+
107
+ traverseAst(ast, {
108
+ FunctionDeclaration(path) {
109
+ if (workspacePath) return
110
+ if (!path.node.id || path.node.id.name !== workspaceFunction) return
111
+ workspacePath = path
112
+ },
113
+ VariableDeclarator(path) {
114
+ if (workspacePath) return
115
+ if (!t.isIdentifier(path.node.id, { name: workspaceFunction })) return
116
+ const { init } = path.node
117
+ if (!init) return
118
+ if (!t.isFunctionExpression(init) && !t.isArrowFunctionExpression(init)) return
119
+ workspacePath = path.get('init')
120
+ },
121
+ })
122
+
123
+ return workspacePath
124
+ }
125
+
126
+ function injectPanelIntoWorkspace(workspacePath, sourcePath) {
127
+ ensureBlockBody(workspacePath.node)
128
+
129
+ let returnPath = null
130
+
131
+ workspacePath.get('body').traverse({
132
+ ReturnStatement(path) {
133
+ if (path.getFunctionParent() !== workspacePath) return
134
+ if (returnPath) return
135
+
136
+ const arg = path.node.argument
137
+ if (t.isJSXElement(arg) || t.isJSXFragment(arg)) {
138
+ returnPath = path
139
+ }
140
+ },
141
+ })
142
+
143
+ if (!returnPath) return false
144
+
145
+ const returnArg = returnPath.node.argument
146
+ if (!returnArg) return false
147
+
148
+ if (hasPanelInJSX(returnArg)) return false
149
+
150
+ if (t.isJSXElement(returnArg) || t.isJSXFragment(returnArg)) {
151
+ returnArg.children.push(buildPanelElement(sourcePath))
152
+ return true
153
+ }
154
+
155
+ return false
156
+ }
157
+
158
+ export default function llmExplainInject(options = {}) {
159
+ const {
160
+ targetFile = 'src/App.tsx',
161
+ workspaceFunction = 'TodoWorkspace',
162
+ sourcePath = 'App.tsx',
163
+ } = options
164
+
165
+ let rootDir = process.cwd()
166
+ let targetAbsolutePath = path.resolve(rootDir, targetFile)
167
+
168
+ return {
169
+ name: 'vite-plugin-llm-explain-inject',
170
+ enforce: 'pre',
171
+ configResolved(config) {
172
+ rootDir = config.root
173
+ targetAbsolutePath = path.resolve(rootDir, targetFile)
174
+ },
175
+ transform(code, id) {
176
+ const cleanId = id.split('?')[0]
177
+ if (path.resolve(cleanId) !== targetAbsolutePath) return
178
+ if (!/\.[jt]sx?$/.test(cleanId)) return
179
+
180
+ let ast
181
+ try {
182
+ ast = parse(code, {
183
+ sourceType: 'module',
184
+ plugins: ['jsx', 'typescript'],
185
+ })
186
+ } catch {
187
+ return
188
+ }
189
+
190
+ const workspacePath = findWorkspaceFunctionPath(ast, workspaceFunction)
191
+ if (!workspacePath) {
192
+ this.warn(
193
+ `[vite-plugin-llm-explain-inject] Could not find workspace function "${workspaceFunction}" in ${targetFile}. No injection performed.`,
194
+ )
195
+ return
196
+ }
197
+
198
+ let modified = false
199
+ const panelInjected = injectPanelIntoWorkspace(workspacePath, sourcePath)
200
+ if (panelInjected) {
201
+ modified = true
202
+ }
203
+
204
+ const importModified = ensureRuntimeImport(ast)
205
+ if (importModified) {
206
+ modified = true
207
+ }
208
+
209
+ if (!modified) return
210
+
211
+ const output = generateCode(ast, { retainLines: true }, code)
212
+ return {
213
+ code: output.code,
214
+ map: output.map,
215
+ }
216
+ },
217
+ }
218
+ }
@@ -0,0 +1,4 @@
1
+ import { ComponentType } from 'react'
2
+
3
+ declare const LlmExplainPanel: ComponentType<{ sourcePath?: string }>
4
+ export default LlmExplainPanel
@@ -0,0 +1,2 @@
1
+ export { default } from './llm-explain-panel.jsx'
2
+ export { default as LlmExplainPanel } from './llm-explain-panel.jsx'