fritzkit 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/.claude-plugin/marketplace.json +12 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/CHANGELOG.md +8 -0
  4. package/LICENSE +21 -0
  5. package/README.md +429 -2
  6. package/dist/cli/commands/doctor.d.ts +4 -0
  7. package/dist/cli/commands/doctor.js +7 -0
  8. package/dist/cli/commands/doctor.js.map +1 -0
  9. package/dist/cli/commands/export.d.ts +17 -0
  10. package/dist/cli/commands/export.js +81 -0
  11. package/dist/cli/commands/export.js.map +1 -0
  12. package/dist/cli/commands/kicad.d.ts +10 -0
  13. package/dist/cli/commands/kicad.js +22 -0
  14. package/dist/cli/commands/kicad.js.map +1 -0
  15. package/dist/cli/commands/parts.d.ts +22 -0
  16. package/dist/cli/commands/parts.js +46 -0
  17. package/dist/cli/commands/parts.js.map +1 -0
  18. package/dist/cli/commands/prune.d.ts +14 -0
  19. package/dist/cli/commands/prune.js +67 -0
  20. package/dist/cli/commands/prune.js.map +1 -0
  21. package/dist/cli/commands/render.d.ts +24 -0
  22. package/dist/cli/commands/render.js +62 -0
  23. package/dist/cli/commands/render.js.map +1 -0
  24. package/dist/cli/commands/schema.d.ts +10 -0
  25. package/dist/cli/commands/schema.js +35 -0
  26. package/dist/cli/commands/schema.js.map +1 -0
  27. package/dist/cli/commands/ui.d.ts +14 -0
  28. package/dist/cli/commands/ui.js +67 -0
  29. package/dist/cli/commands/ui.js.map +1 -0
  30. package/dist/cli/commands/validate.d.ts +24 -0
  31. package/dist/cli/commands/validate.js +91 -0
  32. package/dist/cli/commands/validate.js.map +1 -0
  33. package/dist/cli/context.d.ts +81 -0
  34. package/dist/cli/context.js +139 -0
  35. package/dist/cli/context.js.map +1 -0
  36. package/dist/cli/main.d.ts +2 -0
  37. package/dist/cli/main.js +8 -0
  38. package/dist/cli/main.js.map +1 -0
  39. package/dist/cli/program.d.ts +21 -0
  40. package/dist/cli/program.js +158 -0
  41. package/dist/cli/program.js.map +1 -0
  42. package/dist/cli/raster.d.ts +4 -0
  43. package/dist/cli/raster.js +9 -0
  44. package/dist/cli/raster.js.map +1 -0
  45. package/dist/core/compile/breadboard-util.d.ts +12 -0
  46. package/dist/core/compile/breadboard-util.js +107 -0
  47. package/dist/core/compile/breadboard-util.js.map +1 -0
  48. package/dist/core/compile/breadboard.d.ts +60 -0
  49. package/dist/core/compile/breadboard.js +513 -0
  50. package/dist/core/compile/breadboard.js.map +1 -0
  51. package/dist/core/compile/fzp.d.ts +12 -0
  52. package/dist/core/compile/fzp.js +183 -0
  53. package/dist/core/compile/fzp.js.map +1 -0
  54. package/dist/core/compile/icon.d.ts +17 -0
  55. package/dist/core/compile/icon.js +27 -0
  56. package/dist/core/compile/icon.js.map +1 -0
  57. package/dist/core/compile/index.d.ts +4 -0
  58. package/dist/core/compile/index.js +62 -0
  59. package/dist/core/compile/index.js.map +1 -0
  60. package/dist/core/compile/pcb.d.ts +39 -0
  61. package/dist/core/compile/pcb.js +164 -0
  62. package/dist/core/compile/pcb.js.map +1 -0
  63. package/dist/core/compile/schematic.d.ts +69 -0
  64. package/dist/core/compile/schematic.js +240 -0
  65. package/dist/core/compile/schematic.js.map +1 -0
  66. package/dist/core/compile/style.d.ts +52 -0
  67. package/dist/core/compile/style.js +72 -0
  68. package/dist/core/compile/style.js.map +1 -0
  69. package/dist/core/compile/types.d.ts +50 -0
  70. package/dist/core/compile/types.js +15 -0
  71. package/dist/core/compile/types.js.map +1 -0
  72. package/dist/core/export/bin.d.ts +61 -0
  73. package/dist/core/export/bin.js +221 -0
  74. package/dist/core/export/bin.js.map +1 -0
  75. package/dist/core/export/fzpz.d.ts +37 -0
  76. package/dist/core/export/fzpz.js +121 -0
  77. package/dist/core/export/fzpz.js.map +1 -0
  78. package/dist/core/export/index.d.ts +5 -0
  79. package/dist/core/export/index.js +6 -0
  80. package/dist/core/export/index.js.map +1 -0
  81. package/dist/core/export/install.d.ts +77 -0
  82. package/dist/core/export/install.js +183 -0
  83. package/dist/core/export/install.js.map +1 -0
  84. package/dist/core/export/prune.d.ts +72 -0
  85. package/dist/core/export/prune.js +165 -0
  86. package/dist/core/export/prune.js.map +1 -0
  87. package/dist/core/fritzing.d.ts +22 -0
  88. package/dist/core/fritzing.js +77 -0
  89. package/dist/core/fritzing.js.map +1 -0
  90. package/dist/core/identity.d.ts +29 -0
  91. package/dist/core/identity.js +70 -0
  92. package/dist/core/identity.js.map +1 -0
  93. package/dist/core/image/bbox.d.ts +27 -0
  94. package/dist/core/image/bbox.js +24 -0
  95. package/dist/core/image/bbox.js.map +1 -0
  96. package/dist/core/image/index.d.ts +8 -0
  97. package/dist/core/image/index.js +9 -0
  98. package/dist/core/image/index.js.map +1 -0
  99. package/dist/core/image/key.d.ts +72 -0
  100. package/dist/core/image/key.js +317 -0
  101. package/dist/core/image/key.js.map +1 -0
  102. package/dist/core/image/prepare.d.ts +31 -0
  103. package/dist/core/image/prepare.js +98 -0
  104. package/dist/core/image/prepare.js.map +1 -0
  105. package/dist/core/image/remover.d.ts +17 -0
  106. package/dist/core/image/remover.js +51 -0
  107. package/dist/core/image/remover.js.map +1 -0
  108. package/dist/core/index.d.ts +16 -0
  109. package/dist/core/index.js +17 -0
  110. package/dist/core/index.js.map +1 -0
  111. package/dist/core/kicad/footprint.d.ts +104 -0
  112. package/dist/core/kicad/footprint.js +317 -0
  113. package/dist/core/kicad/footprint.js.map +1 -0
  114. package/dist/core/kicad/index.d.ts +25 -0
  115. package/dist/core/kicad/index.js +197 -0
  116. package/dist/core/kicad/index.js.map +1 -0
  117. package/dist/core/kicad/sexpr.d.ts +35 -0
  118. package/dist/core/kicad/sexpr.js +163 -0
  119. package/dist/core/kicad/sexpr.js.map +1 -0
  120. package/dist/core/resolve.d.ts +113 -0
  121. package/dist/core/resolve.js +301 -0
  122. package/dist/core/resolve.js.map +1 -0
  123. package/dist/core/schema/part.d.ts +995 -0
  124. package/dist/core/schema/part.js +278 -0
  125. package/dist/core/schema/part.js.map +1 -0
  126. package/dist/core/schema/part.schema.json +1122 -0
  127. package/dist/core/svg.d.ts +20 -0
  128. package/dist/core/svg.js +56 -0
  129. package/dist/core/svg.js.map +1 -0
  130. package/dist/core/units.d.ts +28 -0
  131. package/dist/core/units.js +42 -0
  132. package/dist/core/units.js.map +1 -0
  133. package/dist/core/validate/checker.d.ts +46 -0
  134. package/dist/core/validate/checker.js +142 -0
  135. package/dist/core/validate/checker.js.map +1 -0
  136. package/dist/core/validate/doctor.d.ts +25 -0
  137. package/dist/core/validate/doctor.js +156 -0
  138. package/dist/core/validate/doctor.js.map +1 -0
  139. package/dist/core/validate/explain.d.ts +10 -0
  140. package/dist/core/validate/explain.js +135 -0
  141. package/dist/core/validate/explain.js.map +1 -0
  142. package/dist/core/validate/index.d.ts +21 -0
  143. package/dist/core/validate/index.js +96 -0
  144. package/dist/core/validate/index.js.map +1 -0
  145. package/dist/core/validate/structural.d.ts +74 -0
  146. package/dist/core/validate/structural.js +664 -0
  147. package/dist/core/validate/structural.js.map +1 -0
  148. package/dist/core/workspace.d.ts +69 -0
  149. package/dist/core/workspace.js +213 -0
  150. package/dist/core/workspace.js.map +1 -0
  151. package/dist/mcp/jsonpatch.d.ts +30 -0
  152. package/dist/mcp/jsonpatch.js +145 -0
  153. package/dist/mcp/jsonpatch.js.map +1 -0
  154. package/dist/mcp/main.d.ts +2 -0
  155. package/dist/mcp/main.js +5 -0
  156. package/dist/mcp/main.js.map +1 -0
  157. package/dist/mcp/server.d.ts +28 -0
  158. package/dist/mcp/server.js +310 -0
  159. package/dist/mcp/server.js.map +1 -0
  160. package/dist/server/api.d.ts +72 -0
  161. package/dist/server/api.js +507 -0
  162. package/dist/server/api.js.map +1 -0
  163. package/dist/server/events.d.ts +28 -0
  164. package/dist/server/events.js +134 -0
  165. package/dist/server/events.js.map +1 -0
  166. package/dist/server/http.d.ts +36 -0
  167. package/dist/server/http.js +127 -0
  168. package/dist/server/http.js.map +1 -0
  169. package/dist/server/index.d.ts +65 -0
  170. package/dist/server/index.js +193 -0
  171. package/dist/server/index.js.map +1 -0
  172. package/dist/ui/assets/index-DLU8opyI.js +63 -0
  173. package/dist/ui/assets/index-GhUA-T3b.css +1 -0
  174. package/dist/ui/index.html +14 -0
  175. package/package.json +89 -4
  176. package/skills/fritzkit/SKILL.md +690 -0
  177. package/skills/fritzkit/reference/examples.md +275 -0
  178. package/skills/fritzkit/reference/part-json.md +333 -0
  179. package/skills/fritzkit/scripts/measure.py +197 -0
  180. package/tools/fzp-checker/.python-version +1 -0
  181. package/tools/fzp-checker/README.md +45 -0
  182. package/tools/fzp-checker/check_fzp.py +48 -0
  183. package/tools/fzp-checker/pyproject.toml +26 -0
  184. package/tools/fzp-checker/uv.lock +266 -0
  185. package/tools/fzp-checker/vendor/LICENSE.txt +8 -0
  186. package/tools/fzp-checker/vendor/UPSTREAM.md +25 -0
  187. package/tools/fzp-checker/vendor/fzp_checker.py +14 -0
  188. package/tools/fzp-checker/vendor/scripts/checks/__init__.py +26 -0
  189. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/__init__.cpython-313.pyc +0 -0
  190. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/check_missing_leg_ids.cpython-313.pyc +0 -0
  191. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/connector_numbering.cpython-313.pyc +0 -0
  192. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checker_runner.cpython-313.pyc +0 -0
  193. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_checkers.cpython-313.pyc +0 -0
  194. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_svg_checkers.cpython-313.pyc +0 -0
  195. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/fzp_utils.cpython-313.pyc +0 -0
  196. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_checkers.cpython-313.pyc +0 -0
  197. package/tools/fzp-checker/vendor/scripts/checks/__pycache__/svg_utils.cpython-313.pyc +0 -0
  198. package/tools/fzp-checker/vendor/scripts/checks/check_missing_leg_ids.py +44 -0
  199. package/tools/fzp-checker/vendor/scripts/checks/checkascii.py +27 -0
  200. package/tools/fzp-checker/vendor/scripts/checks/checkcase.py +156 -0
  201. package/tools/fzp-checker/vendor/scripts/checks/checkcopies.py +104 -0
  202. package/tools/fzp-checker/vendor/scripts/checks/connector_numbering.py +79 -0
  203. package/tools/fzp-checker/vendor/scripts/checks/connectors_misnumbered.py +228 -0
  204. package/tools/fzp-checker/vendor/scripts/checks/explain_errors.md +95 -0
  205. package/tools/fzp-checker/vendor/scripts/checks/fzp_checker_runner.py +717 -0
  206. package/tools/fzp-checker/vendor/scripts/checks/fzp_checkers.py +1069 -0
  207. package/tools/fzp-checker/vendor/scripts/checks/fzp_svg_checkers.py +62 -0
  208. package/tools/fzp-checker/vendor/scripts/checks/fzp_utils.py +222 -0
  209. package/tools/fzp-checker/vendor/scripts/checks/inspector_fc51.png +0 -0
  210. package/tools/fzp-checker/vendor/scripts/checks/invisibleconnectors.py +118 -0
  211. package/tools/fzp-checker/vendor/scripts/checks/nolayeridcheck.txt +758 -0
  212. package/tools/fzp-checker/vendor/scripts/checks/requirements.txt +3 -0
  213. package/tools/fzp-checker/vendor/scripts/checks/svg_checkers.py +953 -0
  214. package/tools/fzp-checker/vendor/scripts/checks/svg_utils.py +96 -0
  215. package/tools/fzp-checker/vendor/scripts/checks/test_checkers.py +839 -0
  216. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_missing.fzp.test +3 -0
  217. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/author_present.fzp.test +4 -0
  218. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_id.fzp.test +8 -0
  219. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_missing_node_members.fzp.test +7 -0
  220. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/buses_present_valid.fzp.test +8 -0
  221. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_missing_attributes.fzp.test +16 -0
  222. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_layers_present_valid.fzp.test +14 -0
  223. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_invalid.fzp.test +70 -0
  224. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_numbering_valid.fzp.test +70 -0
  225. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/connector_refs_valid.fzp.test +53 -0
  226. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_mixed.fzp.test +11 -0
  227. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_silkscreen_nested.fzp.test +11 -0
  228. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_style_attr.fzp.test +11 -0
  229. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_white_stroke.fzp.test +11 -0
  230. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_invalid_wrong_color.fzp.test +11 -0
  231. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_inherited.fzp.test +11 -0
  232. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_legacy.fzp.test +12 -0
  233. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_nested_copper.fzp.test +12 -0
  234. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_none.fzp.test +11 -0
  235. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_standard.fzp.test +12 -0
  236. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/copper_color_valid_uppercase.fzp.test +11 -0
  237. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/css_connector.fzp.test +100 -0
  238. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_missing.fzp.test +3 -0
  239. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/description_present.fzp.test +4 -0
  240. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/duplicate_ids.fzp.test +27 -0
  241. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_missing.fzp.test +6 -0
  242. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/family_property_present_valid.fzp.test +6 -0
  243. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/font_size.fzp.test +46 -0
  244. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_invalid_format.fzp.test +4 -0
  245. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_missing.fzp.test +4 -0
  246. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/fritzing_version_present_valid.fzp.test +4 -0
  247. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_absent.fzp.test +15 -0
  248. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/gorn_present.fzp.test +15 -0
  249. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/hybrid_connectors.fzp.test +232 -0
  250. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/invalid_xml.fzp.test +12 -0
  251. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_match.fzp.test +29 -0
  252. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_ids_mismatch.fzp.test +30 -0
  253. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_invalid.fzp.test +11 -0
  254. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/layer_nesting_valid.fzp.test +11 -0
  255. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/matrix_transform.fzp.test +8 -0
  256. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_connector_refs.fzp.test +63 -0
  257. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/missing_leg_ids.fzp.test +34 -0
  258. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_missing.fzp.test +4 -0
  259. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_non_alnum.fzp.test +4 -0
  260. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_present.fzp.test +4 -0
  261. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_absent.fzp.test +4 -0
  262. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_special_chars_present.fzp.test +4 -0
  263. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/module_id_too_short.fzp.test +4 -0
  264. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_invalid.fzp.test +19 -0
  265. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_namedview.fzp.test +19 -0
  266. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/no_layer_valid.fzp.test +19 -0
  267. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/pcb_only.fzp.test +48 -0
  268. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_name.fzp.test +6 -0
  269. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_missing_value.fzp.test +6 -0
  270. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/property_fields_present.fzp.test +6 -0
  271. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_missing.fzp.test +3 -0
  272. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/required_tags_present.fzp.test +11 -0
  273. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/stroke_test.fzp.test +60 -0
  274. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/template_svgs.fzp.test +35 -0
  275. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_missing.fzp.test +3 -0
  276. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/title_present.fzp.test +4 -0
  277. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_ids.fzp.test +27 -0
  278. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_duplicates.fzp.test +7 -0
  279. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/unique_property_names_unique.fzp.test +7 -0
  280. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/valid_xml.fzp.test +249 -0
  281. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_invalid_format.fzp.test +4 -0
  282. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_missing.fzp.test +3 -0
  283. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/version_present_valid.fzp.test +4 -0
  284. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_missing.fzp.test +3 -0
  285. package/tools/fzp-checker/vendor/scripts/checks/test_data/core/views_present.fzp.test +8 -0
  286. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/connector_refs_valid_breadboard.svg +7 -0
  287. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/duplicate_ids_breadboard.svg +13 -0
  288. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_absent_breadboard.svg +7 -0
  289. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/gorn_present_breadboard.svg +7 -0
  290. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/hybrid_connectors_breadboard.svg +102 -0
  291. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_invalid.svg +13 -0
  292. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/layer_nesting_valid.svg +13 -0
  293. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/matrix_transform.svg +11 -0
  294. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_connector_refs_breadboard.svg +8 -0
  295. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/missing_leg_ids_breadboard.svg +9 -0
  296. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_match_breadboard.svg +9 -0
  297. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/test_layer_ids_mismatch_breadboard.svg +9 -0
  298. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/breadboard/unique_ids_breadboard.svg +13 -0
  299. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/hybrid_connectors_icon.svg +78 -0
  300. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_invalid.svg +4 -0
  301. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_namedview.svg +9 -0
  302. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/no_layer_valid.svg +7 -0
  303. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/icon/plain_pcb.svg +10 -0
  304. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/connector_refs_valid_pcb.svg +10 -0
  305. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_mixed.svg +8 -0
  306. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_silkscreen_nested.svg +10 -0
  307. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_style_attr.svg +6 -0
  308. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_white_stroke.svg +6 -0
  309. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_invalid_wrong_color.svg +6 -0
  310. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_inherited.svg +7 -0
  311. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_legacy.svg +10 -0
  312. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_nested_copper.svg +9 -0
  313. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_none.svg +7 -0
  314. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_standard.svg +10 -0
  315. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/copper_color_valid_uppercase.svg +7 -0
  316. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/css_connector.svg +25 -0
  317. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/font_size.svg +54 -0
  318. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/hybrid_connectors_pcb.svg +35 -0
  319. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/missing_connector_refs_pcb.svg +14 -0
  320. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/pcb-arduino-shield.svg +25 -0
  321. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/stroke_test.svg +12 -0
  322. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_match_pcb.svg +12 -0
  323. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/pcb/test_layer_ids_mismatch_pcb.svg +12 -0
  324. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/connector_refs_valid_schematic.svg +7 -0
  325. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/hybrid_connectors_schematic.svg +59 -0
  326. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/missing_connector_refs_schematic.svg +8 -0
  327. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_match_schematic.svg +6 -0
  328. package/tools/fzp-checker/vendor/scripts/checks/test_data/svg/core/schematic/test_layer_ids_mismatch_schematic.svg +6 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Install a part the way Fritzing's own .fzpz import lays it out:
3
+ *
4
+ * <userPartsDir>/<moduleId>/<moduleId>.fzp (the "part." prefix stripped)
5
+ * <userPartsDir>/<moduleId>/<view>/<base>_<view>.svg (the "svg.<view>." prefix stripped)
6
+ *
7
+ * plus an instance in Documents/Fritzing/bins/my_parts.fzb (updateBin, default on).
8
+ * Verified on Fritzing 1.0.7b: the My Parts bin shows only what my_parts.fzb lists (it is
9
+ * not rebuilt from local_parts/user), and an instance whose path is the absolute fzp path
10
+ * makes the part appear there after a restart. Fritzing must be closed: it reads
11
+ * my_parts.fzb and local_parts only at startup and rewrites the bin file on exit.
12
+ */
13
+ import { existsSync } from "node:fs";
14
+ import { resolve, sep } from "node:path";
15
+ import { isFritzingRunning, fritzingPaths } from "../fritzing.js";
16
+ import { MODULE_ID_RE } from "../identity.js";
17
+ import { VIEWS } from "../compile/types.js";
18
+ import { addBinInstance, readBin, writeBin, writeFileAtomic } from "./bin.js";
19
+ import { parseFzpInfo, readFzpz } from "./fzpz.js";
20
+ export class FritzingRunningError extends Error {
21
+ constructor(action = "install") {
22
+ super(`Fritzing is running; ${action} needs Fritzing closed. Fritzing reads my_parts.fzb ` +
23
+ `and local_parts only at startup (it would not see the change) and rewrites the bin ` +
24
+ `file on exit (it would overwrite the my_parts.fzb edit). Close Fritzing and retry, ` +
25
+ `or pass force to do it anyway.`);
26
+ this.name = "FritzingRunningError";
27
+ }
28
+ }
29
+ /** Windows device names, which stay devices even with an extension (e.g. NUL.abcdef). */
30
+ const WINDOWS_DEVICE_RE = /^(con|prn|aux|nul|com\d|lpt\d)(\..*)?$/i;
31
+ /**
32
+ * Throw unless moduleId is a safe single path segment (checked before any fs call). A
33
+ * trailing "." is refused because Windows strips it, so "x-12345678." would alias the
34
+ * folder "x-12345678".
35
+ */
36
+ export function assertSafeModuleId(moduleId) {
37
+ if (typeof moduleId !== "string" ||
38
+ !MODULE_ID_RE.test(moduleId) ||
39
+ /^\.+$/.test(moduleId) ||
40
+ moduleId.endsWith(".") ||
41
+ WINDOWS_DEVICE_RE.test(moduleId)) {
42
+ throw new Error(`unsafe moduleId "${moduleId}": only letters, digits, "-", "_" and "." are allowed ` +
43
+ `(no trailing ".", no Windows device name)`);
44
+ }
45
+ }
46
+ /** Absolute dir of a part inside userPartsDir; throws if it would escape the folder. */
47
+ export function partDirFor(userPartsDir, moduleId) {
48
+ assertSafeModuleId(moduleId);
49
+ const root = resolve(userPartsDir);
50
+ const dir = resolve(root, moduleId);
51
+ if (!dir.startsWith(root + sep) || dir.slice(root.length + 1) !== moduleId) {
52
+ throw new Error(`moduleId "${moduleId}" resolves outside ${root}`);
53
+ }
54
+ return dir;
55
+ }
56
+ /**
57
+ * Entry names are single file names. Third-party parts use spaces, parentheses and "+"
58
+ * (e.g. "Adafruit 4980 NeoKey 1x4 QT I2C"), so only what Windows forbids or what could
59
+ * escape the folder is rejected: / \ : * ? " < > |, control chars, empty dot segments
60
+ * (".." included) and a trailing dot or space.
61
+ */
62
+ // eslint-disable-next-line no-control-regex
63
+ const UNSAFE_NAME_CHARS = /[/\\:*?"<>|\x00-\x1f\x7f]/;
64
+ export function isSafeEntryName(name) {
65
+ return (name !== "" &&
66
+ !UNSAFE_NAME_CHARS.test(name) &&
67
+ !name.split(".").some((s) => s === "") &&
68
+ !/[. ]$/.test(name));
69
+ }
70
+ function fzpOf(entries) {
71
+ const fzps = entries.filter((e) => /\.fzp$/i.test(e.name));
72
+ if (fzps.length !== 1)
73
+ throw new Error(`expected exactly one .fzp entry, got ${fzps.length}`);
74
+ return fzps[0];
75
+ }
76
+ /**
77
+ * Pure: where every entry goes. The folder name is the moduleId read from the fzp
78
+ * content; entry names map `part.X.fzp` -> `X.fzp` and `svg.<view>.Y.svg` -> `<view>/Y.svg`.
79
+ * Rejects any entry name with a path separator, `..` or other unsafe characters (a sign
80
+ * of a hostile archive). Other entries (readme, unknown view) are listed in `ignored` and
81
+ * not written: Fritzing's importer (moveToPartsFolder) likewise only copies part.* and
82
+ * the svg.* files the fzp references, and silently skips the rest.
83
+ */
84
+ export function plannedInstallLayout(entries, paths) {
85
+ const fzpEntry = fzpOf(entries);
86
+ const { moduleId } = parseFzpInfo(new TextDecoder().decode(fzpEntry.data));
87
+ const dir = partDirFor(paths.userPartsDir, moduleId);
88
+ const files = {};
89
+ const ignored = [];
90
+ let fzpPath = "";
91
+ for (const e of entries) {
92
+ if (!isSafeEntryName(e.name)) {
93
+ throw new Error(`unsafe .fzpz entry name "${e.name}"`);
94
+ }
95
+ let target;
96
+ const svg = /^svg\.([a-z]+)\.(.+\.svg)$/i.exec(e.name);
97
+ if (e === fzpEntry) {
98
+ target = resolve(dir, e.name.replace(/^part\./, ""));
99
+ fzpPath = target;
100
+ }
101
+ else if (svg) {
102
+ const view = svg[1];
103
+ if (!VIEWS.includes(view)) {
104
+ ignored.push(e.name);
105
+ continue;
106
+ }
107
+ target = resolve(dir, view, svg[2]);
108
+ }
109
+ else {
110
+ ignored.push(e.name);
111
+ continue;
112
+ }
113
+ if (!target.startsWith(dir + sep))
114
+ throw new Error(`entry "${e.name}" escapes ${dir}`);
115
+ if (Object.values(files).includes(target)) {
116
+ throw new Error(`two entries install to ${target}`);
117
+ }
118
+ files[e.name] = target;
119
+ }
120
+ return { moduleId, dir, fzpPath, files, ignored };
121
+ }
122
+ /**
123
+ * The `path` attribute written into the bin instance. Default: the absolute installed
124
+ * fzp path with forward slashes, verified on Fritzing 1.0.7b to make the part appear in
125
+ * My Parts after a restart. (search.fzb/core.fzb use the bare file name for core parts.)
126
+ */
127
+ export function binInstancePath(fzpAbs, _moduleId) {
128
+ return fzpAbs.replace(/\\/g, "/");
129
+ }
130
+ export async function installPart(source, opts = {}) {
131
+ const paths = opts.paths ?? fritzingPaths();
132
+ const entries = typeof source === "string" ? (await readFzpz(source)).entries : source.entries;
133
+ const layout = plannedInstallLayout(entries, paths);
134
+ // Every image the fzp points at must be one of the files we install.
135
+ const info = parseFzpInfo(new TextDecoder().decode(fzpOf(entries).data));
136
+ const targets = new Set(Object.values(layout.files));
137
+ for (const [view, image] of Object.entries(info.images)) {
138
+ if (!targets.has(resolve(layout.dir, image))) {
139
+ throw new Error(`fzp ${view}View image "${image}" is not among the .fzpz entries`);
140
+ }
141
+ }
142
+ const alreadyInstalled = existsSync(layout.fzpPath);
143
+ const binPath = paths.myPartsBin;
144
+ let newXml;
145
+ if (opts.updateBin !== false) {
146
+ const bin = await readBin(binPath);
147
+ const instancePath = (opts.instancePath ?? binInstancePath)(layout.fzpPath, layout.moduleId);
148
+ const xml = addBinInstance(bin.xml, { moduleIdRef: layout.moduleId, path: instancePath });
149
+ if (xml !== bin.xml || !existsSync(binPath))
150
+ newXml = xml;
151
+ }
152
+ const binUpdated = newXml !== undefined;
153
+ const result = {
154
+ moduleId: layout.moduleId,
155
+ installedDir: layout.dir,
156
+ files: Object.values(layout.files),
157
+ binPath,
158
+ alreadyInstalled,
159
+ binUpdated,
160
+ ignoredEntries: layout.ignored,
161
+ warnings: layout.ignored.map((n) => `ignored .fzpz entry "${n}" (not part.*.fzp or svg.<view>.*.svg; Fritzing skips it too)`),
162
+ };
163
+ if (opts.dryRun)
164
+ return { ...result, skipped: "dry run" };
165
+ if (!opts.force && (await (opts.isRunning ?? isFritzingRunning)())) {
166
+ throw new FritzingRunningError("install");
167
+ }
168
+ for (const e of entries) {
169
+ const target = layout.files[e.name];
170
+ if (target !== undefined)
171
+ await writeFileAtomic(target, e.data);
172
+ }
173
+ if (newXml !== undefined) {
174
+ const bak = await writeBin(binPath, newXml, {
175
+ backup: opts.backupBin !== false,
176
+ now: opts.now,
177
+ });
178
+ if (bak)
179
+ result.binBackupPath = bak;
180
+ }
181
+ return result;
182
+ }
183
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/core/export/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { type FritzingPaths } from "../fritzing.js";
2
+ export interface InstalledPart {
3
+ moduleId: string;
4
+ /** Absolute folder under userPartsDir. */
5
+ dir: string;
6
+ fzpPath: string;
7
+ family?: string;
8
+ variant?: string;
9
+ title?: string;
10
+ /** `<version>` of the fzp, when numeric. */
11
+ version?: number;
12
+ }
13
+ /**
14
+ * Scan userPartsDir: one entry per folder holding an fzp. Uses `<dir>/<dir>.fzp`, else the
15
+ * single *.fzp in the folder. Folders without an fzp (aborted imports), with several
16
+ * candidate fzps, or with an unparsable fzp are skipped. Sorted by folder name.
17
+ */
18
+ export declare function listInstalledParts(paths?: Pick<FritzingPaths, "userPartsDir">): Promise<InstalledPart[]>;
19
+ export interface PrunePlanOptions {
20
+ family: string;
21
+ /** moduleId(s) to keep. Default: keep only the newest member of the family. */
22
+ keep?: string | string[];
23
+ /** Only ever remove moduleIds starting with "fritzkit-" (default true). */
24
+ onlyFritzkit?: boolean;
25
+ }
26
+ export interface PrunePlan {
27
+ remove: InstalledPart[];
28
+ keep: InstalledPart[];
29
+ }
30
+ /**
31
+ * Pure: split the installed members of a family into remove / keep. "Newest" is decided
32
+ * from the fzp content only (`<version>`, then the variant string), never file mtimes,
33
+ * so the plan is deterministic. Parts outside the "fritzkit-" namespace are always kept
34
+ * when onlyFritzkit is on. Parts of other families are ignored. Throws when a keep id
35
+ * is not an installed member of the family.
36
+ */
37
+ export declare function prunePlan(installed: readonly InstalledPart[], opts: PrunePlanOptions): PrunePlan;
38
+ export interface PruneOptions {
39
+ /** Actually delete; without it prune is a dry run. */
40
+ yes: boolean;
41
+ paths?: FritzingPaths;
42
+ /** Prune even while Fritzing is running. */
43
+ force?: boolean;
44
+ /** Remove the pruned parts' instances from my_parts.fzb (default true, see install.ts). */
45
+ updateBin?: boolean;
46
+ /** Back the bin up before writing it (default true). */
47
+ backupBin?: boolean;
48
+ isRunning?: () => Promise<boolean>;
49
+ now?: () => Date;
50
+ /** Injected for tests; defaults to a recursive rm. */
51
+ removeDir?: (dir: string) => Promise<void>;
52
+ }
53
+ export interface PruneResult {
54
+ /** Folders removed (or that would be removed on a dry run). */
55
+ removedDirs: string[];
56
+ /** moduleIds whose bin instances were removed (or would be); empty with updateBin: false. */
57
+ removedBinEntries: string[];
58
+ dryRun: boolean;
59
+ binBackupPath?: string;
60
+ /** Folders that could not be removed; their bin instances are kept. */
61
+ failed: {
62
+ dir: string;
63
+ moduleId: string;
64
+ error: string;
65
+ }[];
66
+ }
67
+ /**
68
+ * Execute a plan. Dry run unless opts.yes. Only deletes a folder whose resolved path is
69
+ * directly under userPartsDir and whose name equals the moduleId being pruned; every
70
+ * check runs for every part before the first deletion.
71
+ */
72
+ export declare function prune(plan: PrunePlan, opts: PruneOptions): Promise<PruneResult>;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * List and prune installed parts. Every export gets a new moduleId (identity D12), so a
3
+ * part iterated N times leaves N folders in local_parts/user; prune removes the old ones
4
+ * of one family together with their my_parts.fzb instances (updateBin, default on).
5
+ */
6
+ import { existsSync } from "node:fs";
7
+ import { readdir, readFile, rm, stat } from "node:fs/promises";
8
+ import { join, resolve, sep } from "node:path";
9
+ import { isFritzingRunning, fritzingPaths } from "../fritzing.js";
10
+ import { readBin, removeBinInstances, writeBin } from "./bin.js";
11
+ import { parseFzpInfo } from "./fzpz.js";
12
+ import { assertSafeModuleId, FritzingRunningError, partDirFor } from "./install.js";
13
+ /**
14
+ * Scan userPartsDir: one entry per folder holding an fzp. Uses `<dir>/<dir>.fzp`, else the
15
+ * single *.fzp in the folder. Folders without an fzp (aborted imports), with several
16
+ * candidate fzps, or with an unparsable fzp are skipped. Sorted by folder name.
17
+ */
18
+ export async function listInstalledParts(paths = fritzingPaths()) {
19
+ const root = resolve(paths.userPartsDir);
20
+ if (!existsSync(root))
21
+ return [];
22
+ const out = [];
23
+ const dirents = await readdir(root, { withFileTypes: true });
24
+ for (const d of dirents.filter((x) => x.isDirectory()).sort((a, b) => cmp(a.name, b.name))) {
25
+ const dir = join(root, d.name);
26
+ const fzps = (await readdir(dir)).filter((f) => /\.fzp$/i.test(f));
27
+ const name = fzps.includes(`${d.name}.fzp`)
28
+ ? `${d.name}.fzp`
29
+ : fzps.length === 1
30
+ ? fzps[0]
31
+ : undefined;
32
+ if (!name)
33
+ continue;
34
+ const fzpPath = join(dir, name);
35
+ try {
36
+ if (!(await stat(fzpPath)).isFile())
37
+ continue;
38
+ const info = parseFzpInfo(await readFile(fzpPath, "utf8"));
39
+ const part = { moduleId: info.moduleId, dir, fzpPath };
40
+ if (info.family !== undefined)
41
+ part.family = info.family;
42
+ if (info.variant !== undefined)
43
+ part.variant = info.variant;
44
+ if (info.title !== undefined)
45
+ part.title = info.title;
46
+ const v = Number(info.version);
47
+ if (info.version !== undefined && Number.isFinite(v))
48
+ part.version = v;
49
+ out.push(part);
50
+ }
51
+ catch {
52
+ continue;
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ function cmp(a, b) {
58
+ return a < b ? -1 : a > b ? 1 : 0;
59
+ }
60
+ /** Version number of a part: `<version>`, else the N of a "vN ..." variant, else 0. */
61
+ function versionOf(p) {
62
+ if (p.version !== undefined)
63
+ return p.version;
64
+ const m = /^v(\d+(?:\.\d+)?)\b/.exec(p.variant ?? "");
65
+ return m ? Number(m[1]) : 0;
66
+ }
67
+ /** Newest first: higher version, then variant string, then moduleId (descending). */
68
+ function newerFirst(a, b) {
69
+ return (versionOf(b) - versionOf(a) ||
70
+ cmp(b.variant ?? "", a.variant ?? "") ||
71
+ cmp(b.moduleId, a.moduleId));
72
+ }
73
+ /**
74
+ * Pure: split the installed members of a family into remove / keep. "Newest" is decided
75
+ * from the fzp content only (`<version>`, then the variant string), never file mtimes,
76
+ * so the plan is deterministic. Parts outside the "fritzkit-" namespace are always kept
77
+ * when onlyFritzkit is on. Parts of other families are ignored. Throws when a keep id
78
+ * is not an installed member of the family.
79
+ */
80
+ export function prunePlan(installed, opts) {
81
+ const onlyFritzkit = opts.onlyFritzkit !== false;
82
+ const members = installed.filter((p) => p.family === opts.family).sort(newerFirst);
83
+ const protectedParts = onlyFritzkit
84
+ ? members.filter((p) => !p.moduleId.startsWith("fritzkit-"))
85
+ : [];
86
+ const candidates = members.filter((p) => !protectedParts.includes(p));
87
+ let keepIds;
88
+ if (opts.keep !== undefined) {
89
+ keepIds = new Set(Array.isArray(opts.keep) ? opts.keep : [opts.keep]);
90
+ // A mistyped keep id would otherwise silently plan the removal of every version.
91
+ const unknown = [...keepIds].filter((id) => !members.some((p) => p.moduleId === id));
92
+ if (unknown.length > 0) {
93
+ throw new Error(`keep: ${unknown.map((id) => `"${id}"`).join(", ")} is not an installed member of ` +
94
+ `family "${opts.family}"`);
95
+ }
96
+ }
97
+ else {
98
+ keepIds = new Set(candidates.slice(0, 1).map((p) => p.moduleId));
99
+ }
100
+ return {
101
+ remove: candidates.filter((p) => !keepIds.has(p.moduleId)),
102
+ keep: members.filter((p) => keepIds.has(p.moduleId) || protectedParts.includes(p)),
103
+ };
104
+ }
105
+ /**
106
+ * Execute a plan. Dry run unless opts.yes. Only deletes a folder whose resolved path is
107
+ * directly under userPartsDir and whose name equals the moduleId being pruned; every
108
+ * check runs for every part before the first deletion.
109
+ */
110
+ export async function prune(plan, opts) {
111
+ const paths = opts.paths ?? fritzingPaths();
112
+ const root = resolve(paths.userPartsDir);
113
+ const targets = [];
114
+ for (const p of plan.remove) {
115
+ assertSafeModuleId(p.moduleId);
116
+ const dir = partDirFor(root, p.moduleId);
117
+ if (resolve(p.dir) !== dir || !dir.startsWith(root + sep)) {
118
+ throw new Error(`refusing to prune ${p.dir}: it is not ${dir} (folder name must equal moduleId)`);
119
+ }
120
+ targets.push({ dir, moduleId: p.moduleId });
121
+ }
122
+ const bin = opts.updateBin !== false ? await readBin(paths.myPartsBin) : undefined;
123
+ const inBin = (ids) => bin
124
+ ? [...new Set(bin.instances.filter((i) => ids.has(i.moduleIdRef)).map((i) => i.moduleIdRef))]
125
+ : [];
126
+ const existing = targets.filter((t) => existsSync(t.dir));
127
+ if (!opts.yes) {
128
+ return {
129
+ removedDirs: existing.map((t) => t.dir),
130
+ removedBinEntries: inBin(new Set(targets.map((t) => t.moduleId))),
131
+ dryRun: true,
132
+ failed: [],
133
+ };
134
+ }
135
+ if (!opts.force && (await (opts.isRunning ?? isFritzingRunning)())) {
136
+ throw new FritzingRunningError("prune");
137
+ }
138
+ // Folders first; only ids whose folder is really gone leave the bin, so a failed rm
139
+ // never leaves a bin that disagrees with the parts folder.
140
+ const removeDir = opts.removeDir ?? ((dir) => rm(dir, { recursive: true, force: true }));
141
+ const result = { removedDirs: [], removedBinEntries: [], dryRun: false, failed: [] };
142
+ const gone = new Set(targets.filter((t) => !existsSync(t.dir)).map((t) => t.moduleId));
143
+ for (const t of existing) {
144
+ try {
145
+ await removeDir(t.dir);
146
+ result.removedDirs.push(t.dir);
147
+ gone.add(t.moduleId);
148
+ }
149
+ catch (e) {
150
+ result.failed.push({ ...t, error: e instanceof Error ? e.message : String(e) });
151
+ }
152
+ }
153
+ result.removedBinEntries = inBin(gone);
154
+ if (bin && result.removedBinEntries.length > 0) {
155
+ const xml = removeBinInstances(bin.xml, (i) => gone.has(i.moduleIdRef));
156
+ const bak = await writeBin(paths.myPartsBin, xml, {
157
+ backup: opts.backupBin !== false,
158
+ now: opts.now,
159
+ });
160
+ if (bak)
161
+ result.binBackupPath = bak;
162
+ }
163
+ return result;
164
+ }
165
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.js","sourceRoot":"","sources":["../../../src/core/export/prune.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export interface FritzingPaths {
2
+ /** Fritzing executable, or undefined when not found. */
3
+ exe?: string;
4
+ /** Folder holding fzp_checker.py and core/, or undefined. */
5
+ partsLibrary?: string;
6
+ /** %APPDATA%/Fritzing/Fritzing/local_parts/user - where imported parts live. */
7
+ userPartsDir: string;
8
+ /** ~/Documents/Fritzing/bins/my_parts.fzb - the My Parts bin. */
9
+ myPartsBin: string;
10
+ binsDir: string;
11
+ }
12
+ export declare function fritzingPaths(env?: NodeJS.ProcessEnv): FritzingPaths;
13
+ export type FritzingRunState = "running" | "stopped" | "unknown";
14
+ /** Whether a Fritzing process is running; "unknown" when the process list cannot be read. */
15
+ export declare function fritzingRunState(): Promise<FritzingRunState>;
16
+ /**
17
+ * True unless Fritzing is known to be stopped. Fails CLOSED: install and prune must not
18
+ * touch Fritzing's user folders when the process list cannot be read.
19
+ */
20
+ export declare function isFritzingRunning(): Promise<boolean>;
21
+ /** File version of Fritzing.exe (Windows), e.g. "1.0.7.0"; undefined elsewhere. */
22
+ export declare function fritzingVersion(exe: string | undefined): Promise<string | undefined>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Where Fritzing lives on this machine and whether it is running. Shared by install,
3
+ * prune, doctor and the e2e harness. Windows first (the only tested platform);
4
+ * macOS/Linux paths are best-effort guesses that doctor reports as unverified.
5
+ */
6
+ import { execFile } from "node:child_process";
7
+ import { existsSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { promisify } from "node:util";
11
+ const execFileP = promisify(execFile);
12
+ export function fritzingPaths(env = process.env) {
13
+ const home = env.USERPROFILE ?? homedir();
14
+ const appData = env.APPDATA ?? join(home, "AppData", "Roaming");
15
+ const candidates = process.platform === "win32"
16
+ ? [
17
+ join(env.ProgramFiles ?? "C:\\Program Files", "Fritzing", "Fritzing.exe"),
18
+ join(env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)", "Fritzing", "Fritzing.exe"),
19
+ join(env.LOCALAPPDATA ?? join(home, "AppData", "Local"), "Programs", "Fritzing", "Fritzing.exe"),
20
+ ]
21
+ : process.platform === "darwin"
22
+ ? ["/Applications/Fritzing.app/Contents/MacOS/Fritzing"]
23
+ : ["/usr/bin/Fritzing", "/usr/local/bin/Fritzing", "/opt/fritzing/Fritzing"];
24
+ const exe = env.FRITZKIT_FRITZING_EXE ?? candidates.find((c) => existsSync(c));
25
+ const partsLibrary = exe
26
+ ? [join(exe, "..", "fritzing-parts"), join(exe, "..", "..", "fritzing-parts")].find((p) => existsSync(join(p, "fzp_checker.py")))
27
+ : undefined;
28
+ const userPartsDir = process.platform === "win32"
29
+ ? join(appData, "Fritzing", "Fritzing", "local_parts", "user")
30
+ : process.platform === "darwin"
31
+ ? join(home, "Library", "Application Support", "Fritzing", "Fritzing", "local_parts", "user")
32
+ : join(home, ".local", "share", "Fritzing", "Fritzing", "local_parts", "user");
33
+ const binsDir = join(home, "Documents", "Fritzing", "bins");
34
+ return { exe, partsLibrary, userPartsDir, binsDir, myPartsBin: join(binsDir, "my_parts.fzb") };
35
+ }
36
+ /** Whether a Fritzing process is running; "unknown" when the process list cannot be read. */
37
+ export async function fritzingRunState() {
38
+ try {
39
+ if (process.platform === "win32") {
40
+ const { stdout } = await execFileP("tasklist", ["/FI", "IMAGENAME eq Fritzing.exe", "/NH"]);
41
+ return /Fritzing\.exe/i.test(stdout) ? "running" : "stopped";
42
+ }
43
+ const { stdout } = await execFileP("pgrep", ["-ix", "fritzing"]);
44
+ return stdout.trim().length > 0 ? "running" : "stopped";
45
+ }
46
+ catch (err) {
47
+ // pgrep exits 1 when nothing matches; anything else means we could not tell.
48
+ const code = err.code;
49
+ if (process.platform !== "win32" && code === 1)
50
+ return "stopped";
51
+ return "unknown";
52
+ }
53
+ }
54
+ /**
55
+ * True unless Fritzing is known to be stopped. Fails CLOSED: install and prune must not
56
+ * touch Fritzing's user folders when the process list cannot be read.
57
+ */
58
+ export async function isFritzingRunning() {
59
+ return (await fritzingRunState()) !== "stopped";
60
+ }
61
+ /** File version of Fritzing.exe (Windows), e.g. "1.0.7.0"; undefined elsewhere. */
62
+ export async function fritzingVersion(exe) {
63
+ if (!exe || process.platform !== "win32")
64
+ return undefined;
65
+ try {
66
+ const { stdout } = await execFileP("powershell", [
67
+ "-NoProfile",
68
+ "-Command",
69
+ `(Get-Item '${exe.replace(/'/g, "''")}').VersionInfo.ProductVersion`,
70
+ ]);
71
+ return stdout.trim() || undefined;
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ //# sourceMappingURL=fritzing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fritzing.js","sourceRoot":"","sources":["../../src/core/fritzing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { PartSpec } from "./schema/part.js";
2
+ export declare const MODULE_ID_RE: RegExp;
3
+ export interface Asset {
4
+ /** Path relative to the part folder, forward slashes, e.g. "assets/top.png". */
5
+ name: string;
6
+ data: Uint8Array;
7
+ }
8
+ export interface PartIdentity {
9
+ moduleId: string;
10
+ family: string;
11
+ variant: string;
12
+ hash8: string;
13
+ /** Base file name used for every SVG and the fzp inside the .fzpz. */
14
+ baseName: string;
15
+ }
16
+ /** JSON with recursively sorted object keys; arrays keep their order. */
17
+ export declare function canonicalJson(value: unknown): string;
18
+ export declare function contentHash(spec: PartSpec, assets?: Asset[]): string;
19
+ export declare function identityFor(spec: PartSpec, assets?: Asset[]): PartIdentity;
20
+ /** Zip entry names inside a .fzpz for a given identity (flat, no folders). */
21
+ export declare function fzpzEntryNames(id: PartIdentity): {
22
+ readonly fzp: `part.${string}.fzp`;
23
+ readonly breadboard: `svg.breadboard.${string}_breadboard.svg`;
24
+ readonly schematic: `svg.schematic.${string}_schematic.svg`;
25
+ readonly pcb: `svg.pcb.${string}_pcb.svg`;
26
+ readonly icon: `svg.icon.${string}_icon.svg`;
27
+ };
28
+ /** The <layers image="..."> value for a view; must match the zip entry after Fritzing's rewrite. */
29
+ export declare function layerImage(id: PartIdentity, view: "breadboard" | "schematic" | "pcb" | "icon"): string;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Part identity (decisions D12 / D12a).
3
+ *
4
+ * moduleId = fritzkit-<slug>-<hash8> unique per exported content; Fritzing refuses a
5
+ * duplicate moduleId, so every change gets a new one
6
+ * family = spec.family ?? slug stable across versions (Inspector swap group)
7
+ * variant = v<version> <hash8> unique per export, differs between family members
8
+ *
9
+ * hash8 = first 8 hex chars of sha256 over the canonical spec JSON (sorted keys,
10
+ * $schema dropped) followed by every asset file (name + bytes, sorted by name).
11
+ */
12
+ import { createHash } from "node:crypto";
13
+ export const MODULE_ID_RE = /^[A-Za-z0-9_.-]{8,}$/;
14
+ /** JSON with recursively sorted object keys; arrays keep their order. */
15
+ export function canonicalJson(value) {
16
+ return JSON.stringify(sortKeys(value));
17
+ }
18
+ function sortKeys(v) {
19
+ if (Array.isArray(v))
20
+ return v.map(sortKeys);
21
+ if (v && typeof v === "object") {
22
+ const out = {};
23
+ for (const k of Object.keys(v).sort()) {
24
+ const val = v[k];
25
+ if (val !== undefined)
26
+ out[k] = sortKeys(val);
27
+ }
28
+ return out;
29
+ }
30
+ return v;
31
+ }
32
+ export function contentHash(spec, assets = []) {
33
+ const h = createHash("sha256");
34
+ const { $schema: _schema, ...rest } = spec;
35
+ h.update(canonicalJson(rest));
36
+ for (const a of [...assets].sort((x, y) => (x.name < y.name ? -1 : x.name > y.name ? 1 : 0))) {
37
+ h.update("\0" + a.name + "\0");
38
+ h.update(a.data);
39
+ }
40
+ return h.digest("hex");
41
+ }
42
+ export function identityFor(spec, assets = []) {
43
+ const hash8 = contentHash(spec, assets).slice(0, 8);
44
+ const moduleId = `fritzkit-${spec.slug}-${hash8}`;
45
+ if (!MODULE_ID_RE.test(moduleId)) {
46
+ throw new Error(`moduleId "${moduleId}" is not filename-safe`);
47
+ }
48
+ return {
49
+ moduleId,
50
+ family: spec.family ?? spec.slug,
51
+ variant: `v${spec.version} ${hash8}`,
52
+ hash8,
53
+ baseName: moduleId,
54
+ };
55
+ }
56
+ /** Zip entry names inside a .fzpz for a given identity (flat, no folders). */
57
+ export function fzpzEntryNames(id) {
58
+ return {
59
+ fzp: `part.${id.moduleId}.fzp`,
60
+ breadboard: `svg.breadboard.${id.baseName}_breadboard.svg`,
61
+ schematic: `svg.schematic.${id.baseName}_schematic.svg`,
62
+ pcb: `svg.pcb.${id.baseName}_pcb.svg`,
63
+ icon: `svg.icon.${id.baseName}_icon.svg`,
64
+ };
65
+ }
66
+ /** The <layers image="..."> value for a view; must match the zip entry after Fritzing's rewrite. */
67
+ export function layerImage(id, view) {
68
+ return `${view}/${id.baseName}_${view}.svg`;
69
+ }
70
+ //# sourceMappingURL=identity.js.map