@tscircuit/eval 0.0.275 → 0.0.276
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/eval",
|
|
3
3
|
"main": "dist/lib/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.276",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "bun run build:lib && bun run build:webworker && bun run build:blob-url && bun run build:runner && bun run build:worker-wrapper",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"@biomejs/biome": "^1.8.3",
|
|
54
54
|
"@playwright/test": "^1.50.1",
|
|
55
55
|
"@tscircuit/capacity-autorouter": "^0.0.100",
|
|
56
|
-
"@tscircuit/core": "^0.0.
|
|
56
|
+
"@tscircuit/core": "^0.0.614",
|
|
57
57
|
"@tscircuit/math-utils": "^0.0.18",
|
|
58
58
|
"@tscircuit/parts-engine": "^0.0.8",
|
|
59
59
|
"@types/babel__standalone": "^7.1.9",
|
|
60
60
|
"@types/bun": "^1.2.16",
|
|
61
61
|
"@types/react": "^19.1.8",
|
|
62
|
-
"circuit-json": "^0.0.
|
|
62
|
+
"circuit-json": "^0.0.226",
|
|
63
63
|
"comlink": "^4.4.2",
|
|
64
64
|
"graphics-debug": "^0.0.60",
|
|
65
65
|
"jscad-fiber": "^0.0.82",
|
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
# Example Circuit Transpilation and Execution Implementation
|
|
2
|
-
|
|
3
|
-
This is a sample implementation of transpiling and executing tscircuit code from
|
|
4
|
-
another project.
|
|
5
|
-
|
|
6
|
-
```tsx
|
|
7
|
-
import * as tscircuitCore from "@tscircuit/core"
|
|
8
|
-
import { getImportsFromCode } from "lib/utils/get-imports-from-code"
|
|
9
|
-
import type { AnyCircuitElement } from "circuit-json"
|
|
10
|
-
import * as jscadFiber from "jscad-fiber"
|
|
11
|
-
import * as React from "react"
|
|
12
|
-
import { useEffect, useMemo, useReducer, useRef, useState } from "react"
|
|
13
|
-
import { safeCompileTsx } from "../use-compiled-tsx"
|
|
14
|
-
import { useSnippetsBaseApiUrl } from "../use-snippets-base-api-url"
|
|
15
|
-
import { constructCircuit } from "./construct-circuit"
|
|
16
|
-
import { evalCompiledJs } from "./eval-compiled-js"
|
|
17
|
-
import { getSyntaxError } from "@/lib/utils/getSyntaxError"
|
|
18
|
-
|
|
19
|
-
type RunTsxResult = {
|
|
20
|
-
compiledModule: any
|
|
21
|
-
message: string
|
|
22
|
-
circuitJson: AnyCircuitElement[] | null
|
|
23
|
-
compiledJs?: string
|
|
24
|
-
isLoading: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const useRunTsx = ({
|
|
28
|
-
code,
|
|
29
|
-
userImports,
|
|
30
|
-
type,
|
|
31
|
-
isStreaming = false,
|
|
32
|
-
}: {
|
|
33
|
-
code?: string
|
|
34
|
-
userImports?: Record<string, object>
|
|
35
|
-
type?: "board" | "footprint" | "package" | "model"
|
|
36
|
-
isStreaming?: boolean
|
|
37
|
-
} = {}): RunTsxResult & {
|
|
38
|
-
circuitJsonKey: string
|
|
39
|
-
triggerRunTsx: () => void
|
|
40
|
-
tsxRunTriggerCount: number
|
|
41
|
-
} => {
|
|
42
|
-
type ??= "board"
|
|
43
|
-
const [tsxRunTriggerCount, incTsxRunTriggerCount] = useReducer(
|
|
44
|
-
(c) => c + 1,
|
|
45
|
-
0
|
|
46
|
-
)
|
|
47
|
-
const [tsxResult, setTsxResult] = useState<RunTsxResult>({
|
|
48
|
-
compiledModule: null,
|
|
49
|
-
message: "",
|
|
50
|
-
circuitJson: null,
|
|
51
|
-
isLoading: false,
|
|
52
|
-
})
|
|
53
|
-
const apiBaseUrl = useSnippetsBaseApiUrl()
|
|
54
|
-
const preSuppliedImportsRef = useRef<Record<string, any>>({})
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (tsxRunTriggerCount === 0) return
|
|
58
|
-
if (isStreaming) {
|
|
59
|
-
setTsxResult({
|
|
60
|
-
compiledModule: null,
|
|
61
|
-
message: "",
|
|
62
|
-
circuitJson: null,
|
|
63
|
-
isLoading: false,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
if (!code) return
|
|
67
|
-
const syntaxError = getSyntaxError(code)
|
|
68
|
-
if (syntaxError) {
|
|
69
|
-
setTsxResult({
|
|
70
|
-
compiledModule: null,
|
|
71
|
-
message: syntaxError,
|
|
72
|
-
circuitJson: null,
|
|
73
|
-
isLoading: false,
|
|
74
|
-
})
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
async function run() {
|
|
78
|
-
setTsxResult({
|
|
79
|
-
compiledModule: null,
|
|
80
|
-
message: "",
|
|
81
|
-
circuitJson: null,
|
|
82
|
-
isLoading: true,
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const userCodeTsciImports = getImportsFromCode(code!).filter((imp) =>
|
|
86
|
-
imp.startsWith("@tsci/")
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
const preSuppliedImports: Record<string, any> =
|
|
90
|
-
preSuppliedImportsRef.current
|
|
91
|
-
|
|
92
|
-
for (const [importName, importValue] of Object.entries(
|
|
93
|
-
userImports ?? {}
|
|
94
|
-
)) {
|
|
95
|
-
preSuppliedImports[importName] = importValue
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const __tscircuit_require = (name: string) => {
|
|
99
|
-
if (!preSuppliedImports[name]) {
|
|
100
|
-
throw new Error(
|
|
101
|
-
`Import "${name}" not found (imports available: ${Object.keys(
|
|
102
|
-
preSuppliedImports
|
|
103
|
-
).join(",")})`
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
return preSuppliedImports[name]
|
|
107
|
-
}
|
|
108
|
-
;(globalThis as any).__tscircuit_require = __tscircuit_require
|
|
109
|
-
preSuppliedImports["@tscircuit/core"] = tscircuitCore
|
|
110
|
-
preSuppliedImports["react"] = React
|
|
111
|
-
preSuppliedImports["jscad-fiber"] = jscadFiber
|
|
112
|
-
globalThis.React = React
|
|
113
|
-
|
|
114
|
-
async function addImport(importName: string, depth = 0) {
|
|
115
|
-
if (!importName.startsWith("@tsci/")) return
|
|
116
|
-
if (preSuppliedImports[importName]) return
|
|
117
|
-
if (depth > 5) {
|
|
118
|
-
console.log("Max depth for imports reached")
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const fullSnippetName = importName
|
|
123
|
-
.replace("@tsci/", "")
|
|
124
|
-
.replace(".", "/")
|
|
125
|
-
const { snippet: importedSnippet, error } = await fetch(
|
|
126
|
-
`${apiBaseUrl}/snippets/get?name=${fullSnippetName}`
|
|
127
|
-
)
|
|
128
|
-
.then((res) => res.json())
|
|
129
|
-
.catch((e) => ({ error: e }))
|
|
130
|
-
|
|
131
|
-
if (error) {
|
|
132
|
-
console.error("Error fetching import", importName, error)
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const { compiled_js, code } = importedSnippet
|
|
137
|
-
|
|
138
|
-
const importNames = getImportsFromCode(code!)
|
|
139
|
-
|
|
140
|
-
for (const importName of importNames) {
|
|
141
|
-
if (!preSuppliedImports[importName]) {
|
|
142
|
-
await addImport(importName, depth + 1)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
preSuppliedImports[importName] = evalCompiledJs(compiled_js).exports
|
|
148
|
-
} catch (e) {
|
|
149
|
-
console.error("Error importing snippet", e)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
for (const userCodeTsciImport of userCodeTsciImports) {
|
|
154
|
-
await addImport(userCodeTsciImport)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const { success, compiledTsx: compiledJs, error } = safeCompileTsx(code!)
|
|
158
|
-
|
|
159
|
-
if (!success) {
|
|
160
|
-
setTsxResult({
|
|
161
|
-
compiledModule: null,
|
|
162
|
-
message: `Compile Error: ${error.message}`,
|
|
163
|
-
circuitJson: null,
|
|
164
|
-
isLoading: false,
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
const module = evalCompiledJs(compiledJs!)
|
|
170
|
-
|
|
171
|
-
const componentExportKeys = Object.keys(module.exports).filter(
|
|
172
|
-
(key) => !key.startsWith("use")
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
if (componentExportKeys.length > 1) {
|
|
176
|
-
throw new Error(
|
|
177
|
-
`Too many exports, only export one component. You exported: ${JSON.stringify(
|
|
178
|
-
Object.keys(module.exports)
|
|
179
|
-
)}`
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const primaryKey = componentExportKeys[0]
|
|
184
|
-
|
|
185
|
-
const UserElm = (props: any) =>
|
|
186
|
-
React.createElement(module.exports[primaryKey], props)
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const circuit = constructCircuit(UserElm, type as any)
|
|
190
|
-
const renderPromise = circuit.renderUntilSettled()
|
|
191
|
-
|
|
192
|
-
// wait one tick to allow a single render pass
|
|
193
|
-
await new Promise((resolve) => setTimeout(resolve, 1))
|
|
194
|
-
|
|
195
|
-
let circuitJson = circuit.getCircuitJson()
|
|
196
|
-
setTsxResult({
|
|
197
|
-
compiledModule: module,
|
|
198
|
-
compiledJs,
|
|
199
|
-
message: "",
|
|
200
|
-
circuitJson: circuitJson as AnyCircuitElement[],
|
|
201
|
-
isLoading: true,
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
await renderPromise
|
|
205
|
-
|
|
206
|
-
circuitJson = circuit.getCircuitJson()
|
|
207
|
-
setTsxResult({
|
|
208
|
-
compiledModule: module,
|
|
209
|
-
compiledJs,
|
|
210
|
-
message: "",
|
|
211
|
-
circuitJson: circuitJson as AnyCircuitElement[],
|
|
212
|
-
isLoading: false,
|
|
213
|
-
})
|
|
214
|
-
} catch (error: any) {
|
|
215
|
-
console.error("Evaluation error:", error)
|
|
216
|
-
setTsxResult({
|
|
217
|
-
compiledModule: module,
|
|
218
|
-
message: `Render Error: ${error.message}`,
|
|
219
|
-
circuitJson: null,
|
|
220
|
-
isLoading: false,
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
} catch (error: any) {
|
|
224
|
-
console.error("Evaluation error:", error)
|
|
225
|
-
setTsxResult({
|
|
226
|
-
compiledModule: null,
|
|
227
|
-
message: `Eval Error: ${error.message}\n\n${error.stack}`,
|
|
228
|
-
circuitJson: null,
|
|
229
|
-
isLoading: false,
|
|
230
|
-
})
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
run()
|
|
234
|
-
}, [tsxRunTriggerCount])
|
|
235
|
-
|
|
236
|
-
const circuitJsonKey: string = useMemo(() => {
|
|
237
|
-
if (!tsxResult.circuitJson) return ""
|
|
238
|
-
return `cj-${Math.random().toString(36).substring(2, 15)}`
|
|
239
|
-
}, [tsxResult.circuitJson, tsxResult.circuitJson?.length])
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
...tsxResult,
|
|
243
|
-
circuitJsonKey: circuitJsonKey,
|
|
244
|
-
triggerRunTsx: incTsxRunTriggerCount,
|
|
245
|
-
tsxRunTriggerCount,
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
```tsx
|
|
251
|
-
export const evalCompiledJs = (compiledCode: string) => {
|
|
252
|
-
const functionBody = `
|
|
253
|
-
var exports = {};
|
|
254
|
-
var require = globalThis.__tscircuit_require;
|
|
255
|
-
var module = { exports };
|
|
256
|
-
${compiledCode};
|
|
257
|
-
return module;`.trim()
|
|
258
|
-
return Function(functionBody).call(globalThis)
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { Circuit } from "@tscircuit/core"
|
|
264
|
-
import { useEffect, useMemo, useState } from "react"
|
|
265
|
-
import * as React from "react"
|
|
266
|
-
import { useCompiledTsx } from "../use-compiled-tsx"
|
|
267
|
-
import { createJSCADRenderer } from "jscad-fiber"
|
|
268
|
-
import { jscadPlanner } from "jscad-planner"
|
|
269
|
-
import { jlcPartsEngine } from "@/lib/jlc-parts-engine"
|
|
270
|
-
|
|
271
|
-
export const constructCircuit = (
|
|
272
|
-
UserElm: any,
|
|
273
|
-
type: "board" | "footprint" | "package" | "model"
|
|
274
|
-
) => {
|
|
275
|
-
const circuit = new Circuit()
|
|
276
|
-
|
|
277
|
-
if (type === "board") {
|
|
278
|
-
circuit.add(<UserElm />)
|
|
279
|
-
// HACK: switch to selectOne when root fixes bug with selecting board
|
|
280
|
-
const board = circuit.root?.children[0]
|
|
281
|
-
// const board = circuit.selectOne("board")
|
|
282
|
-
if (board) {
|
|
283
|
-
board.setProps({
|
|
284
|
-
...board.props,
|
|
285
|
-
partsEngine: jlcPartsEngine,
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
} else if (type === "package") {
|
|
289
|
-
circuit.add(
|
|
290
|
-
<board width="50mm" height="50mm">
|
|
291
|
-
<UserElm name="U1" />
|
|
292
|
-
</board>
|
|
293
|
-
)
|
|
294
|
-
} else if (type === "footprint") {
|
|
295
|
-
circuit.add(
|
|
296
|
-
<board width="10mm" height="10mm">
|
|
297
|
-
<chip name="U1" footprint={<UserElm />} />
|
|
298
|
-
</board>
|
|
299
|
-
)
|
|
300
|
-
} else if (type === "model") {
|
|
301
|
-
const jscadGeoms: any[] = []
|
|
302
|
-
const { createJSCADRoot } = createJSCADRenderer(jscadPlanner as any)
|
|
303
|
-
const jscadRoot = createJSCADRoot(jscadGeoms)
|
|
304
|
-
jscadRoot.render(<UserElm />)
|
|
305
|
-
circuit.add(
|
|
306
|
-
<board width="10mm" height="10mm">
|
|
307
|
-
<chip
|
|
308
|
-
name="U1"
|
|
309
|
-
cadModel={{
|
|
310
|
-
jscad: jscadGeoms[0],
|
|
311
|
-
}}
|
|
312
|
-
/>
|
|
313
|
-
</board>
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
return circuit
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
```tsx
|
|
321
|
-
import * as Babel from "@babel/standalone"
|
|
322
|
-
|
|
323
|
-
export function getSyntaxError(code: string): string | null {
|
|
324
|
-
try {
|
|
325
|
-
Babel.transform(code, {
|
|
326
|
-
filename: "index.tsx",
|
|
327
|
-
presets: ["react", "typescript"],
|
|
328
|
-
})
|
|
329
|
-
return null
|
|
330
|
-
} catch (error: unknown) {
|
|
331
|
-
return (error as Error).message
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
```tsx
|
|
337
|
-
import { useMemo } from "react"
|
|
338
|
-
import * as Babel from "@babel/standalone"
|
|
339
|
-
|
|
340
|
-
export const safeCompileTsx = (
|
|
341
|
-
code: string
|
|
342
|
-
):
|
|
343
|
-
| { success: true; compiledTsx: string; error?: undefined }
|
|
344
|
-
| { success: false; error: Error; compiledTsx?: undefined } => {
|
|
345
|
-
try {
|
|
346
|
-
return {
|
|
347
|
-
success: true,
|
|
348
|
-
compiledTsx:
|
|
349
|
-
Babel.transform(code, {
|
|
350
|
-
presets: ["react", "typescript"],
|
|
351
|
-
plugins: ["transform-modules-commonjs"],
|
|
352
|
-
filename: "virtual.tsx",
|
|
353
|
-
}).code || "",
|
|
354
|
-
}
|
|
355
|
-
} catch (error: any) {
|
|
356
|
-
return { success: false, error }
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
export const useCompiledTsx = (
|
|
361
|
-
code?: string,
|
|
362
|
-
{ isStreaming = false }: { isStreaming?: boolean } = {}
|
|
363
|
-
) => {
|
|
364
|
-
return useMemo(() => {
|
|
365
|
-
if (!code) return ""
|
|
366
|
-
if (isStreaming) return ""
|
|
367
|
-
const result = safeCompileTsx(code)
|
|
368
|
-
if (result.success) {
|
|
369
|
-
return result.compiledTsx
|
|
370
|
-
}
|
|
371
|
-
return `Error: ${result.error.message}`
|
|
372
|
-
}, [code, isStreaming])
|
|
373
|
-
}
|
|
374
|
-
```
|