diff --git a/.github/workflows/branch-release.yml b/.github/workflows/branch-release.yml new file mode 100644 index 0000000..c2203ad --- /dev/null +++ b/.github/workflows/branch-release.yml @@ -0,0 +1,30 @@ +name: Continuous Branch Release + +on: + pull_request: + branches: + - "*" + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org/ + + - name: Install dependencies + run: bun install + + - name: Build Project + run: bun run build + + - name: Run pkg.pr.new for PR-based release + run: bun run pkg-pr-new-release diff --git a/docs/example-circuit-transpilation-and-execution-implementation.md b/docs/example-circuit-transpilation-and-execution-implementation.md deleted file mode 100644 index 5163ca7..0000000 --- a/docs/example-circuit-transpilation-and-execution-implementation.md +++ /dev/null @@ -1,374 +0,0 @@ -# Example Circuit Transpilation and Execution Implementation - -This is a sample implementation of transpiling and executing tscircuit code from -another project. - -```tsx -import * as tscircuitCore from "@tscircuit/core" -import { getImportsFromCode } from "lib/utils/get-imports-from-code" -import type { AnyCircuitElement } from "circuit-json" -import * as jscadFiber from "jscad-fiber" -import * as React from "react" -import { useEffect, useMemo, useReducer, useRef, useState } from "react" -import { safeCompileTsx } from "../use-compiled-tsx" -import { useSnippetsBaseApiUrl } from "../use-snippets-base-api-url" -import { constructCircuit } from "./construct-circuit" -import { evalCompiledJs } from "./eval-compiled-js" -import { getSyntaxError } from "@/lib/utils/getSyntaxError" - -type RunTsxResult = { - compiledModule: any - message: string - circuitJson: AnyCircuitElement[] | null - compiledJs?: string - isLoading: boolean -} - -export const useRunTsx = ({ - code, - userImports, - type, - isStreaming = false, -}: { - code?: string - userImports?: Record - type?: "board" | "footprint" | "package" | "model" - isStreaming?: boolean -} = {}): RunTsxResult & { - circuitJsonKey: string - triggerRunTsx: () => void - tsxRunTriggerCount: number -} => { - type ??= "board" - const [tsxRunTriggerCount, incTsxRunTriggerCount] = useReducer( - (c) => c + 1, - 0 - ) - const [tsxResult, setTsxResult] = useState({ - compiledModule: null, - message: "", - circuitJson: null, - isLoading: false, - }) - const apiBaseUrl = useSnippetsBaseApiUrl() - const preSuppliedImportsRef = useRef>({}) - - useEffect(() => { - if (tsxRunTriggerCount === 0) return - if (isStreaming) { - setTsxResult({ - compiledModule: null, - message: "", - circuitJson: null, - isLoading: false, - }) - } - if (!code) return - const syntaxError = getSyntaxError(code) - if (syntaxError) { - setTsxResult({ - compiledModule: null, - message: syntaxError, - circuitJson: null, - isLoading: false, - }) - return - } - async function run() { - setTsxResult({ - compiledModule: null, - message: "", - circuitJson: null, - isLoading: true, - }) - - const userCodeTsciImports = getImportsFromCode(code!).filter((imp) => - imp.startsWith("@tsci/") - ) - - const preSuppliedImports: Record = - preSuppliedImportsRef.current - - for (const [importName, importValue] of Object.entries( - userImports ?? {} - )) { - preSuppliedImports[importName] = importValue - } - - const __tscircuit_require = (name: string) => { - if (!preSuppliedImports[name]) { - throw new Error( - `Import "${name}" not found (imports available: ${Object.keys( - preSuppliedImports - ).join(",")})` - ) - } - return preSuppliedImports[name] - } - ;(globalThis as any).__tscircuit_require = __tscircuit_require - preSuppliedImports["@tscircuit/core"] = tscircuitCore - preSuppliedImports["react"] = React - preSuppliedImports["jscad-fiber"] = jscadFiber - globalThis.React = React - - async function addImport(importName: string, depth = 0) { - if (!importName.startsWith("@tsci/")) return - if (preSuppliedImports[importName]) return - if (depth > 5) { - console.log("Max depth for imports reached") - return - } - - const fullSnippetName = importName - .replace("@tsci/", "") - .replace(".", "/") - const { snippet: importedSnippet, error } = await fetch( - `${apiBaseUrl}/snippets/get?name=${fullSnippetName}` - ) - .then((res) => res.json()) - .catch((e) => ({ error: e })) - - if (error) { - console.error("Error fetching import", importName, error) - return - } - - const { compiled_js, code } = importedSnippet - - const importNames = getImportsFromCode(code!) - - for (const importName of importNames) { - if (!preSuppliedImports[importName]) { - await addImport(importName, depth + 1) - } - } - - try { - preSuppliedImports[importName] = evalCompiledJs(compiled_js).exports - } catch (e) { - console.error("Error importing snippet", e) - } - } - - for (const userCodeTsciImport of userCodeTsciImports) { - await addImport(userCodeTsciImport) - } - - const { success, compiledTsx: compiledJs, error } = safeCompileTsx(code!) - - if (!success) { - setTsxResult({ - compiledModule: null, - message: `Compile Error: ${error.message}`, - circuitJson: null, - isLoading: false, - }) - } - - try { - const module = evalCompiledJs(compiledJs!) - - const componentExportKeys = Object.keys(module.exports).filter( - (key) => !key.startsWith("use") - ) - - if (componentExportKeys.length > 1) { - throw new Error( - `Too many exports, only export one component. You exported: ${JSON.stringify( - Object.keys(module.exports) - )}` - ) - } - - const primaryKey = componentExportKeys[0] - - const UserElm = (props: any) => - React.createElement(module.exports[primaryKey], props) - - try { - const circuit = constructCircuit(UserElm, type as any) - const renderPromise = circuit.renderUntilSettled() - - // wait one tick to allow a single render pass - await new Promise((resolve) => setTimeout(resolve, 1)) - - let circuitJson = circuit.getCircuitJson() - setTsxResult({ - compiledModule: module, - compiledJs, - message: "", - circuitJson: circuitJson as AnyCircuitElement[], - isLoading: true, - }) - - await renderPromise - - circuitJson = circuit.getCircuitJson() - setTsxResult({ - compiledModule: module, - compiledJs, - message: "", - circuitJson: circuitJson as AnyCircuitElement[], - isLoading: false, - }) - } catch (error: any) { - console.error("Evaluation error:", error) - setTsxResult({ - compiledModule: module, - message: `Render Error: ${error.message}`, - circuitJson: null, - isLoading: false, - }) - } - } catch (error: any) { - console.error("Evaluation error:", error) - setTsxResult({ - compiledModule: null, - message: `Eval Error: ${error.message}\n\n${error.stack}`, - circuitJson: null, - isLoading: false, - }) - } - } - run() - }, [tsxRunTriggerCount]) - - const circuitJsonKey: string = useMemo(() => { - if (!tsxResult.circuitJson) return "" - return `cj-${Math.random().toString(36).substring(2, 15)}` - }, [tsxResult.circuitJson, tsxResult.circuitJson?.length]) - - return { - ...tsxResult, - circuitJsonKey: circuitJsonKey, - triggerRunTsx: incTsxRunTriggerCount, - tsxRunTriggerCount, - } -} -``` - -```tsx -export const evalCompiledJs = (compiledCode: string) => { - const functionBody = ` -var exports = {}; -var require = globalThis.__tscircuit_require; -var module = { exports }; -${compiledCode}; -return module;`.trim() - return Function(functionBody).call(globalThis) -} -``` - -```tsx -import { Circuit } from "@tscircuit/core" -import { useEffect, useMemo, useState } from "react" -import * as React from "react" -import { useCompiledTsx } from "../use-compiled-tsx" -import { createJSCADRenderer } from "jscad-fiber" -import { jscadPlanner } from "jscad-planner" -import { jlcPartsEngine } from "@/lib/jlc-parts-engine" - -export const constructCircuit = ( - UserElm: any, - type: "board" | "footprint" | "package" | "model" -) => { - const circuit = new Circuit() - - if (type === "board") { - circuit.add() - // HACK: switch to selectOne when root fixes bug with selecting board - const board = circuit.root?.children[0] - // const board = circuit.selectOne("board") - if (board) { - board.setProps({ - ...board.props, - partsEngine: jlcPartsEngine, - }) - } - } else if (type === "package") { - circuit.add( - - - - ) - } else if (type === "footprint") { - circuit.add( - - } /> - - ) - } else if (type === "model") { - const jscadGeoms: any[] = [] - const { createJSCADRoot } = createJSCADRenderer(jscadPlanner as any) - const jscadRoot = createJSCADRoot(jscadGeoms) - jscadRoot.render() - circuit.add( - - - - ) - } - return circuit -} -``` - -```tsx -import * as Babel from "@babel/standalone" - -export function getSyntaxError(code: string): string | null { - try { - Babel.transform(code, { - filename: "index.tsx", - presets: ["react", "typescript"], - }) - return null - } catch (error: unknown) { - return (error as Error).message - } -} -``` - -```tsx -import { useMemo } from "react" -import * as Babel from "@babel/standalone" - -export const safeCompileTsx = ( - code: string -): - | { success: true; compiledTsx: string; error?: undefined } - | { success: false; error: Error; compiledTsx?: undefined } => { - try { - return { - success: true, - compiledTsx: - Babel.transform(code, { - presets: ["react", "typescript"], - plugins: ["transform-modules-commonjs"], - filename: "virtual.tsx", - }).code || "", - } - } catch (error: any) { - return { success: false, error } - } -} - -export const useCompiledTsx = ( - code?: string, - { isStreaming = false }: { isStreaming?: boolean } = {} -) => { - return useMemo(() => { - if (!code) return "" - if (isStreaming) return "" - const result = safeCompileTsx(code) - if (result.success) { - return result.compiledTsx - } - return `Error: ${result.error.message}` - }, [code, isStreaming]) -} -``` diff --git a/package.json b/package.json index f9cad2f..acad6f3 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build:webworker:analyze": "tsup ./webworker/entrypoint.ts --platform browser --metafile ./metadata.json --format esm --sourcemap inline -d dist/webworker", "build:blob-url": "bun run ./scripts/build-worker-blob-url.ts", "build:runner": "tsup-node --config tsup-runner.config.ts", + "pkg-pr-new-release": "bunx pkg-pr-new publish --comment=off --peerDeps", "start:browser-test-server": "bun --port 3070 ./browser-tests/browsertest.html", "format": "biome format --write .", "format:check": "biome format .", @@ -53,50 +54,51 @@ "@biomejs/biome": "^1.8.3", "@playwright/test": "^1.50.1", "@tscircuit/capacity-autorouter": "^0.0.93", - "@tscircuit/core": "^0.0.558", - "@tscircuit/math-utils": "^0.0.18", - "@tscircuit/parts-engine": "^0.0.8", - "@types/babel__standalone": "^7.1.9", - "@types/bun": "^1.2.16", - "@types/react": "^19.1.8", - "circuit-json": "^0.0.219", - "comlink": "^4.4.2", - "graphics-debug": "^0.0.60", - "jscad-fiber": "^0.0.79", - "react": "^19.1.0", - "schematic-symbols": "^0.0.165", - "tsup": "^8.2.4", "@tscircuit/checks": "^0.0.56", "@tscircuit/circuit-json-util": "^0.0.51", + "@tscircuit/core": "^0.0.558", "@tscircuit/footprinter": "^0.0.189", "@tscircuit/import-snippet": "^0.0.4", "@tscircuit/infgrid-ijump-astar": "^0.0.33", "@tscircuit/layout": "^0.0.28", "@tscircuit/log-soup": "^1.0.2", + "@tscircuit/math-utils": "^0.0.18", + "@tscircuit/parts-engine": "^0.0.8", "@tscircuit/props": "^0.0.255", "@tscircuit/schematic-autolayout": "^0.0.6", "@tscircuit/schematic-corpus": "^0.0.52", "@tscircuit/schematic-match-adapt": "^0.0.16", "@tscircuit/simple-3d-svg": "^0.0.6", + "@types/babel__standalone": "^7.1.9", + "@types/bun": "^1.2.16", "@types/debug": "^4.1.12", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@types/react-reconciler": "^0.28.9", "bpc-graph": "^0.0.57", "bun-match-svg": "0.0.12", "calculate-elbow": "^0.0.5", "chokidar-cli": "^3.0.0", + "circuit-json": "^0.0.219", "circuit-json-to-bpc": "^0.0.13", "circuit-json-to-connectivity-map": "^0.0.22", "circuit-json-to-simple-3d": "^0.0.2", "circuit-to-svg": "^0.0.162", + "comlink": "^4.4.2", "concurrently": "^9.1.2", "debug": "^4.3.6", + "graphics-debug": "^0.0.60", "howfat": "^0.3.8", + "jscad-fiber": "^0.0.80", "live-server": "^1.2.2", "looks-same": "^9.0.1", - "pkg-pr-new": "^0.0.37", - "react-dom": "^19.1.0", - "ts-expect": "^1.3.0" + "pkg-pr-new": "^0.0.54", + "react": "~19.0.0", + "react-dom": "~19.0.0", + "react-reconciler": "0.31.0", + "schematic-symbols": "^0.0.165", + "ts-expect": "^1.3.0", + "tsup": "^8.2.4" }, "peerDependencies": { "typescript": "^5.0.0", diff --git a/webworker/execution-context.ts b/webworker/execution-context.ts index d26016f..5c81abc 100644 --- a/webworker/execution-context.ts +++ b/webworker/execution-context.ts @@ -24,6 +24,9 @@ export function createExecutionContext( platform?: PlatformConfig } = {}, ): ExecutionContext { + debug("createExecutionContext", { + ReactVersion: React.version, + }) globalThis.React = React const circuit = new RootCircuit({