From 248b4838a72b8cd9d46d2b77c0fe3abdafaa81ff Mon Sep 17 00:00:00 2001 From: Dr Alex Mitre <30060514+mitre88@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:04:31 -0600 Subject: [PATCH 1/2] feat: dynamically load export converters from jsdelivr CDN --- bun.lock | 148 +------------ lib/components/BomTable/BomTable.tsx | 6 +- lib/components/BomTable/bom-table.types.ts | 16 +- .../exporting/dynamic-converters.ts | 204 ++++++++++++++++++ .../formats/export-fabrication-files.ts | 36 ++-- .../exporting/formats/export-glb.ts | 4 +- .../exporting/formats/export-kicad-library.ts | 6 +- .../exporting/formats/export-kicad-project.ts | 14 +- .../exporting/formats/export-lbrn.ts | 4 +- .../exporting/formats/export-step.ts | 7 +- package.json | 6 - tests/dynamic-converters.test.ts | 188 ++++++++++++++++ 12 files changed, 453 insertions(+), 186 deletions(-) create mode 100644 lib/optional-features/exporting/dynamic-converters.ts create mode 100644 tests/dynamic-converters.test.ts diff --git a/bun.lock b/bun.lock index a615ea1a7..362cb9508 100644 --- a/bun.lock +++ b/bun.lock @@ -41,12 +41,6 @@ "autoprefixer": "^10.4.20", "chokidar-cli": "^3.0.0", "circuit-json": "^0.0.454", - "circuit-json-to-bom-csv": "^0.0.9", - "circuit-json-to-gerber": "^0.0.85", - "circuit-json-to-kicad": "^0.0.171", - "circuit-json-to-lbrn": "^0.0.74", - "circuit-json-to-pnp-csv": "^0.0.7", - "circuit-json-to-step": "^0.0.36", "circuit-to-svg": "^0.0.393", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -172,8 +166,6 @@ "@edge-runtime/vm": ["@edge-runtime/vm@3.2.0", "", { "dependencies": { "@edge-runtime/primitives": "4.1.0" } }, "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], - "@emotion/babel-plugin": ["@emotion/babel-plugin@11.13.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ=="], "@emotion/cache": ["@emotion/cache@11.14.0", "", { "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA=="], @@ -258,62 +250,6 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="], - "@gltf-transform/core": ["@gltf-transform/core@4.4.1", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-ygWWFOrj1HnfvBimHF1yHTY4abbeBjn3CvljijaWKoRLMRV/a8pWYQu9rfMFQhfpT5kYZTD5Mp+dizAXPbiTDQ=="], - - "@gltf-transform/extensions": ["@gltf-transform/extensions@4.4.1", "", { "dependencies": { "@gltf-transform/core": "^4.4.1", "ktx-parse": "^1.1.0" } }, "sha512-dZZ9D/NdpNeJUmQKExtISYtd3W6OxU4njk8UI3IKm6j97uVskYQ24BNi2YP40uUpdWPiREsS/DhjNhWAbGU/1A=="], - - "@gltf-transform/functions": ["@gltf-transform/functions@4.4.1", "", { "dependencies": { "@gltf-transform/core": "^4.4.1", "@gltf-transform/extensions": "^4.4.1", "ktx-parse": "^1.1.0", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-CAU6hczuRz7NIEtpn7BARuwVEwRawwIcGqmoMCaEwA4XC+CSUi3xptXuf2zDG/5AftFO7IeQxXl/56rDsck0GQ=="], - - "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -334,8 +270,6 @@ "@jscad/stl-serializer": ["@jscad/stl-serializer@2.1.23", "", { "dependencies": { "@jscad/array-utils": "2.1.4", "@jscad/modeling": "2.13.0" } }, "sha512-bSaz2hte30ig2kT1ogBQIjQVGD0Nu1dDyQ4q+fHBFKKmJ8A7ximnFZj7hgQegm3XUD6pSE5KwzPWspCyRWQ5kA=="], - "@jscadui/3mf-export": ["@jscadui/3mf-export@0.5.0", "", {}, "sha512-y5vZktqCjyi7wA38zqNlLIdZUIRZoOO9vCjLzwmL4bR0hk7B/Zm1IeffzJPFe1vFc0C1IEv3hm8caDv3doRk9g=="], - "@lume/kiwi": ["@lume/kiwi@0.4.4", "", {}, "sha512-ie0YTKgiZqD4TXlJ4eUbfi4UEoKs6YlLRYNTfPm5eUXwfudTBmPRs7Qcxz2SWKDpVTwThv3sWG6zwtyAA0nPpw=="], "@nodable/entities": ["@nodable/entities@3.0.0", "", {}, "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw=="], @@ -632,8 +566,6 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/ndarray": ["@types/ndarray@1.0.14", "", {}, "sha512-oANmFZMnFQvb219SSBIhI1Ih/r4CvHDOzkWyJS/XRqkMrGH5/kaPSA1hQhdIBzouaE+5KpE/f5ylI9cujmckQg=="], - "@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="], "@types/offscreencanvas": ["@types/offscreencanvas@2019.7.3", "", {}, "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="], @@ -798,28 +730,16 @@ "circuit-json": ["circuit-json@0.0.454", "", { "peerDependencies": { "format-si-unit": "*" } }, "sha512-e/l/ueZ6y2kyPTWG/M2oqsNc0L7wYsduVpurdaUdtzXVQD65m9jkmMtxKID2gtUrgmalKfuEltT6AFbzbooKcA=="], - "circuit-json-to-bom-csv": ["circuit-json-to-bom-csv@0.0.9", "", { "dependencies": { "format-si-prefix": "^0.3.2", "papaparse": "^5.4.1" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-FbpYQW40WjpTJle1aCzHQ9yu/L0XgIRPWEfCYANcsg7zakP3Q/pEwr80uCJ5PkdnOStF1z3P3SomOH94z26EMg=="], - "circuit-json-to-bpc": ["circuit-json-to-bpc@0.0.13", "", { "peerDependencies": { "bpc-graph": "*", "circuit-json": "*", "typescript": "^5" } }, "sha512-3wSMtPa6tJkiBQN4tsm7f0Mb7Wp90X2c8dNbULoDVE4mGGoFqP1DXqBlyvvZZl+4SjqznzQQ0EioLe2SCQTOcg=="], - "circuit-json-to-connectivity-map": ["circuit-json-to-connectivity-map@0.0.22", "", { "dependencies": { "@tscircuit/math-utils": "^0.0.9" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-HN8DiISjZZLTglGEkYNRpKeQ/DMG4dDo5j4Hck0UGSJbpux9aFwtJOGszMf06Inh/gu5oKBrpZJIeWxaNacKUg=="], - - "circuit-json-to-gerber": ["circuit-json-to-gerber@0.0.85", "", { "dependencies": { "@tscircuit/alphabet": "^0.0.25", "fast-json-stable-stringify": "^2.1.0", "polygon-clipping": "^0.15.7", "transformation-matrix": "^3.0.0" }, "peerDependencies": { "circuit-json": "*", "tscircuit": "*", "typescript": "^5.0.0" }, "bin": { "circuit-to-gerber": "dist/cli.js" } }, "sha512-a9nBmCf1DAb78svcP80OrWwgLlbqMsh/9LKQyvgoFilOBvIqtdJDstdKyTM4dII1F0TKPdSWqntLAnuxEut2rA=="], + "circuit-json-to-connectivity-map": ["circuit-json-to-connectivity-map@0.0.23", "", { "dependencies": { "@tscircuit/math-utils": "^0.0.9" }, "peerDependencies": { "typescript": "^5.9.3" } }, "sha512-DSOiXaXOTvjU+7et8ITXb2LjgKto6cQzLv3hReYdXuUNtLw2GVnpOly1G83VcIBcSQ4hRVHI4VMKRyZB3XVzdg=="], - "circuit-json-to-gltf": ["circuit-json-to-gltf@0.0.102", "", { "dependencies": { "@jscad/modeling": "^2.12.6", "earcut": "^3.0.2", "jscad-electronics": "^0.0.135", "jscad-to-gltf": "^0.0.5", "occt-import-js": "^0.0.23" }, "peerDependencies": { "@resvg/resvg-js": "2", "@resvg/resvg-wasm": "2", "@tscircuit/circuit-json-util": "*", "circuit-json": "*", "circuit-to-svg": "*", "typescript": "^5" }, "optionalPeers": ["@resvg/resvg-js", "@resvg/resvg-wasm"] }, "sha512-1q9r3pgxwB13gIQQdDUG2kxg3qdg8UzSGd1Taraf8xxKoD0Dq0zk3Xq5Ei9L+WvTknrT2wnux6+ILCOXdHJgtA=="], - - "circuit-json-to-kicad": ["circuit-json-to-kicad@0.0.171", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-c8mncdvVQ3H+++3Hh2+fkZOpNmFZ9jEM7m2ZV4j6C6yKAR2+VQGD52jT5Vp+hZMWOAy0zE6nwu4sd7cBXf2gPg=="], - - "circuit-json-to-lbrn": ["circuit-json-to-lbrn@0.0.74", "", { "dependencies": { "lbrnts": "^0.0.18", "manifold-3d": "^3.3.2" }, "peerDependencies": { "typescript": "^5" } }, "sha512-MVdlgj+TYHrTdcE2IN7yk6rkULJ02owAr9zeZg9UeypfrdjNNAjBvhz3Ue24FDGjSmvQdmjiafbUKAOapslxZQ=="], - - "circuit-json-to-pnp-csv": ["circuit-json-to-pnp-csv@0.0.7", "", { "dependencies": { "papaparse": "^5.4.1" }, "peerDependencies": { "@tscircuit/soup-util": "*", "typescript": "^5.0.0" } }, "sha512-WAdNRHtaPhQM8X5NN/43WMBKDCEKQSLShg/mHIZxMUzJviymnfbq6rJj/2WvDqm/bogey34PyTEHwF3mC7zxlQ=="], + "circuit-json-to-gltf": ["circuit-json-to-gltf@0.0.107", "", { "dependencies": { "@jscad/modeling": "^2.12.6", "earcut": "^3.0.2", "jscad-electronics": "^0.0.135", "jscad-planner": "^0.0.14", "jscad-to-gltf": "^0.0.5", "occt-import-js": "^0.0.23" }, "peerDependencies": { "@resvg/resvg-js": "2", "@resvg/resvg-wasm": "2", "@tscircuit/circuit-json-util": "*", "circuit-json": "*", "circuit-to-svg": "*", "typescript": "^5" }, "optionalPeers": ["@resvg/resvg-js", "@resvg/resvg-wasm"] }, "sha512-YuER0b6d+bYk/S/FAClS3RPW3ipo/CQb+OoNZiNkNqObsuRh6N/UxQeo/M1NXh+b0j2tfeL0nlSIkfSmKx9lyQ=="], "circuit-json-to-simple-3d": ["circuit-json-to-simple-3d@0.0.9", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-thpCtDb9LpAQfGO+Z0hae19sxVAmJAMYM/It9UTMCtyXC3zoUHwRcp/oqtMO/ZIW+JDBhiR8AHmmtKScL2kW7Q=="], "circuit-json-to-spice": ["circuit-json-to-spice@0.0.43", "", { "dependencies": { "circuit-json-to-connectivity-map": "^0.0.22" }, "peerDependencies": { "@tscircuit/circuit-json-util": "*", "circuit-json": "*", "typescript": "^5.0.0" } }, "sha512-lYXOplosjiYhpWe8IUJ8s9E9G0OH4c+Ygn60lnpco1TEjwD+byUTp3dCk6qDw5VNG5QUdGUD1pe95HoFMzL/TA=="], - "circuit-json-to-step": ["circuit-json-to-step@0.0.36", "", { "dependencies": { "circuit-json-to-connectivity-map": "^0.0.22", "circuit-json-to-gltf": "^0.0.102", "schematic-symbols": "^0.0.202", "stepts": "^0.0.4" }, "peerDependencies": { "@tscircuit/circuit-json-util": "*", "circuit-to-svg": "*", "typescript": "^5" } }, "sha512-p6TPhEwXplX9QMyPw9xgYoHSlMHD+xozDs2Mu6TaEoDYYs09KSWkmn81woyq60Fud0iO11p983/ED/a5IX7xrA=="], - "circuit-to-canvas": ["circuit-to-canvas@0.0.111", "", { "dependencies": { "transformation-matrix": "^3.1.0" }, "peerDependencies": { "@tscircuit/alphabet": "*", "typescript": "^5" } }, "sha512-UueWYe8ZRtMql8FbT8BtFXiUFz35Fl5/wsh4hgmZ7AUU/3xeyS2jXGLc4E7HxnC6zvy4pyrTTRS0YQd2DjgvgA=="], "circuit-to-svg": ["circuit-to-svg@0.0.393", "", { "dependencies": { "@types/node": "^22.5.5", "bun-types": "^1.1.40", "calculate-elbow": "0.0.12", "debug": "^4.4.3", "svg-path-commander": "^2.1.11", "svgson": "^5.3.1", "transformation-matrix": "^2.16.1" }, "peerDependencies": { "@tscircuit/alphabet": "*" } }, "sha512-wyPi94yex6lYMbXpNHyaTzrZM8qqH0nNUJDCl1bzQlpc9Q6P92aU6SwAHEkuxRAmhs3Yk1kS1DZLT9ci3KafDA=="], @@ -856,7 +776,7 @@ "comlink": ["comlink@4.4.2", "", {}, "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g=="], - "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], @@ -916,8 +836,6 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "cwise-compiler": ["cwise-compiler@1.1.3", "", { "dependencies": { "uniq": "^1.0.0" } }, "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], @@ -1002,8 +920,6 @@ "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], - "esbuild-wasm": ["esbuild-wasm@0.27.7", "", { "bin": { "esbuild": "bin/esbuild" } }, "sha512-1k03e2/tGz+sLz3/xzoZmUsIqtaGIvJa8k4UqUeqCUry83nHmlxQYZUUES0WBFUYilSQUf7nDUGAciIIklljSg=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], @@ -1070,8 +986,6 @@ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "format-si-prefix": ["format-si-prefix@0.3.2", "", { "dependencies": { "parseunit": "^0" } }, "sha512-gtCZh4RpmlmEZtyzyvs+FXXWOmdfpQQ0M7mjc81zpAYm5QpsoUDPKhAK+Lj7fJCtZSJpE5xbpCYgspCBxahObQ=="], - "format-si-unit": ["format-si-unit@0.0.7", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-wTi2TqGqPG9LvUwhshiMlaert+1kWtxMEzIpKIkVbmOz4bopcDtqpDKV06JT6xpGIquOSFbkV1fQoMHq6UJZ1g=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -1164,8 +1078,6 @@ "iobuffer": ["iobuffer@6.0.1", "", {}, "sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q=="], - "iota-array": ["iota-array@1.0.0", "", {}, "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA=="], - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], @@ -1258,12 +1170,8 @@ "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], - "ktx-parse": ["ktx-parse@1.1.0", "", {}, "sha512-mKp3y+FaYgR7mXWAbyyzpa/r1zDWeaunH+INJO4fou3hb45XuNSwar+7llrRyvpMWafxSIi99RNFJ05MHedaJQ=="], - "ky": ["ky@1.14.3", "", {}, "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw=="], - "lbrnts": ["lbrnts@0.0.18", "", { "dependencies": { "fast-xml-parser": "^5.3.2" }, "peerDependencies": { "typescript": "^5" } }, "sha512-udieyaH2VxEIHP3bgwjSyL+kYj5gl0p2Dv41+cWfWowPHcyWC9QlUa0vK9PHqMaz+bMheg+8IyosarGfkVanJA=="], - "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], @@ -1300,8 +1208,6 @@ "make-vfs": ["make-vfs@1.1.0", "", { "bin": { "make-vfs": "dist/cli.js" } }, "sha512-rIotyFrjN7wzjVvLu1eh8YBv/K4Ttkl2zKwvj9RZ34/X/x4v2hLl4cC2VDzIUBk4jemKUr8VDWtEA6R1OGEZOw=="], - "manifold-3d": ["manifold-3d@3.5.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.0", "@gltf-transform/extensions": "^4.2.0", "@gltf-transform/functions": "^4.2.0", "@jridgewell/resolve-uri": "^3.1.2", "@jridgewell/trace-mapping": "^0.3.31", "@jscadui/3mf-export": "^0.5.0", "commander": "^13.1.0", "convert-source-map": "^2.0.0", "fast-xml-parser": "^5.4.2", "fflate": "^0.8.0", "magic-string": "^0.30.21" }, "peerDependencies": { "esbuild-wasm": "^0.27.3" }, "bin": { "manifold-cad": "bin/manifold-cad" } }, "sha512-/+m6kxYMMhnPutcQ5oSmFJiJ+gyP/0fmuUCb9Qeaunvecm/bfqogKYDDJarsnWiFioSMtKheF+lGmSlnYCik9g=="], - "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], "match-sorter": ["match-sorter@6.4.0", "", { "dependencies": { "@babel/runtime": "^7.23.8", "remove-accents": "0.5.0" } }, "sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q=="], @@ -1366,14 +1272,6 @@ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - "ndarray": ["ndarray@1.0.19", "", { "dependencies": { "iota-array": "^1.0.0", "is-buffer": "^1.0.2" } }, "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ=="], - - "ndarray-lanczos": ["ndarray-lanczos@0.3.0", "", { "dependencies": { "@types/ndarray": "^1.0.11", "ndarray": "^1.0.19" } }, "sha512-5kBmmG3Zvyj77qxIAC4QFLKuYdDIBJwCG+DukT6jQHNa1Ft74/hPH1z5mbQXeHBt8yvGPBGVrr3wEOdJPYYZYg=="], - - "ndarray-ops": ["ndarray-ops@1.2.2", "", { "dependencies": { "cwise-compiler": "^1.0.0" } }, "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw=="], - - "ndarray-pixels": ["ndarray-pixels@5.0.1", "", { "dependencies": { "@types/ndarray": "^1.0.14", "ndarray": "^1.0.19", "ndarray-ops": "^1.2.2", "sharp": "^0.34.0" } }, "sha512-IBtrpefpqlI8SPDCGjXk4v5NV5z7r3JSuCbfuEEXaM0vrOJtNGgYUa4C3Lt5H+qWdYF4BCPVFsnXhNC7QvZwkw=="], - "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], "nested-error-stacks": ["nested-error-stacks@2.1.1", "", {}, "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw=="], @@ -1430,8 +1328,6 @@ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - "papaparse": ["papaparse@5.5.4", "", {}, "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ=="], - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-color": ["parse-color@1.0.0", "", { "dependencies": { "color-convert": "~0.5.0" } }, "sha512-fuDHYgFHJGbpGMgw9skY/bj3HL/Jrn4l/5rSspy00DoT4RyLnDcRvPxdZ+r6OFwIsgAuhDh4I09tAId4mI12bw=="], @@ -1442,8 +1338,6 @@ "parsel-js": ["parsel-js@1.2.3", "", {}, "sha512-kJHFmSNnujpusFfVEXjNaoJ09VYHF8Ih0aVUn7Clu+Ug8AFoA2wx7Ezh17JfW4+vhm/lauAd9A+//uFkmydZtQ=="], - "parseunit": ["parseunit@0.3.1", "", {}, "sha512-kKtTgCJDsktOHsnvRR/B5DhWi3a/RZ5M9XjVQZGbOatcam+/tk4sMOCQ1WM9Rswfr78zWGBgHNbWTyJvfR0ejg=="], - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], @@ -1490,8 +1384,6 @@ "polished": ["polished@4.3.1", "", { "dependencies": { "@babel/runtime": "^7.17.8" } }, "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA=="], - "polygon-clipping": ["polygon-clipping@0.15.7", "", { "dependencies": { "robust-predicates": "^3.0.2", "splaytree": "^3.1.0" } }, "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA=="], - "poppygl": ["poppygl@0.0.26", "", { "dependencies": { "fast-png": "^8.0.0", "gl-matrix": "^3.4.4", "pureimage": "^0.4.18", "readable-stream": "^4.7.0" }, "peerDependencies": { "typescript": "^5" } }, "sha512-5jnQuKpDCfFT0b9aCm/AidGdzwjw/I3obFpc10j66FY7ZtDPMRekHYU76BzfKDyV5iU8BBWQzxX+8lS9+EEkbg=="], "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], @@ -1580,8 +1472,6 @@ "promise-make-naked": ["promise-make-naked@3.0.2", "", {}, "sha512-B+b+kQ1YrYS7zO7P7bQcoqqMUizP06BOyNSBEnB5VJKDSWo8fsVuDkfSmwdjF0JsRtaNh83so5MMFJ95soH5jg=="], - "property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], @@ -1678,8 +1568,6 @@ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], - "rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="], "rollup-plugin-dts": ["rollup-plugin-dts@6.4.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.5.5", "convert-source-map": "^2.0.0", "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.29.0" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0 || ^6.0" } }, "sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg=="], @@ -1756,8 +1644,6 @@ "spicey": ["spicey@0.0.14", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-iktSN1BteKEpou3158v25fxGJsNZ4MFeBVV0bZFZ5UZ5NnqusVxVQ1ZrPnCO+2FCmTIBqTD66n0GKMxF6+N/xg=="], - "splaytree": ["splaytree@3.2.3", "", {}, "sha512-7OXrNWzy6CK+r7Ch9OLPBDTKfB6XlWHjX4P0RU5B3IgFuWPeYN0XtRtlexGRjgbQxpfaUve6jTAwBGWuGntz/w=="], - "stack-svgs": ["stack-svgs@0.0.1", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-qcqBlwd5DZdXB+jEc+JUoh3o5zVYfjLrrgtwn7GCvjnoEGaXMPjWQnRotkDNh+uhpXVO7ssNln/JbNnLtIFNQw=="], "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], @@ -1876,8 +1762,6 @@ "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], - "uniq": ["uniq@1.0.1", "", {}, "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA=="], - "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], "unload": ["unload@2.2.0", "", { "dependencies": { "@babel/runtime": "^7.6.2", "detect-node": "^2.0.4" } }, "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA=="], @@ -2016,13 +1900,9 @@ "circuit-json-to-connectivity-map/@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.9", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="], - "circuit-json-to-gerber/@tscircuit/alphabet": ["@tscircuit/alphabet@0.0.25", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-PWLjptI6AlLEtF/wjN1N8uC+n3G7vtg0j3xKE1fgWHDhahtnlQRqHDrtPSLlkIR9aJjRfjplzLuaUEaCRvJmZA=="], - - "circuit-json-to-gerber/transformation-matrix": ["transformation-matrix@3.1.0", "", {}, "sha512-oYubRWTi2tYFHAL2J8DLvPIqIYcYZ0fSOi2vmSy042Ho4jBW2ce6VP7QfD44t65WQz6bw5w1Pk22J7lcUpaTKA=="], + "circuit-json-to-gltf/jscad-planner": ["jscad-planner@0.0.14", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-HrS5C1iTrmIZDlvNk065vg36qjIHyeiil60MyW7wccNGaGJSy2SYgWqPNvLU58tUTTTKWECWgd1Wzah80z4Z3A=="], - "circuit-json-to-step/schematic-symbols": ["schematic-symbols@0.0.202", "", { "peerDependencies": { "typescript": "^5.5.4" } }, "sha512-zMdY7VaEg2Sc25T0h9LkWttEoyxGamgBfFDQKUXtYRoLSChrNDOKbNLaxU/GH2L2GbsasV8OLiHyHGb5u7NUpg=="], - - "circuit-json-to-step/stepts": ["stepts@0.0.4", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-DCGT5bOKy1Ol1uugHsRr50zHrv9S3wwNt87M/kx9hqOb0W8EG0zgqNP4lcXiA4W3qooP+l/pHL32R7ok8wAIsQ=="], + "circuit-json-to-spice/circuit-json-to-connectivity-map": ["circuit-json-to-connectivity-map@0.0.22", "", { "dependencies": { "@tscircuit/math-utils": "^0.0.9" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-HN8DiISjZZLTglGEkYNRpKeQ/DMG4dDo5j4Hck0UGSJbpux9aFwtJOGszMf06Inh/gu5oKBrpZJIeWxaNacKUg=="], "circuit-to-canvas/transformation-matrix": ["transformation-matrix@3.1.0", "", {}, "sha512-oYubRWTi2tYFHAL2J8DLvPIqIYcYZ0fSOi2vmSy042Ho4jBW2ce6VP7QfD44t65WQz6bw5w1Pk22J7lcUpaTKA=="], @@ -2082,14 +1962,10 @@ "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], - "manifold-3d/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "mime-types/mime-db": ["mime-db@1.33.0", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="], - "ndarray-pixels/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], - "node-abi/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -2162,8 +2038,6 @@ "stylehacks/postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="], - "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], @@ -2180,10 +2054,6 @@ "tscircuit/circuit-json": ["circuit-json@0.0.453", "", { "peerDependencies": { "format-si-unit": "*" } }, "sha512-BxdRuD6WPlUXfwQjwZzX0dA9V6CsmusH5SK4UT34oheCU/XPGi+SZuUHfUTqvKd+V8LcCa68+Hjkdld7x4BG1Q=="], - "tscircuit/circuit-json-to-connectivity-map": ["circuit-json-to-connectivity-map@0.0.23", "", { "dependencies": { "@tscircuit/math-utils": "^0.0.9" }, "peerDependencies": { "typescript": "^5.9.3" } }, "sha512-DSOiXaXOTvjU+7et8ITXb2LjgKto6cQzLv3hReYdXuUNtLw2GVnpOly1G83VcIBcSQ4hRVHI4VMKRyZB3XVzdg=="], - - "tscircuit/circuit-json-to-gltf": ["circuit-json-to-gltf@0.0.107", "", { "dependencies": { "@jscad/modeling": "^2.12.6", "earcut": "^3.0.2", "jscad-electronics": "^0.0.135", "jscad-planner": "^0.0.14", "jscad-to-gltf": "^0.0.5", "occt-import-js": "^0.0.23" }, "peerDependencies": { "@resvg/resvg-js": "2", "@resvg/resvg-wasm": "2", "@tscircuit/circuit-json-util": "*", "circuit-json": "*", "circuit-to-svg": "*", "typescript": "^5" }, "optionalPeers": ["@resvg/resvg-js", "@resvg/resvg-wasm"] }, "sha512-YuER0b6d+bYk/S/FAClS3RPW3ipo/CQb+OoNZiNkNqObsuRh6N/UxQeo/M1NXh+b0j2tfeL0nlSIkfSmKx9lyQ=="], - "tscircuit/circuit-to-svg": ["circuit-to-svg@0.0.391", "", { "dependencies": { "@types/node": "^22.5.5", "bun-types": "^1.1.40", "calculate-elbow": "0.0.12", "debug": "^4.4.3", "svg-path-commander": "^2.1.11", "svgson": "^5.3.1", "transformation-matrix": "^2.16.1" }, "peerDependencies": { "@tscircuit/alphabet": "*" } }, "sha512-TMh2vieRiFXQ0Pjkp9eCDT6oyyJ0BGYtZHDfzZNubcJH5JjYNTV9116BG6Vkv+Lot2nz3aUnZplg9+4Z6gTJIQ=="], "tscircuit/kicadts": ["kicadts@0.0.51", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-QQjMRad5zcfvoj8+8UI11bc5vLvboru9jbNYvsJ/9e35QyFJ5x3DmyJCDJp7GE8s7UdUmwct5QRT13VbSCbLOA=="], @@ -2248,6 +2118,8 @@ "chokidar-cli/yargs/yargs-parser": ["yargs-parser@13.1.2", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg=="], + "circuit-json-to-spice/circuit-json-to-connectivity-map/@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.9", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="], + "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "connectivity-map/@biomejs/biome/@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kUrAhXVWUrwmAUnV2iXSK7umxKFysTwvqK+Ty6ptUcLY/7T3SnCAjUowE4uvwaEej6nXZ7hu/dTtbokKdsPeag=="], @@ -2276,8 +2148,6 @@ "glob/minimatch/brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], - "ndarray-pixels/sharp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], "ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -2306,10 +2176,6 @@ "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "tscircuit/circuit-json-to-connectivity-map/@tscircuit/math-utils": ["@tscircuit/math-utils@0.0.9", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="], - - "tscircuit/circuit-json-to-gltf/jscad-planner": ["jscad-planner@0.0.14", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-HrS5C1iTrmIZDlvNk065vg36qjIHyeiil60MyW7wccNGaGJSy2SYgWqPNvLU58tUTTTKWECWgd1Wzah80z4Z3A=="], - "tsup/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], diff --git a/lib/components/BomTable/BomTable.tsx b/lib/components/BomTable/BomTable.tsx index 526161739..4496146d1 100644 --- a/lib/components/BomTable/BomTable.tsx +++ b/lib/components/BomTable/BomTable.tsx @@ -1,6 +1,6 @@ -import importer from "@tscircuit/internal-dynamic-import" import { useEffect, useState } from "react" import type React from "react" +import { loadBomCsvConverter } from "../../optional-features/exporting/dynamic-converters" import { getBomCellDescriptors, getBomMetadata } from "./bom-table.columns" import type { BomRow, BomTableProps } from "./bom-table.types" @@ -16,9 +16,7 @@ export const BomTable: React.FC = ({ circuitJson }) => { setError(null) setRows(null) - const { convertCircuitJsonToBomRows } = await importer( - "circuit-json-to-bom-csv", - ) + const { convertCircuitJsonToBomRows } = await loadBomCsvConverter() const bomRows = await convertCircuitJsonToBomRows({ circuitJson, }) diff --git a/lib/components/BomTable/bom-table.types.ts b/lib/components/BomTable/bom-table.types.ts index e118fa9bf..8204106f2 100644 --- a/lib/components/BomTable/bom-table.types.ts +++ b/lib/components/BomTable/bom-table.types.ts @@ -1,14 +1,22 @@ import type { AnyCircuitElement } from "circuit-json" -import type { convertCircuitJsonToBomRows } from "circuit-json-to-bom-csv" import type React from "react" export interface BomTableProps { circuitJson: AnyCircuitElement[] } -export type BomRow = Awaited< - ReturnType ->[number] +export type BomRow = { + designator?: string + comment?: string + value?: string + footprint?: string + supplier_part_number_columns?: Record + extra_columns?: Record + manufacturer_mpn_pairs?: Array<{ + manufacturer: string + mpn: string + }> +} export type BomMetadata = { extraColumnNames: string[] diff --git a/lib/optional-features/exporting/dynamic-converters.ts b/lib/optional-features/exporting/dynamic-converters.ts new file mode 100644 index 000000000..b5185cbc7 --- /dev/null +++ b/lib/optional-features/exporting/dynamic-converters.ts @@ -0,0 +1,204 @@ +/** + * Centralized dynamic converter loader. + * + * Instead of bundling heavy converter packages, each converter is lazy-loaded + * on demand from the jsdelivr CDN when the user actually requests an export. + * Modules are cached after first successful load; failed loads are cleared + * so a retry can succeed. + */ + +// --------------------------------------------------------------------------- +// Module type declarations +// --------------------------------------------------------------------------- + +type GerberConverterModule = { + stringifyGerberCommandLayers: (...args: any[]) => Record + convertSoupToGerberCommands: (...args: any[]) => any + convertSoupToExcellonDrillCommands: (...args: any[]) => any + stringifyExcellonDrill: (...args: any[]) => string +} + +type BomCsvConverterModule = { + convertCircuitJsonToBomRows: (...args: any[]) => Promise | any[] + convertBomRowsToCsv: (...args: any[]) => Promise | string +} + +type PnpCsvConverterModule = { + convertCircuitJsonToPickAndPlaceCsv: ( + ...args: any[] + ) => Promise | string +} + +type KicadConverterModule = { + CircuitJsonToKicadPcbConverter: new ( + ...args: any[] + ) => { + runUntilFinished: () => void + getOutputString: () => string + getModel3dSourcePaths: () => string[] + } + CircuitJsonToKicadSchConverter: new ( + ...args: any[] + ) => { + runUntilFinished: () => void + getOutputString: () => string + } + CircuitJsonToKicadProConverter: new ( + ...args: any[] + ) => { + runUntilFinished: () => void + getOutputString: () => string + } + CircuitJsonToKicadLibraryConverter: new ( + ...args: any[] + ) => { + runUntilFinished: () => void + getOutput: () => { + kicadSymString: string + footprints: Array<{ footprintName: string; kicadModString: string }> + model3dSourcePaths: string[] + fpLibTableString: string + symLibTableString: string + } + } + resolveAndLoadKicad3dModelFiles: (...args: any[]) => Promise +} + +type GltfConverterModule = { + convertCircuitJsonToGltf: ( + ...args: any[] + ) => Promise | ArrayBuffer +} + +type StepConverterModule = { + circuitJsonToStep: (...args: any[]) => Promise | string +} + +type LbrnConverterModule = { + convertCircuitJsonToLbrn: (...args: any[]) => Promise<{ toXml: () => string }> +} + +// --------------------------------------------------------------------------- +// Package → type mapping +// --------------------------------------------------------------------------- + +type ConverterModules = { + "circuit-json-to-gerber": GerberConverterModule + "circuit-json-to-bom-csv": BomCsvConverterModule + "circuit-json-to-pnp-csv": PnpCsvConverterModule + "circuit-json-to-kicad": KicadConverterModule + "circuit-json-to-gltf": GltfConverterModule + "circuit-json-to-step": StepConverterModule + "circuit-json-to-lbrn": LbrnConverterModule +} + +export type ConverterPackageName = keyof ConverterModules + +// --------------------------------------------------------------------------- +// Pinned CDN URLs (jsdelivr ESM) +// --------------------------------------------------------------------------- + +const CONVERTER_CDN_URLS: Record = { + "circuit-json-to-gerber": + "https://cdn.jsdelivr.net/npm/circuit-json-to-gerber@0.0.76/+esm", + "circuit-json-to-bom-csv": + "https://cdn.jsdelivr.net/npm/circuit-json-to-bom-csv@0.0.9/+esm", + "circuit-json-to-pnp-csv": + "https://cdn.jsdelivr.net/npm/circuit-json-to-pnp-csv@0.0.8/+esm", + "circuit-json-to-kicad": + "https://cdn.jsdelivr.net/npm/circuit-json-to-kicad@0.0.147/+esm", + "circuit-json-to-gltf": + "https://cdn.jsdelivr.net/npm/circuit-json-to-gltf@0.0.102/+esm", + "circuit-json-to-step": + "https://cdn.jsdelivr.net/npm/circuit-json-to-step@0.0.33/+esm", + "circuit-json-to-lbrn": + "https://cdn.jsdelivr.net/npm/circuit-json-to-lbrn@0.0.82/+esm", +} + +// --------------------------------------------------------------------------- +// Loader with singleton-promise cache + failure retry +// --------------------------------------------------------------------------- + +export type ConverterModuleImporter = ( + specifier: string, +) => Promise + +declare global { + var tscircuitDynamicModules: Record | undefined +} + +const converterModulePromises = new Map< + ConverterPackageName, + Promise +>() + +const importConverterModule: ConverterModuleImporter = (specifier) => + import(/* @vite-ignore */ specifier) + +export const getConverterCdnUrl = (packageName: ConverterPackageName) => + CONVERTER_CDN_URLS[packageName] + +export const clearConverterModuleCache = () => { + converterModulePromises.clear() +} + +const registerConverterModule = ( + packageName: TName, + module: ConverterModules[TName], +) => { + globalThis.tscircuitDynamicModules ??= {} + globalThis.tscircuitDynamicModules[packageName] = module + return module +} + +export const loadConverterModule = async ( + packageName: TName, + importer: ConverterModuleImporter = importConverterModule, +): Promise => { + let modulePromise = converterModulePromises.get(packageName) as + | Promise + | undefined + + if (!modulePromise) { + modulePromise = importer( + getConverterCdnUrl(packageName), + ) + .then((module) => registerConverterModule(packageName, module)) + .catch((error) => { + // Clear cache on failure so next call retries + converterModulePromises.delete(packageName) + throw error + }) + converterModulePromises.set( + packageName, + modulePromise as Promise, + ) + } + + return modulePromise +} + +// --------------------------------------------------------------------------- +// Convenience loaders (one per converter) +// --------------------------------------------------------------------------- + +export const loadGerberConverter = () => + loadConverterModule("circuit-json-to-gerber") + +export const loadBomCsvConverter = () => + loadConverterModule("circuit-json-to-bom-csv") + +export const loadPnpCsvConverter = () => + loadConverterModule("circuit-json-to-pnp-csv") + +export const loadKicadConverter = () => + loadConverterModule("circuit-json-to-kicad") + +export const loadGltfConverter = () => + loadConverterModule("circuit-json-to-gltf") + +export const loadStepConverter = () => + loadConverterModule("circuit-json-to-step") + +export const loadLbrnConverter = () => + loadConverterModule("circuit-json-to-lbrn") diff --git a/lib/optional-features/exporting/formats/export-fabrication-files.ts b/lib/optional-features/exporting/formats/export-fabrication-files.ts index 9ea5fdbb0..7f5f6bffa 100644 --- a/lib/optional-features/exporting/formats/export-fabrication-files.ts +++ b/lib/optional-features/exporting/formats/export-fabrication-files.ts @@ -1,7 +1,11 @@ -import importer from "@tscircuit/internal-dynamic-import" import type { AnyCircuitElement } from "circuit-json" import JSZip from "jszip" import { toast } from "lib/utils/toast" +import { + loadGerberConverter, + loadBomCsvConverter, + loadPnpCsvConverter, +} from "../dynamic-converters" import { openForDownload } from "../open-for-download" export const exportFabricationFiles = async ({ @@ -15,20 +19,17 @@ export const exportFabricationFiles = async ({ (async () => { const zip = new JSZip() - const [ - { - stringifyGerberCommandLayers, - convertSoupToGerberCommands, - convertSoupToExcellonDrillCommands, - stringifyExcellonDrill, - }, - { convertCircuitJsonToBomRows, convertBomRowsToCsv }, - { convertCircuitJsonToPickAndPlaceCsv }, - ] = await Promise.all([ - importer("circuit-json-to-gerber"), - importer("circuit-json-to-bom-csv"), - importer("circuit-json-to-pnp-csv"), + const [gerberMod, bomMod, pnpMod] = await Promise.all([ + loadGerberConverter(), + loadBomCsvConverter(), + loadPnpCsvConverter(), ]) + const { + stringifyGerberCommandLayers, + convertSoupToGerberCommands, + convertSoupToExcellonDrillCommands, + stringifyExcellonDrill, + } = gerberMod // Filter out error and warning elements for gerber/drill generation const filteredCircuitJson = circuitJson.filter( @@ -57,12 +58,13 @@ export const exportFabricationFiles = async ({ zip.file("gerber/drill.drl", drillFileContents) // Generate BOM CSV - const bomRows = await convertCircuitJsonToBomRows({ circuitJson }) - const bomCsv = await convertBomRowsToCsv(bomRows) + const bomRows = await bomMod.convertCircuitJsonToBomRows({ circuitJson }) + const bomCsv = await bomMod.convertBomRowsToCsv(bomRows) zip.file("bom.csv", bomCsv) // Generate Pick and Place CSV - const pnpCsv = await convertCircuitJsonToPickAndPlaceCsv(circuitJson) + const pnpCsv = + await pnpMod.convertCircuitJsonToPickAndPlaceCsv(circuitJson) zip.file("pick_and_place.csv", pnpCsv) // Generate and download the zip file diff --git a/lib/optional-features/exporting/formats/export-glb.ts b/lib/optional-features/exporting/formats/export-glb.ts index 11351306b..ceddb76c7 100644 --- a/lib/optional-features/exporting/formats/export-glb.ts +++ b/lib/optional-features/exporting/formats/export-glb.ts @@ -1,6 +1,6 @@ import type { CircuitJson } from "circuit-json" import { openForDownload } from "../open-for-download" -import importer from "@tscircuit/internal-dynamic-import" +import { loadGltfConverter } from "../dynamic-converters" export const exportGlb = async ({ circuitJson, @@ -11,7 +11,7 @@ export const exportGlb = async ({ }) => { let blob: Blob try { - const { convertCircuitJsonToGltf } = await importer("circuit-json-to-gltf") + const { convertCircuitJsonToGltf } = await loadGltfConverter() console.log("convertCircuitJsonToGltf", convertCircuitJsonToGltf) diff --git a/lib/optional-features/exporting/formats/export-kicad-library.ts b/lib/optional-features/exporting/formats/export-kicad-library.ts index 725c243c5..8849d5c54 100644 --- a/lib/optional-features/exporting/formats/export-kicad-library.ts +++ b/lib/optional-features/exporting/formats/export-kicad-library.ts @@ -1,7 +1,7 @@ -import importer from "@tscircuit/internal-dynamic-import" import type { CircuitJson } from "circuit-json" import JSZip from "jszip" import { toast } from "lib/utils/toast" +import { loadKicadConverter } from "../dynamic-converters" import { openForDownload } from "../open-for-download" export const createKicadLibraryZip = async ({ @@ -11,9 +11,7 @@ export const createKicadLibraryZip = async ({ circuitJson: CircuitJson libraryName: string }) => { - const { CircuitJsonToKicadLibraryConverter } = await importer( - "circuit-json-to-kicad", - ) + const { CircuitJsonToKicadLibraryConverter } = await loadKicadConverter() const libConverter = new CircuitJsonToKicadLibraryConverter( circuitJson as any, { diff --git a/lib/optional-features/exporting/formats/export-kicad-project.ts b/lib/optional-features/exporting/formats/export-kicad-project.ts index ccd72624d..aebcef8d8 100644 --- a/lib/optional-features/exporting/formats/export-kicad-project.ts +++ b/lib/optional-features/exporting/formats/export-kicad-project.ts @@ -1,7 +1,7 @@ -import importer from "@tscircuit/internal-dynamic-import" import type { CircuitJson } from "circuit-json" import JSZip from "jszip" import { toast } from "lib/utils/toast" +import { loadKicadConverter } from "../dynamic-converters" import { openForDownload } from "../open-for-download" export const createKicadProjectZip = async ({ @@ -16,7 +16,7 @@ export const createKicadProjectZip = async ({ CircuitJsonToKicadSchConverter, CircuitJsonToKicadProConverter, resolveAndLoadKicad3dModelFiles, - } = await importer("circuit-json-to-kicad") + } = await loadKicadConverter() const schConverter = new CircuitJsonToKicadSchConverter(circuitJson as any) schConverter.runUntilFinished() const schContent = schConverter.getOutputString() @@ -46,10 +46,16 @@ export const createKicadProjectZip = async ({ projectName, model3dSourcePaths: pcbConverter.getModel3dSourcePaths(), fetch, - onModelFile: ({ outputPath, content }) => { + onModelFile: ({ + outputPath, + content, + }: { + outputPath: string + content: string | ArrayBuffer | Blob | Uint8Array + }) => { zip.file(outputPath, content) }, - onError: ({ sourcePath }) => { + onError: ({ sourcePath }: { sourcePath: string }) => { console.warn(`Failed to load 3D model from ${sourcePath}`) }, }) diff --git a/lib/optional-features/exporting/formats/export-lbrn.ts b/lib/optional-features/exporting/formats/export-lbrn.ts index 987162546..6fd1006c8 100644 --- a/lib/optional-features/exporting/formats/export-lbrn.ts +++ b/lib/optional-features/exporting/formats/export-lbrn.ts @@ -1,6 +1,6 @@ import type { CircuitJson } from "circuit-json" import { openForDownload } from "../open-for-download" -import importer from "@tscircuit/internal-dynamic-import" +import { loadLbrnConverter } from "../dynamic-converters" import { toast } from "lib/utils/toast" export interface LbrnExportOptions { @@ -19,7 +19,7 @@ export const exportLbrn = async ({ }) => { try { // Convert Circuit JSON to LBRN format - const { convertCircuitJsonToLbrn } = await importer("circuit-json-to-lbrn") + const { convertCircuitJsonToLbrn } = await loadLbrnConverter() const lbrnProject = await convertCircuitJsonToLbrn(circuitJson, { includeSilkscreen: options.includeSilkscreen ?? false, includeOxidationCleaningLayer: diff --git a/lib/optional-features/exporting/formats/export-step.ts b/lib/optional-features/exporting/formats/export-step.ts index abb468c51..ed77c45d6 100644 --- a/lib/optional-features/exporting/formats/export-step.ts +++ b/lib/optional-features/exporting/formats/export-step.ts @@ -1,7 +1,7 @@ import type { CircuitJson } from "circuit-json" import { openForDownload } from "../open-for-download" import { toast } from "lib/utils/toast" -import importer from "@tscircuit/internal-dynamic-import" +import { loadGltfConverter, loadStepConverter } from "../dynamic-converters" export const exportStep = async ({ circuitJson, @@ -10,7 +10,10 @@ export const exportStep = async ({ circuitJson: CircuitJson projectName: string }) => { - const { circuitJsonToStep } = await importer("circuit-json-to-step") + const [{ circuitJsonToStep }] = await Promise.all([ + loadStepConverter(), + loadGltfConverter(), + ]) // Extract board dimensions from circuit JSON const pcbBoard = circuitJson.find((el) => el.type === "pcb_board") diff --git a/package.json b/package.json index 21fab56ba..2d880b66a 100644 --- a/package.json +++ b/package.json @@ -62,12 +62,6 @@ "@vitejs/plugin-react": "^5.0.0", "autoprefixer": "^10.4.20", "chokidar-cli": "^3.0.0", - "circuit-json-to-bom-csv": "^0.0.9", - "circuit-json-to-gerber": "^0.0.85", - "circuit-json-to-kicad": "^0.0.171", - "circuit-json-to-lbrn": "^0.0.74", - "circuit-json-to-pnp-csv": "^0.0.7", - "circuit-json-to-step": "^0.0.36", "circuit-json": "^0.0.454", "circuit-to-svg": "^0.0.393", "class-variance-authority": "^0.7.1", diff --git a/tests/dynamic-converters.test.ts b/tests/dynamic-converters.test.ts new file mode 100644 index 000000000..a5ce02666 --- /dev/null +++ b/tests/dynamic-converters.test.ts @@ -0,0 +1,188 @@ +import { describe, expect, test, beforeEach } from "bun:test" +import { + getConverterCdnUrl, + loadConverterModule, + clearConverterModuleCache, + type ConverterPackageName, + type ConverterModuleImporter, +} from "../lib/optional-features/exporting/dynamic-converters" + +describe("dynamic converter CDN URLs", () => { + test("all converter packages have pinned jsdelivr ESM URLs", () => { + const packages: ConverterPackageName[] = [ + "circuit-json-to-gerber", + "circuit-json-to-bom-csv", + "circuit-json-to-pnp-csv", + "circuit-json-to-kicad", + "circuit-json-to-gltf", + "circuit-json-to-step", + "circuit-json-to-lbrn", + ] + + for (const pkg of packages) { + const url = getConverterCdnUrl(pkg) + expect(url).toStartWith("https://cdn.jsdelivr.net/npm/") + expect(url).toEndWith("/+esm") + // Must have pinned version (no @latest) + expect(url).not.toContain("@latest") + // Must contain the package name + expect(url).toContain(pkg) + } + }) + + test("URLs contain specific version pins", () => { + const url = getConverterCdnUrl("circuit-json-to-gerber") + // Should match pattern: @X.Y.Z + expect(url).toMatch(/@\d+\.\d+\.\d+/) + }) +}) + +describe("dynamic converter loading", () => { + beforeEach(() => { + clearConverterModuleCache() + delete globalThis.tscircuitDynamicModules + }) + + test("caches successful loads (only imports once)", async () => { + const importedSpecifiers: string[] = [] + const mockImporter: ConverterModuleImporter = async ( + specifier: string, + ) => { + importedSpecifiers.push(specifier) + return { + convertCircuitJsonToBomRows: () => [], + convertBomRowsToCsv: () => "", + } as TModule + } + + const [first, second] = await Promise.all([ + loadConverterModule("circuit-json-to-bom-csv", mockImporter), + loadConverterModule("circuit-json-to-bom-csv", mockImporter), + ]) + + expect(first).toBe(second) + // Only one import call despite two concurrent loads + expect(importedSpecifiers).toHaveLength(1) + expect(importedSpecifiers[0]).toBe( + getConverterCdnUrl("circuit-json-to-bom-csv"), + ) + }) + + test("clears cache on failure so retry can succeed", async () => { + let attempts = 0 + const flakyImporter: ConverterModuleImporter = async () => { + attempts += 1 + if (attempts === 1) throw new Error("CDN timeout") + return { + convertCircuitJsonToBomRows: () => [], + convertBomRowsToCsv: () => "", + } as TModule + } + + // First call fails + await expect( + loadConverterModule("circuit-json-to-bom-csv", flakyImporter), + ).rejects.toThrow("CDN timeout") + + // Second call retries and succeeds + const mod = await loadConverterModule( + "circuit-json-to-bom-csv", + flakyImporter, + ) + expect(mod).toBeDefined() + expect(mod.convertCircuitJsonToBomRows).toBeFunction() + expect(attempts).toBe(2) + }) + + test("clearConverterModuleCache resets all cached modules", async () => { + let calls = 0 + const countingImporter: ConverterModuleImporter = async () => { + calls += 1 + return { + stringifyGerberCommandLayers: () => ({}), + convertSoupToGerberCommands: () => ({}), + convertSoupToExcellonDrillCommands: () => ({}), + stringifyExcellonDrill: () => "", + } as TModule + } + + await loadConverterModule("circuit-json-to-gerber", countingImporter) + expect(calls).toBe(1) + + // Cached — no new import + await loadConverterModule("circuit-json-to-gerber", countingImporter) + expect(calls).toBe(1) + + // Clear cache — forces re-import + clearConverterModuleCache() + await loadConverterModule("circuit-json-to-gerber", countingImporter) + expect(calls).toBe(2) + }) + + test("registers loaded modules for converter dependencies that use the legacy registry", async () => { + const mockGltfModule = { + convertCircuitJsonToGltf: () => new ArrayBuffer(0), + } + const mockImporter: ConverterModuleImporter = async () => + mockGltfModule as TModule + + const mod = await loadConverterModule("circuit-json-to-gltf", mockImporter) + + expect(mod).toBe(mockGltfModule) + expect(globalThis.tscircuitDynamicModules).toBeDefined() + expect(globalThis.tscircuitDynamicModules?.["circuit-json-to-gltf"]).toBe( + mockGltfModule, + ) + }) +}) + +describe("no static converter imports in export paths", () => { + test("export files do not statically import converter packages", async () => { + const fs = await import("node:fs") + const path = await import("node:path") + + const exportsDir = path.join( + import.meta.dir, + "../lib/optional-features/exporting/formats", + ) + + const files = fs.readdirSync(exportsDir).filter((f) => f.endsWith(".ts")) + + const converterPackages = [ + "circuit-json-to-gerber", + "circuit-json-to-bom-csv", + "circuit-json-to-pnp-csv", + "circuit-json-to-kicad", + "circuit-json-to-gltf", + "circuit-json-to-step", + "circuit-json-to-lbrn", + ] + + for (const file of files) { + const content = fs.readFileSync(path.join(exportsDir, file), "utf-8") + for (const pkg of converterPackages) { + const hasStaticImport = + content.includes(`from "${pkg}"`) || content.includes(`from '${pkg}'`) + expect(hasStaticImport).toBe(false) + } + } + }) + + test("BomTable does not statically import converter packages", async () => { + const fs = await import("node:fs") + const path = await import("node:path") + + const bomDir = path.join(import.meta.dir, "../lib/components/BomTable") + const files = fs + .readdirSync(bomDir) + .filter((f) => f.endsWith(".ts") || f.endsWith(".tsx")) + + for (const file of files) { + const content = fs.readFileSync(path.join(bomDir, file), "utf-8") + const hasStaticImport = + content.includes(`from "circuit-json-to-bom-csv"`) || + content.includes(`from 'circuit-json-to-bom-csv'`) + expect(hasStaticImport).toBe(false) + } + }) +}) From cd6785bbea842a97c7dff08881f4440c798dd24b Mon Sep 17 00:00:00 2001 From: Dr Alex Mitre <30060514+mitre88@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:41:15 -0600 Subject: [PATCH 2/2] fix: tolerate optional GLTF preload failures --- .../exporting/formats/export-step.ts | 16 ++++++++++++---- tests/dynamic-converters.test.ts | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/optional-features/exporting/formats/export-step.ts b/lib/optional-features/exporting/formats/export-step.ts index ed77c45d6..bd5aeaf88 100644 --- a/lib/optional-features/exporting/formats/export-step.ts +++ b/lib/optional-features/exporting/formats/export-step.ts @@ -3,6 +3,17 @@ import { openForDownload } from "../open-for-download" import { toast } from "lib/utils/toast" import { loadGltfConverter, loadStepConverter } from "../dynamic-converters" +export const loadStepExportConverter = async ( + stepLoader = loadStepConverter, + gltfLoader = loadGltfConverter, +) => { + const [stepConverter] = await Promise.all([ + stepLoader(), + gltfLoader().catch(() => undefined), + ]) + return stepConverter +} + export const exportStep = async ({ circuitJson, projectName, @@ -10,10 +21,7 @@ export const exportStep = async ({ circuitJson: CircuitJson projectName: string }) => { - const [{ circuitJsonToStep }] = await Promise.all([ - loadStepConverter(), - loadGltfConverter(), - ]) + const { circuitJsonToStep } = await loadStepExportConverter() // Extract board dimensions from circuit JSON const pcbBoard = circuitJson.find((el) => el.type === "pcb_board") diff --git a/tests/dynamic-converters.test.ts b/tests/dynamic-converters.test.ts index a5ce02666..cf7198b77 100644 --- a/tests/dynamic-converters.test.ts +++ b/tests/dynamic-converters.test.ts @@ -6,6 +6,7 @@ import { type ConverterPackageName, type ConverterModuleImporter, } from "../lib/optional-features/exporting/dynamic-converters" +import { loadStepExportConverter } from "../lib/optional-features/exporting/formats/export-step" describe("dynamic converter CDN URLs", () => { test("all converter packages have pinned jsdelivr ESM URLs", () => { @@ -185,4 +186,19 @@ describe("no static converter imports in export paths", () => { expect(hasStaticImport).toBe(false) } }) + + test("STEP exports tolerate optional GLTF preload failures", async () => { + const stepConverter = { + circuitJsonToStep: () => "STEP output", + } + + const loaded = await loadStepExportConverter( + async () => stepConverter, + async () => { + throw new Error("GLTF CDN blocked") + }, + ) + + expect(loaded).toBe(stepConverter) + }) })