From de4fca728225f42315e7cf252c8ca29209606d25 Mon Sep 17 00:00:00 2001 From: IllagerCaptain <76600137+IllagerCaptain@users.noreply.github.com> Date: Sat, 4 Apr 2026 12:42:54 -0500 Subject: [PATCH 01/47] Use encompassing collider for `minecraft:selection_box` --- js/formats/bedrock/bedrock_voxel_shape.ts | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/js/formats/bedrock/bedrock_voxel_shape.ts b/js/formats/bedrock/bedrock_voxel_shape.ts index 9187e4637..9838c89ef 100644 --- a/js/formats/bedrock/bedrock_voxel_shape.ts +++ b/js/formats/bedrock/bedrock_voxel_shape.ts @@ -189,7 +189,32 @@ BARS.defineActions(function() { size: bb.size() } }); - if (type == 'selection_box') box_data.length = 1; + if (type == 'selection_box') { + let { origin, size } = box_data[0]; + let minX = origin[0]; + let minY = origin[1]; + let minZ = origin[2]; + let maxX = origin[0] + size[0]; + let maxY = origin[1] + size[1]; + let maxZ = origin[2] + size[2]; + for (let i = 1; i < box_data.length; i++) { + const { origin, size } = box_data[i]; + minX = Math.min(minX, origin[0]); + minY = Math.min(minY, origin[1]); + minZ = Math.min(minZ, origin[2]); + maxX = Math.max(maxX, origin[0] + size[0]); + maxY = Math.max(maxY, origin[1] + size[1]); + maxZ = Math.max(maxZ, origin[2] + size[2]); + } + box_data = [{ + origin: [minX, minY, minZ], + size: [ + maxX - minX, + maxY - minY, + maxZ - minZ + ] + }] + }; let data = box_data as any; if (box_data.length == 1 && box_data[0].origin.equals([-8, 0, -8]) && box_data[0].size.equals([16, 16, 16])) { data = true; From 47d5a56b68e0d7cf28d2d5285c3e00132f3063a5 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 00:21:09 +0200 Subject: [PATCH 02/47] Fix #3467 mesh deformation inconsistent when switching away from animation mode --- js/animations/animation_mode.js | 8 ++++---- js/outliner/types/mesh.js | 2 +- js/texturing/painter.js | 2 +- package-lock.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/js/animations/animation_mode.js b/js/animations/animation_mode.js index 1fd1a6b80..9fe50d2ab 100644 --- a/js/animations/animation_mode.js +++ b/js/animations/animation_mode.js @@ -81,9 +81,9 @@ export const Animator = { Timeline.pause() Animator.open = false; - scene.remove(WinterskyScene.space); - scene.remove(Animator.motion_trail); - scene.remove(Animator.onion_skin_object); + Canvas.scene.remove(WinterskyScene.space); + Canvas.scene.remove(Animator.motion_trail); + Canvas.scene.remove(Animator.onion_skin_object); Animator.resetParticles(true); three_grid.position.z = three_grid.position.x; @@ -113,7 +113,7 @@ export const Animator = { } } Blockbench.dispatchEvent('display_default_pose', {reduced_updates}); - if (!reduced_updates) scene.updateMatrixWorld() + if (!reduced_updates) Canvas.scene.updateMatrixWorld() }, resetParticles(optimized) { for (var path in Animator.particle_effects) { diff --git a/js/outliner/types/mesh.js b/js/outliner/types/mesh.js index 30e36571a..28d216dde 100644 --- a/js/outliner/types/mesh.js +++ b/js/outliner/types/mesh.js @@ -1138,7 +1138,7 @@ new NodePreviewController(Mesh, { } } } - if (Modes.animate && vertex_offsets) { + if (vertex_offsets) { for (let key in faces) { let face = faces[key]; if (face.vertices.length <= 2) continue; diff --git a/js/texturing/painter.js b/js/texturing/painter.js index d27cac1c6..565ef4ebd 100644 --- a/js/texturing/painter.js +++ b/js/texturing/painter.js @@ -2361,7 +2361,7 @@ BARS.defineActions(function() { three_grid.visible = false; }, onUnselect: () => { - Canvas.updateAllBones() + Animator.showDefaultPose(); Outliner.elements.forEach(cube => { if (cube.preview_controller.updatePixelGrid) cube.preview_controller.updatePixelGrid(cube); }) diff --git a/package-lock.json b/package-lock.json index 8efcfc0fd..7c6059a79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "Blockbench", - "version": "5.1.1", + "version": "5.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Blockbench", - "version": "5.1.1", + "version": "5.1.2", "license": "GPL-3.0-or-later", "dependencies": { "@electron/remote": "^2.1.2", From 68e21723b9cdaf842c48c9900dcc9d30cc1b1016 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 00:26:09 +0200 Subject: [PATCH 03/47] Fix #3465 Dragging groups in outliner does not display number of dragged nodes --- js/outliner/outliner.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index be88a5db3..973d0150d 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -1519,10 +1519,12 @@ Interface.definePanels(function() { Interface.createElement('label', {}, item.name) ]); - if (item instanceof Group == false && Outliner.selected.length > 1) { + let all_nodes = Outliner.selected.concat(Group.selected); + let count = all_nodes.filter(node => node.parent == Outliner.ROOT || node.parent.selected == false).length; + if (count > 1) { let counter = document.createElement('div'); counter.classList.add('outliner_drag_number'); - counter.textContent = Outliner.selected.length.toString(); + counter.textContent = count.toString(); helper.append(counter); } document.body.append(helper); From 2e1a7e854c4929bc82cf9e46cb598c1418e051bd Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 13:02:26 +0200 Subject: [PATCH 04/47] Improve painting performance on simple meshes --- js/interface/about.ts | 1 + js/outliner/types/mesh.js | 20 +++++++++++++++----- js/uv/uv.js | 11 ++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/js/interface/about.ts b/js/interface/about.ts index 29967c738..8da2f5f6d 100644 --- a/js/interface/about.ts +++ b/js/interface/about.ts @@ -140,6 +140,7 @@ BARS.defineActions(() => {

Released under the GPL 3.0 license

Privacy Policy

+ egg ` } diff --git a/js/outliner/types/mesh.js b/js/outliner/types/mesh.js index 28d216dde..c6c6292c4 100644 --- a/js/outliner/types/mesh.js +++ b/js/outliner/types/mesh.js @@ -134,7 +134,7 @@ export class MeshFace extends Face { face.uv[vkey][0] * factor_x, face.uv[vkey][1] * factor_y, ]; - if (pointInRectangle(uv_a, ...px_rect)) { + if (pointInRectangle(uv_a, px_rect[0], px_rect[1])) { inside = true; break; } let vkey_b = sorted_vertices[i+1] || sorted_vertices[0]; @@ -143,7 +143,7 @@ export class MeshFace extends Face { face.uv[vkey_b][0] * factor_x, face.uv[vkey_b][1] * factor_y, ]; - if (lineIntersectsReactangle(uv_a, uv_b, ...px_rect)) { + if (lineIntersectsReactangle(uv_a, uv_b, px_rect[0], px_rect[1])) { inside = true; break; } i++; @@ -160,10 +160,19 @@ export class MeshFace extends Face { getUVIsland(max_depth = 4096) { let keys = [this.getFaceKey()]; let epsilon = 0.2; + let vkey_fkey_map = {}; + let faces = this.mesh.faces; + for (let fkey in faces) { + let face = faces[fkey]; + for (let vkey of face.vertices) { + vkey_fkey_map[vkey] ??= []; + vkey_fkey_map[vkey].push(fkey); + } + } function crawl(face, depth) { if (depth >= max_depth) return; for (let i = 0; i < face.vertices.length; i++) { - let adjacent = face.getAdjacentFace(i); + let adjacent = face.getAdjacentFace(i, vkey_fkey_map); if (!adjacent) continue; if (keys.includes(adjacent.key)) continue; let uv_a1 = adjacent.face.uv[adjacent.edge[0]]; @@ -267,7 +276,7 @@ export class MeshFace extends Face { return []; } } - getAdjacentFace(side_index = 0) { + getAdjacentFace(side_index = 0, vkey_fkey_map) { let vertices = this.getSortedVertices(); side_index = side_index % this.vertices.length; let side_vertices = [ @@ -275,7 +284,8 @@ export class MeshFace extends Face { vertices[side_index+1] || vertices[0] ] let faces = this.mesh.faces; - for (let fkey in faces) { + let fkeys = vkey_fkey_map ? vkey_fkey_map[side_vertices[0]] : Object.keys(faces); + for (let fkey of fkeys) { let face = faces[fkey]; if (face === this) continue; if (face.vertices.includes(side_vertices[0]) && face.vertices.includes(side_vertices[1])) { diff --git a/js/uv/uv.js b/js/uv/uv.js index 61c43787c..105c809ea 100644 --- a/js/uv/uv.js +++ b/js/uv/uv.js @@ -1035,9 +1035,18 @@ export const UVEditor = { let selected_faces = mesh.getSelectedFaces(true); if (face_key && mesh && mesh.faces[face_key]) { if (selected_faces.length == 1) { + let vkey_fkey_map = {}; + let faces = mesh.faces; + for (let fkey in faces) { + let face = faces[fkey]; + for (let vkey of face.vertices) { + vkey_fkey_map[vkey] ??= []; + vkey_fkey_map[vkey].push(fkey); + } + } function crawl(face) { for (let i = 0; i < face.vertices.length; i++) { - let adjacent = face.getAdjacentFace(i); + let adjacent = face.getAdjacentFace(i, vkey_fkey_map); if (!adjacent) continue; if (selected_faces.includes(adjacent.key)) continue; let epsilon = 0.2; From 2b7c9e17cec73f965f1c34c20b87a1d93c639df9 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 13:41:52 +0200 Subject: [PATCH 05/47] Switch Focus On View target to UV on hover over UV editor Closes #2182 Fix it shifting UV when no UV is selected --- js/preview/preview.js | 2 +- js/uv/uv.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index 9c706923d..e9801668d 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -2457,7 +2457,7 @@ BARS.defineActions(function() { click(event = 0) { if (!Project) return; let zoom = this.keybind.additionalModifierTriggered(event, 'zoom'); - if (Prop.active_panel == 'uv') { + if (Prop.active_panel == 'uv' || document.querySelector('#UVEditor:hover')) { UVEditor.focusOnSelection(zoom) } else { diff --git a/js/uv/uv.js b/js/uv/uv.js index 105c809ea..839f45116 100644 --- a/js/uv/uv.js +++ b/js/uv/uv.js @@ -358,6 +358,7 @@ export const UVEditor = { zoom = BarItems.focus_on_selection.keybind.additionalModifierTriggered(zoom, 'zoom'); } let [min_x, min_y, max_x, max_y] = this.vue.getSelectedUVBoundingBox(); + if (min_x == Infinity) return; if (zoom) { let width = (max_x-min_x) / UVEditor.getUVWidth(); let height = (max_y-min_y) / UVEditor.getUVHeight(); From b2974cf2f2a85a0eb0c4ff040a0d91b0fada93a0 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 13:41:56 +0200 Subject: [PATCH 06/47] Changes --- js/interface/about.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/interface/about.ts b/js/interface/about.ts index 8da2f5f6d..93fdbc9a8 100644 --- a/js/interface/about.ts +++ b/js/interface/about.ts @@ -7,9 +7,11 @@ BARS.defineActions(() => { icon: 'info', category: 'blockbench', click: function () { + let date = new Date(); const data = { isApp, - version_label: Blockbench.version + version_label: Blockbench.version, + ann: date.dayOfYear() == 95 && date.getFullYear() == 2026, c: 0, link: Blockbench.openLink }; jQuery.ajax({ url: 'https://api.github.com/repos/JannisX11/blockbench/releases/latest', @@ -140,7 +142,7 @@ BARS.defineActions(() => {

Released under the GPL 3.0 license

Privacy Policy

- egg + egg ` } From aa1e36475b7e2af2c3091a08e9ea271bea4c2d9e Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 13:50:26 +0200 Subject: [PATCH 07/47] Fix #3460 action side menu button click detection area does not cover entire side --- css/general.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/css/general.css b/css/general.css index 61d8ef5b5..8730a3381 100644 --- a/css/general.css +++ b/css/general.css @@ -687,7 +687,10 @@ } .tool.side_menu_tool i.icon.action_more_options { display: inline-block; - width: 19px; + width: 20px; + height: 100%; + margin-top: 0; + padding-top: 4px; color: var(--color-text); margin-left: 0; } @@ -702,6 +705,7 @@ text-align: center; width: 100%; margin-top: 0px; + padding-top: 0px; } /*(Context-)Menu*/ From cac8bc6e75a83054254b115d3d36802d9314b5c0 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 15:09:48 +0200 Subject: [PATCH 08/47] Fix recover model overflow Add recover models warning message --- js/auto_backup.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/js/auto_backup.ts b/js/auto_backup.ts index 640469e3d..b119c27ad 100644 --- a/js/auto_backup.ts +++ b/js/auto_backup.ts @@ -119,7 +119,12 @@ export const AutoBackup = { // Confirm selection if (confirm_selection && projects.length > 1) { - let form: Record = {}; + let form: Record = { + info: { + text: 'โš  Recovering models is only a backup. Please remember to always save your work to your device.', + type: 'info' + } + }; let keys: UUID[] = []; projects.sort((a, b) => (b.date??0) - (a.date??0)); @@ -155,7 +160,7 @@ export const AutoBackup = { } projects = await new Promise((resolve, reject) => { - new Dialog({ + let dialog = new Dialog({ id: 'recover_backup', title: 'message.recover_backup.title', form, @@ -171,6 +176,10 @@ export const AutoBackup = { resolve(to_open); } }).show(); + let content = dialog.object.querySelector('.dialog_content') as HTMLElement; + if (content) { + content.style.maxHeight = (window.innerHeight - 150) + 'px'; + } }) } From 02f73fc3905aedaeb9d0cf2029d9639eea9dc8c8 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 5 Apr 2026 15:12:33 +0200 Subject: [PATCH 09/47] v5.1.3 [ci-build] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 943ce0d03..e4a443124 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Blockbench", "description": "Low-poly modeling and animation software", - "version": "5.1.2", + "version": "5.1.3", "license": "GPL-3.0-or-later", "author": { "name": "JannisX11", From ed0a960a5a0a0fde74694ec1aebd37db8a2f601c Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Tue, 7 Apr 2026 20:45:33 +0200 Subject: [PATCH 10/47] Fix #3483 Toggling cube face only works if the face is selected Restoring face now defaults to a texture that's assigned to other faces --- js/uv/uv.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/js/uv/uv.js b/js/uv/uv.js index 839f45116..9caaaf54f 100644 --- a/js/uv/uv.js +++ b/js/uv/uv.js @@ -4726,13 +4726,26 @@ Interface.definePanels(function() { let value = this.mappable_elements[0].faces[key].texture === null; Undo.initEdit({elements: Cube.selected, uv_only: true}) UVEditor.forCubes(obj => { - UVEditor.getFaces(obj, event).forEach(function(side) { - if (value) { - if (obj.faces[side].texture === null) obj.faces[side].texture = false; - } else { + let faces = UVEditor.getFaces(obj, event); + if (!faces.includes(key)) { + faces = [key]; + } + for (let side of faces) { + if (!value) { obj.faces[side].texture = null; + + } else if (obj.faces[side].texture === null) { + let value = false; + for (let fkey in obj.faces) { + let face = obj.faces[fkey]; + if (fkey != side && face.texture) { + value = face.texture; + break; + } + } + obj.faces[side].texture = value; } - }) + } obj.preview_controller.updateFaces(obj); }) UVEditor.loadData() From 5257298ea66fdc705052c003736c777d31b1d493 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Fri, 10 Apr 2026 16:22:51 +0200 Subject: [PATCH 11/47] Fix texture content not updating when duplicating layer --- js/texturing/layers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/texturing/layers.js b/js/texturing/layers.js index 8d0640b20..347e65775 100644 --- a/js/texturing/layers.js +++ b/js/texturing/layers.js @@ -410,9 +410,10 @@ SharedActions.add('duplicate', { let original = texture.getActiveLayer(); let copy = original.getUndoCopy(true); copy.name += '-copy'; - Undo.initEdit({textures: [texture]}); + Undo.initEdit({textures: [texture], bitmap: true}); let layer = new TextureLayer(copy, texture); layer.addForEditing(); + texture.updateLayerChanges(true); Undo.finishEdit('Duplicate layer'); } }) From f977f7d2643120a9b5a310c28cbef4569113d718 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Fri, 10 Apr 2026 16:25:36 +0200 Subject: [PATCH 12/47] Fix labels missing on num sliders --- js/interface/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/interface/actions.ts b/js/interface/actions.ts index 4e8ed177a..2fe0435b5 100644 --- a/js/interface/actions.ts +++ b/js/interface/actions.ts @@ -1150,7 +1150,7 @@ export class NumSlider extends Widget { this.node.classList.add('is_colored'); } - //this.addLabel(data.label); + this.addLabel(); this.jq_inner .on('mousedown touchstart', async (event) => { From bebc73825cc7b11e0ffa85b77d78266fece2cd15 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Fri, 10 Apr 2026 16:30:42 +0200 Subject: [PATCH 13/47] Fix copy brush not working when brush resizing is assigned to ctrl + left drag --- js/texturing/painter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/texturing/painter.js b/js/texturing/painter.js index 565ef4ebd..46c6d1037 100644 --- a/js/texturing/painter.js +++ b/js/texturing/painter.js @@ -195,7 +195,9 @@ export const Painter = { } if (!PointerTarget.requestTarget(PointerTarget.types.paint)) { Painter.paint_stroke_canceled = true; - return; + if (Toolbox.selected != BarItems.copy_brush || !event.ctrlOrCmd) { + return; + } } if (Toolbox.selected.brush && Toolbox.selected.brush.onStrokeStart) { let result = Toolbox.selected.brush.onStrokeStart({texture, x, y, uv: uvTag, event, raycast_data: data}); From 83b2bed0814a159145ad0bdbdaa98681a2de0b53 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Fri, 10 Apr 2026 16:32:10 +0200 Subject: [PATCH 14/47] Fix long layer name overflow --- css/panels.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/panels.css b/css/panels.css index 72012b7af..d89128eca 100644 --- a/css/panels.css +++ b/css/panels.css @@ -1120,6 +1120,7 @@ } .texture_layer > label { flex-grow: 1; + overflow: hidden; } .texture_layer i.toggle_disabled { color: var(--color-subtle_text); From 50ed4c54ff4ef80d761c3e451d3ecbece7539932 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 11 Apr 2026 02:56:43 +0200 Subject: [PATCH 15/47] Add molang validator check for complex expression with no return value --- js/animations/molang.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/animations/molang.js b/js/animations/molang.js index fa5b4cdf8..a56b2bd78 100644 --- a/js/animations/molang.js +++ b/js/animations/molang.js @@ -237,6 +237,9 @@ new ValidatorCheck('molang_syntax', { if (clear_string.match(/^[+*/.,?=&<>|]/)) { issues.push('Expression starts with an invalid character') } + if (clear_string.endsWith(';') && !clear_string.includes('return ')) { + issues.push('Complex expression with no return value. Remove the semicolon or add a return statement') + } if ( (clear_string.match(/[\w.]\s+[\w.]/) && !keywords.find((k) => clear_string.includes(k))) || From 9d2572f05b30d763dfe30980669edb6869e2bf27 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 11 Apr 2026 21:44:43 +0200 Subject: [PATCH 16/47] Fix java version of zombie skin preset texture not matching UV (#3490) --- js/formats/minecraft/skin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/js/formats/minecraft/skin.ts b/js/formats/minecraft/skin.ts index 93787a424..aa21f0a0f 100644 --- a/js/formats/minecraft/skin.ts +++ b/js/formats/minecraft/skin.ts @@ -10500,7 +10500,6 @@ skin_presets.zombie = { pose: true, model_java: `{ "name": "zombie", - "external_textures": ["entity/zombie/zombie.png"], "texturewidth": 64, "textureheight": 64, "eyes": [ From 01ca8dad651c27da32794e7c94dbd2f4563396b8 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 11 Apr 2026 21:45:10 +0200 Subject: [PATCH 17/47] Change plugin store text --- js/plugin_loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/plugin_loader.ts b/js/plugin_loader.ts index a67408830..c6e51514a 100644 --- a/js/plugin_loader.ts +++ b/js/plugin_loader.ts @@ -1855,7 +1855,7 @@ BARS.defineActions(function() {

Blockbench Plugins

-

Plugins allow you to configure Blockbench beyond the default capabilities. Select from a list of 100 community created plugins.

+

Plugins allow you to configure Blockbench beyond the default capabilities. Select from a catalog of community created plugins.

Want to write your own plugin? Check out the Plugin Documentation.

From 39ad4b4c4cade145de6d02f6afbb4712c1f5fb39 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Tue, 14 Apr 2026 15:02:28 +0200 Subject: [PATCH 18/47] Update contributing guide --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76c76fd2e..89221024a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,11 @@ If you want to contribute Blockbench translations, you can visit [blockbench.net ## Source code contributions -Blockbench, the program itself, is mostly just coded by me. But sometimes, code contributions can be helpful! +Blockbench, the program itself, is mostly just coded by me. But third party contributions are welcome! -If you want to contribute to the Blockbench code, please contact me (@Jannis) on [Discord](https://discord.gg/sGkj78W) (e.g. in #bb-feedback) *before* to discuss whether a change is necessary in the base program and how it can be approached. -Some changes make sense in Blockbench itself, while others could work better as a plugin. +Bug fixes can be submitted via pull request. + +If you want to add a feature or change functionality, I recommend opening a discussion on Discord before to talk about if and where it fits in. You can do so in the channel [#bb-feedback](https://discord.gg/kZBmCwvBFw). Some features may be out of scope and work better as plugins, or I might have additional tips on how to approach it. The timing of features being integrated also depends on the release cycle, new features should go through beta testing before being shipped. Minor fixes should be targeting the `patch` branch, new features and fixes that require additional testing should generally target the `next` branch. From 43f37f821456a79f987ca325979073ea682bd768 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 14 Apr 2026 14:51:23 -0400 Subject: [PATCH 19/47] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20type=20of=20`Plugins?= =?UTF-8?q?.download=5Fstats`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/plugin_loader.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/js/plugin_loader.ts b/js/plugin_loader.ts index a67408830..817808ae2 100644 --- a/js/plugin_loader.ts +++ b/js/plugin_loader.ts @@ -25,7 +25,7 @@ export const Plugins = { */ installed: [] as PluginInstallation[], json: undefined, - download_stats: {}, + download_stats: {} as Record, /** * All loaded plugins, including plugins from the store that are not installed */ @@ -684,10 +684,10 @@ export class Plugin { } }); }) - + } else if (isApp) { file_content = fs.readFileSync(path, {encoding: 'utf-8'}); - + } else { throw 'Failed to load plugin: Unknown URL format' } @@ -728,10 +728,10 @@ export class Plugin { } isInstallable() { var scope = this; - var result: string | boolean = + var result: string | boolean = scope.variant === 'both' || ( - isApp === (scope.variant === 'desktop') && + isApp === (scope.variant === 'desktop') && isApp !== (scope.variant === 'web') ); if (result && scope.min_version) { @@ -889,7 +889,7 @@ export class Plugin { } static selected: Plugin|null = null - + static menu = new Menu([ new MenuSeparator('general'), { @@ -1024,7 +1024,7 @@ Plugins.loading_promise = new Promise((resolve, reject) => { dataType: 'json', success(data) { Plugins.json = data; - + resolve(); Plugins.loading_promise = null; }, @@ -1128,7 +1128,7 @@ export async function loadInstalledPlugins() { if (plugin) { plugin.installed = true; if (installation.disabled) plugin.disabled = true; - + if (isApp && ( (installation.version && plugin.version && VersionUtil.compare(plugin.version, '<=', installation.version)) || (plugin.min_version && Blockbench.isOlderThan(plugin.min_version)) @@ -1152,7 +1152,7 @@ export async function loadInstalledPlugins() { } else if (isApp && installation.source == 'store') { // Offline install store plugin - let plugin = new Plugin(installation.id); + let plugin = new Plugin(installation.id); let promise = plugin.load(false); install_promises.push(promise); } else { @@ -1170,7 +1170,7 @@ export async function loadInstalledPlugins() { console.log(`Loaded ${load_counter} plugin${pluralS(load_counter)}`) } StateMemory.save('installed_plugins') - + install_promises.forEach(promise => { promise.catch(console.error); @@ -1632,7 +1632,7 @@ BARS.defineActions(function() {
  • {{ number+1 }}
  • - +
    arrow_back_ios @@ -1788,7 +1788,7 @@ BARS.defineActions(function() { - +
    - +
    • {{ type.name }}

      @@ -1849,15 +1849,15 @@ BARS.defineActions(function() {
    - +
    - +

    Blockbench Plugins

    Plugins allow you to configure Blockbench beyond the default capabilities. Select from a list of 100 community created plugins.

    Want to write your own plugin? Check out the Plugin Documentation.

    - +

    {{row.title}}

      @@ -1872,7 +1872,7 @@ BARS.defineActions(function() {
    - + ` } @@ -1940,7 +1940,7 @@ BARS.defineActions(function() { function getList(details: boolean): string { let plugins = Plugins.all.filter(p => p.installed); if (details) { - return plugins.map(p => + return plugins.map(p => (`${p.id}@${p.version}${p.source == 'store' ? '' : ('('+p.source+')')}`) ).join(', '); } else { From 68125e807bb3e954a67f7f8e6c8cf36fed2b5dd7 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 14 Apr 2026 14:53:19 -0400 Subject: [PATCH 20/47] =?UTF-8?q?=F0=9F=A9=B9=20Ignore=20delete=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/native_apis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/native_apis.ts b/js/native_apis.ts index 45d8dffec..0ca024d3d 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -26,6 +26,7 @@ export { * @internal */ export const process = window.process; +// @ts-expect-error - Purposefully deleting a non-deletable property. delete window.process; @@ -214,7 +215,7 @@ delete window.require; /** * Revoke the permissions of a plugin - * @param plugin + * @param plugin * @returns List of revoked permissions * @private */ From d6cbe8df345847aa284a9175dc5e724c47622ace Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 14 Apr 2026 14:54:35 -0400 Subject: [PATCH 21/47] =?UTF-8?q?=E2=9C=A8=20Make=20node's=20`stream`=20a?= =?UTF-8?q?=20safe=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/native_apis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/native_apis.ts b/js/native_apis.ts index 0ca024d3d..7841fb942 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -42,7 +42,8 @@ const SAFE_APIS = [ 'string_decoder', 'querystring', 'constants', - 'buffer' + 'buffer', + 'stream' ]; const REQUESTABLE_APIS = [ 'fs', From 87c722233aa393344cb568004b9372f4e3b672a8 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 14 Apr 2026 15:42:45 -0400 Subject: [PATCH 22/47] =?UTF-8?q?=E2=9C=A8=20Make=20node's=20`tty`=20a=20s?= =?UTF-8?q?afe=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/native_apis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/native_apis.ts b/js/native_apis.ts index 7841fb942..55f6a69fa 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -43,7 +43,8 @@ const SAFE_APIS = [ 'querystring', 'constants', 'buffer', - 'stream' + 'stream', + 'tty' ]; const REQUESTABLE_APIS = [ 'fs', From eb9e4c7e99fcf58eb45eab239b0f3de134614386 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Tue, 14 Apr 2026 16:10:33 -0400 Subject: [PATCH 23/47] =?UTF-8?q?=E2=9C=A8=20Make=20node's=20`perf=5Fhooks?= =?UTF-8?q?`=20a=20safe=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/native_apis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/native_apis.ts b/js/native_apis.ts index 55f6a69fa..5a08f6793 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -44,7 +44,8 @@ const SAFE_APIS = [ 'constants', 'buffer', 'stream', - 'tty' + 'tty', + 'perf_hooks' ]; const REQUESTABLE_APIS = [ 'fs', From 8c3d3efef4ef8a9181873408ba0fc40369dfbd86 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Fri, 17 Apr 2026 10:05:11 -0400 Subject: [PATCH 24/47] =?UTF-8?q?=E2=9C=82=EF=B8=8F=20Remove=20`tty`=20lib?= =?UTF-8?q?rary=20as=20it's=20not=20what=20I=20thought.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/native_apis.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/js/native_apis.ts b/js/native_apis.ts index 5a08f6793..888c26d8d 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -44,7 +44,6 @@ const SAFE_APIS = [ 'constants', 'buffer', 'stream', - 'tty', 'perf_hooks' ]; const REQUESTABLE_APIS = [ From 61a07ad2e62fcab818be7ceb48d9b340207ff13e Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Fri, 17 Apr 2026 10:09:21 -0400 Subject: [PATCH 25/47] =?UTF-8?q?=E2=9C=82=EF=B8=8F=20Undo=20commit=20"Ign?= =?UTF-8?q?ore=20delete=20error"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/native_apis.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/js/native_apis.ts b/js/native_apis.ts index 888c26d8d..501aaed3d 100644 --- a/js/native_apis.ts +++ b/js/native_apis.ts @@ -26,7 +26,6 @@ export { * @internal */ export const process = window.process; -// @ts-expect-error - Purposefully deleting a non-deletable property. delete window.process; From 8b4d370804c1d4a7c303110d682e48bbc183573f Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 18 Apr 2026 22:26:53 +0200 Subject: [PATCH 26/47] Fix issue with Preserve World Transform on meshes --- js/modeling/transform.js | 4 ++-- js/outliner/outliner.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/js/modeling/transform.js b/js/modeling/transform.js index 46cf360c8..94d328542 100644 --- a/js/modeling/transform.js +++ b/js/modeling/transform.js @@ -520,11 +520,11 @@ function changeNodeLocalPosition(obj, vector) { if ('forEachChild' in obj && obj.getTypeBehavior('use_absolute_position')) { let difference = position_arr.slice().V3_subtract(obj.origin); obj.forEachChild(child => { - if (child instanceof Mesh) { + /*if (child instanceof Mesh) { for (let vkey in child.vertices) { child.vertices[vkey].V3_add(difference); } - } + }*/ if (child.from) child.from.V3_add(difference); if (child.to) child.to.V3_add(difference); if (child.origin) child.origin.V3_add(difference); diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index 973d0150d..ec4d882ff 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -453,6 +453,9 @@ export function moveOutlinerSelectionTo(item, target, order = 0, options = {}) { item.preview_controller.updateFaces(item); } } + if (item.preview_controller.updateGeometry) { + item.preview_controller.updateGeometry(item); + } } let matrix1 = new THREE.Matrix4(); let matrix2 = new THREE.Matrix4(); From 638377bc4025a3b90350b787447235b85cc2c656 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 18 Apr 2026 22:38:48 +0200 Subject: [PATCH 27/47] Add scoped FS watch APIS Closes #3510 --- js/util/scoped_fs.ts | 18 +++++++++++++++++- types/custom/desktop.d.ts | 15 +++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/js/util/scoped_fs.ts b/js/util/scoped_fs.ts index fb49b7c5f..a43912036 100644 --- a/js/util/scoped_fs.ts +++ b/js/util/scoped_fs.ts @@ -118,11 +118,23 @@ export function createScopedFS(scope?: string) { checkPath(path); return fs.statSync(path, options); }, + watchFile(path: string, options, listener) { + checkPath(path); + return fs.watchFile(path, options, listener); + }, + unwatchFile(path: string, listener) { + checkPath(path); + return fs.unwatchFile(path, listener); + }, + watch(path: string, options, listener) { + checkPath(path); + return fs.watch(path, options, listener); + }, promises: { access(path: string, mode) { checkPath(path); - return fs.access(path, mode); + return fs.promises.access(path, mode); }, copyFile(src: string, dest: string, mode) { checkPath(src); @@ -169,6 +181,10 @@ export function createScopedFS(scope?: string) { stat(path: string, options) { checkPath(path); return fs.promises.stat(path, options); + }, + watch(path: string, options) { + checkPath(path); + return fs.promises.watch(path, options); } } } diff --git a/types/custom/desktop.d.ts b/types/custom/desktop.d.ts index 8ebfd5862..eb6a343de 100644 --- a/types/custom/desktop.d.ts +++ b/types/custom/desktop.d.ts @@ -10,7 +10,9 @@ declare function openDefaultTexturePath(): void declare function createBackup(init: any): void declare function closeBlockbenchWindow(): any -type ScopedFS = Pick & { promises: Pick } interface RequireDialogOptions { From ef85dc2552e9a900b728c2782b7f1a3659302231 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 18 Apr 2026 23:19:20 +0200 Subject: [PATCH 28/47] Fix #3505 Advanced screenshot with ortho preset has wrong size on first use --- js/preview/preview.js | 2 +- js/preview/screenshot.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index e9801668d..5f6c8569e 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -596,7 +596,7 @@ export class Preview { render() { this.controls.update() this.renderer.render( - scene, + Canvas.scene, this.camera ) } diff --git a/js/preview/screenshot.js b/js/preview/screenshot.js index 6d81837e7..1a58f8979 100644 --- a/js/preview/screenshot.js +++ b/js/preview/screenshot.js @@ -257,7 +257,6 @@ export const Screencam = { let render_viewport = options.anti_aliasing == 'msaa' ? MediaPreview : Screencam.NoAAPreview; let sample_factor = options.anti_aliasing == 'ssaa' ? 4 : 1; - render_viewport.resize(options.resolution[0] * sample_factor, options.resolution[1] * sample_factor); if (options.angle_preset == 'view') { render_viewport.copyView(preview); } else { @@ -266,6 +265,8 @@ export const Screencam = { : JSON.parse(localStorage.getItem('camera_presets'))[parseInt(options.angle_preset)]; render_viewport.loadAnglePreset(preset); } + render_viewport.resize(options.resolution[0] * sample_factor, options.resolution[1] * sample_factor); + if (options.zoom) { if (!render_viewport.isOrtho) { render_viewport.camera.setFocalLength(options.zoom); From f9a9237b5498620132418beb5bade0d864364cc6 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sun, 19 Apr 2026 14:37:46 +0200 Subject: [PATCH 29/47] Fix #3503 wrong UV on beak on java edition parrot skin preset --- js/formats/minecraft/skin.ts | 71 +++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/js/formats/minecraft/skin.ts b/js/formats/minecraft/skin.ts index aa21f0a0f..4820992fa 100644 --- a/js/formats/minecraft/skin.ts +++ b/js/formats/minecraft/skin.ts @@ -847,6 +847,8 @@ Interface.definePanels(function() { // Source: https://github.com/Mojang/bedrock-samples/, licensed under the Minecraft EULA // With modifications for usability +// Clipbench.setText(compileJSON(Codecs.skin_model.compile({raw: false})).replace(/\n/g, '\n\t')) + skin_presets.steve = { display_name: 'Player - Wide', pose: true, @@ -6873,7 +6875,7 @@ skin_presets.panda_baby = { }; skin_presets.parrot = { display_name: 'Parrot', - model: `{ + model_bedrock: `{ "name": "parrot", "external_textures": ["entity/parrot/parrot_red_blue.png"], "texturewidth": 32, @@ -6939,6 +6941,73 @@ skin_presets.parrot = { ] } ] + }`, + model_java: `{ + "name": "parrot", + "external_textures": ["entity/parrot/parrot_red_blue.png"], + "texturewidth": 32, + "textureheight": 32, + "bones": [ + { + "name": "body", + "pivot": [0, 7.5, -3], + "rotation": [25, 0, 0], + "cubes": [ + {"origin": [-1.5, 1.5, -4.5], "size": [3, 6, 3], "uv": [2, 8]} + ] + }, + { + "name": "wing0", + "parent": "body", + "pivot": [1.5, 7.1, -2.8], + "rotation": [10, 0, 0], + "cubes": [ + {"origin": [1, 2.1, -4.3], "size": [1, 5, 3], "uv": [19, 8]} + ] + }, + { + "name": "wing1", + "parent": "body", + "pivot": [-1.5, 7.1, -2.8], + "rotation": [10, 0, 0], + "cubes": [ + {"origin": [-2, 2.1, -4.3], "size": [1, 5, 3], "uv": [19, 8]} + ] + }, + { + "name": "head", + "pivot": [0, 8.3, -2.8], + "cubes": [ + {"origin": [-1, 6.8, -3.8], "size": [2, 3, 2], "uv": [2, 2]}, + {"origin": [-1, 9.8, -5.8], "size": [2, 1, 4], "uv": [10, 0]}, + {"origin": [-0.5, 7.8, -4.7], "size": [1, 2, 1], "uv": [11, 7]}, + {"origin": [-0.5, 8.035, -5.64], "size": [1, 2.025, 1], "uv": [16, 7]}, + {"origin": [0, 9.1, -4.9], "size": [0, 5, 4], "uv": [2, 18]} + ] + }, + { + "name": "tail", + "pivot": [0, 2.9, 1.2], + "rotation": [50, 0, 0], + "cubes": [ + {"origin": [-1.5, -0.1, 0.2], "size": [3, 4, 1], "uv": [22, 1]} + ] + }, + { + "name": "leg0", + "pivot": [1.5, 1, -0.5], + "cubes": [ + {"origin": [0.5, -0.5, -1.5], "size": [1, 2, 1], "uv": [14, 18]} + ] + }, + { + "name": "leg1", + "pivot": [-0.5, 1, -0.5], + "cubes": [ + {"origin": [-1.5, -0.5, -1.5], "size": [1, 2, 1], "uv": [14, 18]} + ] + } + ] }` }; skin_presets.phantom = { From 464f38951f16ff0974471b5e8dbc553688160c00 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Mon, 20 Apr 2026 22:15:54 +0200 Subject: [PATCH 30/47] Timeline css fix --- css/panels.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/panels.css b/css/panels.css index d89128eca..826390c0e 100644 --- a/css/panels.css +++ b/css/panels.css @@ -1774,7 +1774,7 @@ } #timeline_vue.graph_editor .channel_head { flex-wrap: wrap; - justify-content: flex-end; + justify-content: flex-start; } .drag_hover[order]::before { z-index: 7; From 18d9f091a87932446eedc83744b94105645e1ded Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Mon, 20 Apr 2026 22:16:40 +0200 Subject: [PATCH 31/47] Improve toggle visibility performance --- js/outliner/outliner.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index ec4d882ff..6438ec11d 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -1395,7 +1395,7 @@ Interface.definePanels(function() { node[key] = value; }) // Update - Canvas.updateVisibility(); + Canvas.updateView({elements: affected, element_aspects: {visibility: true}}); } else if (!affected.includes(node) && (!node.locked || key == 'locked' || key == 'visibility')) { let new_affected = [node]; @@ -1417,7 +1417,9 @@ Interface.definePanels(function() { if (key == 'mirror_uv' && node.preview_controller.updateUV) node.preview_controller.updateUV(node); }) // Update - if (key == 'visibility') Canvas.updateVisibility(); + if (key == 'visibility') { + Canvas.updateView({elements: affected, element_aspects: {visibility: true}}); + } if (key == 'locked') updateSelection(); } } From 89042c2e8cb9b3e53199df45b0730c51e6bd6b5e Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Tue, 21 Apr 2026 23:21:48 +0200 Subject: [PATCH 32/47] Fix #3519 Open With Blockbench not working with special character at end of file names --- js/desktop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/desktop.js b/js/desktop.js index f3fcb2523..27edb477b 100644 --- a/js/desktop.js +++ b/js/desktop.js @@ -88,7 +88,7 @@ export function initializeDesktopApp() { //Load Model export function loadOpenWithBlockbenchFile() { function load(path) { - if (!path || path.length < 7 || !path.match(/\w\.\w+$/)) return; + if (!path || path.length < 7 || !path.match(/.\.\w+$/)) return; var extension = pathToExtension(path); if (extension == 'png') { Blockbench.read([path], {readtype: 'image'}, (files) => { From a424b12b1ad3350a4b211635df1471d093d8b233 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Thu, 23 Apr 2026 12:01:45 +0200 Subject: [PATCH 33/47] Improve canvas raycast performance --- js/preview/preview.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index 5f6c8569e..47c2bcdc8 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -421,31 +421,32 @@ export class Preview { raycast(event, options = Toolbox.selected.raycast_options) { if (!options) options = 0; convertTouchEvent(event); - var canvas_offset = $(this.canvas).offset() + let canvas_offset = this.canvas.getBoundingClientRect(); this.mouse.x = ((event.clientX - canvas_offset.left) / this.width) * 2 - 1; this.mouse.y = - ((event.clientY - canvas_offset.top) / this.height) * 2 + 1; this.raycaster.setFromCamera( this.mouse, this.camera ); var objects = [] Outliner.elements.forEach(element => { - if (element.visibility === false || element.locked === true || (element.mesh && element.mesh.visible == false)) return; - if (element.mesh && element.mesh.geometry) { - objects.push(element.mesh); + let mesh = element.mesh; + if (element.visibility === false || element.locked === true || (mesh && mesh.visible == false)) return; + if (mesh && mesh.geometry) { + objects.push(mesh); if (Modes.edit && element.selected) { - if (element.mesh.vertex_points && (element.mesh.vertex_points.visible || options.vertices)) { - objects.push(element.mesh.vertex_points); + if (mesh.vertex_points && (mesh.vertex_points.visible || options.vertices)) { + objects.push(mesh.vertex_points); } - if (element instanceof Mesh && ((element.mesh.outline.visible && BarItems.selection_mode.value == 'edge') || options.edges)) { - objects.push(element.mesh.outline); + if (element instanceof Mesh && ((mesh.outline.visible && BarItems.selection_mode.value == 'edge') || options.edges)) { + objects.push(mesh.outline); } } else if (element instanceof SplineMesh && element.render_mode !== "mesh") { - objects.push(element.mesh.pathLine); + objects.push(mesh.pathLine); } } else if (element instanceof Locator) { - objects.push(element.mesh.sprite); + objects.push(mesh.sprite); } else if (element instanceof ArmatureBone) { if (Toolbox.selected.id == 'weight_brush' && !(event.altKey || Pressing.overrides.alt)) return; - objects.push(element.mesh.children[0]); + objects.push(mesh.children[0]); } }) for (let group of Group.multi_selected) { From ab4611f5c340b392080634c39fb39a642e406abe Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Thu, 23 Apr 2026 22:24:16 +0200 Subject: [PATCH 34/47] Fix mirror modeling issues --- js/modeling/mirror_modeling.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/js/modeling/mirror_modeling.ts b/js/modeling/mirror_modeling.ts index 260ce7460..774f51752 100644 --- a/js/modeling/mirror_modeling.ts +++ b/js/modeling/mirror_modeling.ts @@ -245,7 +245,10 @@ export const MirrorModeling = { // post if (!before_snapshop) undo_aspects.outliner = true; if (!undo_aspects.groups) undo_aspects.groups = []; - undo_aspects.groups.safePush(group); + if (!undo_aspects.groups.includes(group)) { + // Must be new array so we don't accidentally change the undo input array + undo_aspects.groups = [...undo_aspects.groups, group]; + } }, element_types: {} as Record, registerElementType(type_class: any, options: MirrorModelingElementTypeOptions) { @@ -366,13 +369,17 @@ Blockbench.on('finish_edit', ({aspects}) => { if (aspects.group || aspects.groups || aspects.outliner) { Canvas.updateAllBones(); } - } else if (aspects.group || aspects.groups) { + } + if (aspects.group || aspects.groups) { let selected_groups = aspects.groups ?? [aspects.group]; selected_groups.forEach(group => { let mirror_group = MirrorModeling.cached_elements[group.uuid]?.counterpart; if (mirror_group && Group.all.includes(mirror_group)) { + let before_snapshot = mirror_group.getChildlessCopy(); + before_snapshot.uuid = mirror_group.uuid; MirrorModeling.updateParentNodeCounterpart(mirror_group, group); + MirrorModeling.insertGroupIntoUndo(mirror_group, aspects, before_snapshot); } }) @@ -421,7 +428,8 @@ MirrorModeling.registerElementType(Cube, { !off_axes.some(axis => !Math.epsilon(element.to[axis], element2.to[axis], e)) && !symmetry_axes.some(axis => !Math.epsilon(element.size(axis), element2.size(axis), e)) && !symmetry_axes.some(axis => !Math.epsilon(element.to[axis]-center, center-element2.from[axis], e)) && - isOppositeEuler(element.rotation, element2.rotation) + isOppositeEuler(element.rotation, element2.rotation) && + element2.getDepth() == element.getDepth() ) { return element2; } @@ -469,7 +477,8 @@ MirrorModeling.registerElementType(Mesh, { symmetry_axes.find(axis => !Math.epsilon(this_center[axis]-center, center-other_center[axis], ep)) == undefined && off_axes.find(axis => !Math.epsilon(element.origin[axis], element2.origin[axis], e)) == undefined && off_axes.find(axis => !Math.epsilon(this_center[axis], other_center[axis], ep)) == undefined && - isOppositeEuler(element.rotation, element2.rotation) + isOppositeEuler(element.rotation, element2.rotation) && + element2.getDepth() == element.getDepth() ) { return element2; } @@ -698,7 +707,8 @@ MirrorModeling.registerElementType(ArmatureBone, { if ( isOppositeVector(element.position, element2.position, center) && isOppositeEuler(element.rotation, element2.rotation) && - MirrorModeling.isParentTreeOpposite(element, element2) + MirrorModeling.isParentTreeOpposite(element, element2) && + element2.getDepth() == element.getDepth() ) { return element2; } @@ -737,7 +747,8 @@ MirrorModeling.registerElementType(Billboard, { for (let element2 of (Billboard.all as Billboard[])) { if (element == element2) continue; if ( - isOppositeVector(element.position, element2.position, center) + isOppositeVector(element.position, element2.position, center) && + element2.getDepth() == element.getDepth() ) { return element2; } From 7f6ef95f4a174cb2b54151e2688db10be450ad8a Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Thu, 23 Apr 2026 22:32:31 +0200 Subject: [PATCH 35/47] FIx #3476 Resolve Group does not undo track child groups correctly --- js/outliner/types/group.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/js/outliner/types/group.js b/js/outliner/types/group.js index 7fdcc953c..317d8eab1 100644 --- a/js/outliner/types/group.js +++ b/js/outliner/types/group.js @@ -299,7 +299,7 @@ export class Group extends OutlinerNode { } this.remove(false); if (undo) { - all_groups.empty(); + all_groups.remove(this); Undo.finishEdit('Resolve group'); } return array; @@ -909,19 +909,22 @@ BARS.defineActions(function() { condition: {modes: ['edit'], method: () => Group.first_selected}, click() { let all_elements = []; + let all_groups = []; for (let group of Group.multi_selected) { + all_groups.push(group); group.forEachChild(obj => { if (obj instanceof Group == false) { all_elements.safePush(obj); + } else { + all_groups.push(obj); } }) } - let affected_groups = Group.multi_selected.slice(); - Undo.initEdit({outliner: true, elements: all_elements, groups: affected_groups}) - for (let group of affected_groups) { + Undo.initEdit({outliner: true, elements: all_elements, groups: all_groups}) + for (let group of Group.multi_selected.slice()) { group.resolve(false); + all_groups.remove(group); } - affected_groups.empty(); Undo.finishEdit('Resolve group'); } }) From 0483e6399a9788eb96c77c2a4d215c646ab144ae Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Thu, 23 Apr 2026 23:06:21 +0200 Subject: [PATCH 36/47] Fix #3500 Opening a second blockbench instance shows file unsupported dialog --- js/desktop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/desktop.js b/js/desktop.js index 27edb477b..009a22f72 100644 --- a/js/desktop.js +++ b/js/desktop.js @@ -88,7 +88,7 @@ export function initializeDesktopApp() { //Load Model export function loadOpenWithBlockbenchFile() { function load(path) { - if (!path || path.length < 7 || !path.match(/.\.\w+$/)) return; + if (!path || path.length < 7 || path.startsWith('--') || !path.match(/.\.\w+$/)) return; var extension = pathToExtension(path); if (extension == 'png') { Blockbench.read([path], {readtype: 'image'}, (files) => { From 789a57d4852114a4f13e9cd26d3ef98ff4054c06 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 25 Apr 2026 00:31:37 +0200 Subject: [PATCH 37/47] Fix mirror modeling issue with imprecise values --- js/modeling/mirror_modeling.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/modeling/mirror_modeling.ts b/js/modeling/mirror_modeling.ts index 774f51752..aa8193192 100644 --- a/js/modeling/mirror_modeling.ts +++ b/js/modeling/mirror_modeling.ts @@ -82,7 +82,7 @@ export const MirrorModeling = { let parent = child.parent; if (parent instanceof OutlinerNode == false) return 'root'; - if ('origin' in parent && parent.origin[0] == center && MirrorModeling.isParentTreeSymmetrical(child, {center})) { + if ('origin' in parent && Math.epsilon(parent.origin[0], center) && MirrorModeling.isParentTreeSymmetrical(child, {center})) { return parent; } else { let mirror_group_parent = getParentMirror(parent) as OutlinerNode & OutlinerNodeParentTraits; @@ -185,8 +185,8 @@ export const MirrorModeling = { parents.push(subject) } return parents.allAre(parent => { - if (parent.rotation && off_axes.some(axis => parent.rotation[axis])) return false; - if (parent.origin && !symmetry_axes.allAre(axis => parent.origin[axis] == center)) return false; + if (parent.rotation && off_axes.some(axis => !Math.epsilon(parent.rotation[axis], 0))) return false; + if (parent.origin && !symmetry_axes.allAre(axis => Math.epsilon(parent.origin[axis], center))) return false; return true; }) }, @@ -313,7 +313,7 @@ Blockbench.on('init_edit', (args) => { aspects.outliner = true; selected_groups.forEach(group => { - if (group.origin[0] == (Format.centered_grid ? 0 : 8)) return; + if (Math.epsilon(group.origin[0], (Format.centered_grid ? 0 : 8))) return; let mirror_group = Group.all.find(g => { if ( @@ -777,7 +777,7 @@ export function symmetrizeArmature(armature: Armature, mesh: Mesh, affected_vkey // For each vkey, copy its value on each bone to the other side for (let vkey in mesh.vertices) { let position = mesh.vertices[vkey]; - if (position[0] == 0) continue; + if (!Math.epsilon(position[0], 0)) continue; if (affected_vkeys.has(vkey) == false) continue; let opposite = getOppositeMeshVertex(mesh, vkey); if (!opposite) continue; From 10dd09bc30456b3ccdb2e44d62ed1bf9b8957214 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 25 Apr 2026 00:49:31 +0200 Subject: [PATCH 38/47] Fix duplicate group undo issues --- js/outliner/outliner.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index 6438ec11d..97b4f8482 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -433,7 +433,7 @@ export function moveOutlinerSelectionTo(item, target, order = 0, options = {}) { return; } if (duplicate) { - Undo.initEdit({elements: [], outliner: true, selection: true}, options.amended); + Undo.initEdit({elements: [], groups: [], outliner: true, selection: true}, options.amended); Outliner.selected.empty(); } else { Undo.initEdit({ @@ -530,7 +530,7 @@ export function moveOutlinerSelectionTo(item, target, order = 0, options = {}) { } updateSelection(); if (duplicate) { - Undo.finishEdit('Duplicate selection', {elements: Outliner.selected, outliner: true, selection: true, groups: Group.selected}) + Undo.finishEdit('Duplicate selection', {elements: Outliner.selected, outliner: true, selection: true, groups: Group.all.filter(g => g.selected)}) } else { Undo.finishEdit('Move elements in outliner') } @@ -739,7 +739,6 @@ SharedActions.add('duplicate', { condition: () => Modes.edit && Group.first_selected, priority: -1, run() { - let cubes_before = elements.length; Undo.initEdit({outliner: true, elements: [], groups: [], selection: true}); let all_original = []; for (let group of Group.multi_selected) { @@ -748,10 +747,12 @@ SharedActions.add('duplicate', { let all_new = []; let new_groups = []; - let old_selected_groups = Group.multi_selected.slice(); + let groups_to_duplicate = Group.selected.filter(g => g.parent.selected == false); Group.multi_selected.empty(); - for (let group of old_selected_groups) { - group.selected = false; + for (let group of Group.all) { + if (group.selected) group.selected = false; + } + for (let group of groups_to_duplicate) { let new_group = group.duplicate(); new_group.forEachChild(g => all_new.push(g), Group, true); new_group.multiSelect(); @@ -759,7 +760,12 @@ SharedActions.add('duplicate', { } updateSelection(); - Undo.finishEdit('Duplicate group', {outliner: true, elements: elements.slice().slice(cubes_before), groups: new_groups, selection: true}); + Undo.finishEdit('Duplicate group', { + outliner: true, + elements: Outliner.selected, + groups: Group.all.filter(g => g.selected), + selection: true + }); if (Animation.all.length) { let affected_anims = Animation.all.filter(a => all_original.find(bone => a.animators[bone.uuid]?.keyframes.length)); From d104e8fdea99294f5acf9f6faffd949d850dac3c Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 25 Apr 2026 01:26:01 +0200 Subject: [PATCH 39/47] Add node module types --- types/custom/desktop.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/custom/desktop.d.ts b/types/custom/desktop.d.ts index 8ebfd5862..453f55aaf 100644 --- a/types/custom/desktop.d.ts +++ b/types/custom/desktop.d.ts @@ -61,6 +61,8 @@ declare function requireNativeModule(module: 'zlib'): typeof import("node:zlib") declare function requireNativeModule(module: 'timers'): typeof import("node:timers"); declare function requireNativeModule(module: 'url'): typeof import("node:url"); declare function requireNativeModule(module: 'string_decoder'): typeof import("node:string_decoder"); +declare function requireNativeModule(module: 'stream'): typeof import("node:stream"); +declare function requireNativeModule(module: 'perf_hooks'): typeof import("node:perf_hooks"); declare function requireNativeModule(module: 'querystring'): typeof import("node:querystring"); declare function requireNativeModule(module: 'child_process', options?: RequireDialogOptions): (typeof import("node:child_process")) | undefined; declare function requireNativeModule(module: 'electron', options?: RequireDialogOptions): (typeof import("node:electron")) | undefined; From 00e72c8d8f687ed4f3ee0de5bc96fc155be0161e Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 25 Apr 2026 01:29:01 +0200 Subject: [PATCH 40/47] Add missing type --- types/custom/desktop.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/types/custom/desktop.d.ts b/types/custom/desktop.d.ts index 709e1576d..129086f5c 100644 --- a/types/custom/desktop.d.ts +++ b/types/custom/desktop.d.ts @@ -80,3 +80,4 @@ declare function requireNativeModule(module: 'util', options?: RequireDialogOpti declare function requireNativeModule(module: 'os', options?: RequireDialogOptions): (typeof import("node:os")) | undefined; declare function requireNativeModule(module: 'v8', options?: RequireDialogOptions): (typeof import("node:v8")) | undefined; declare function requireNativeModule(module: 'clipboard', options?: RequireDialogOptions): (import('electron').Clipboard) | undefined; +declare function requireNativeModule(module: 'shell', options?: RequireDialogOptions): (import('electron').Shell) | undefined; From 18158c381b18312898416352a0ef81d0f192e2f0 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Sat, 25 Apr 2026 13:13:43 +0200 Subject: [PATCH 41/47] Expose uv size utilities --- js/texturing/textures.js | 2 +- js/uv/uv_size.ts | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/js/texturing/textures.js b/js/texturing/textures.js index 64315677b..65df8975a 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -174,7 +174,7 @@ export class Texture { cancel: 1 }, function(result) { if (result === 0) { - setProjectResolution(img.naturalWidth, img.naturalHeight) + UVSizeUtil.adjustProjectResolution(img.naturalWidth, img.naturalHeight); if (selected.length) { UVEditor.loadData() } diff --git a/js/uv/uv_size.ts b/js/uv/uv_size.ts index b9864a7bc..2f23ff5d0 100644 --- a/js/uv/uv_size.ts +++ b/js/uv/uv_size.ts @@ -1,5 +1,5 @@ -export function setProjectResolution(width: number, height: number, modify_uv: boolean = false) { +function adjustProjectResolution(width: number, height: number, modify_uv: boolean = false) { if (Project.texture_width / width != Project.texture_width / height) { modify_uv = false; } @@ -35,7 +35,7 @@ export function setProjectResolution(width: number, height: number, modify_uv: b } } -export function adjustElementUVToResolution(multiplier: ArrayVector2, elements = Outliner.elements, textures?: Texture[]) { +function adjustElementUVToResolution(multiplier: ArrayVector2, elements = Outliner.elements, textures?: Texture[]) { for (let element of elements) { if ('faces' in element == false) continue; if (element instanceof Mesh) { @@ -231,4 +231,17 @@ export function editUVSizeDialog(options: {texture?: Texture, project?: boolean} updateSelection(); } }).show(); -} \ No newline at end of file +} + +export const UVSizeUtil = { + adjustProjectResolution, + adjustElementUVToResolution, + editUVSizeDialog +} +const global = { + UVSizeUtil +}; +declare global { + const UVSizeUtil: typeof global.UVSizeUtil +} +Object.assign(window, global); From 616452d196783526549668299b216d5616ba40ae Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Wed, 4 Mar 2026 17:26:37 -0500 Subject: [PATCH 42/47] =?UTF-8?q?=E2=9C=A8=20Improved=20`generate-types`?= =?UTF-8?q?=20script.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed `convert_types.js` -> `generate_types.js`. - Converted `generate_types.js` to TypeScript. - Added `--watch` argument to `generate-types`. - Added support for Regex to the `excluded` list in `type_config.json`. --- package-lock.json | 858 +++++++++++++++++++++++++++++++++++--- package.json | 6 +- scripts/convert_types.js | 118 ------ scripts/generate_types.ts | 195 +++++++++ scripts/tsconfig.json | 23 + types/README.md | 2 + types/type_config.json | 2 +- 7 files changed, 1025 insertions(+), 179 deletions(-) delete mode 100644 scripts/convert_types.js create mode 100644 scripts/generate_types.ts create mode 100644 scripts/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 7c6059a79..72d0e6e8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,8 @@ "@types/spectrum": "^1.8.7", "@types/three": "^0.129.2", "@types/tinycolor2": "^1.4.6", + "chalk": "^5.6.2", + "chokidar": "^5.0.0", "command-line-args": "^6.0.1", "concurrently": "^9.1.2", "dompurify": "^3.3.1", @@ -41,6 +43,8 @@ "strip-json-comments": "^5.0.3", "three": "^0.129.0", "tinycolor2": "^1.6.0", + "tscw-config": "^1.1.2", + "tsx": "^4.21.0", "typedoc": "^0.28.14", "typedoc-plugin-missing-exports": "^4.1.2", "typescript": "^5.8.3", @@ -114,7 +118,6 @@ "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1720,7 +1723,6 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1792,6 +1794,23 @@ "electron-fuses": "dist/bin.js" } }, + "node_modules/@electron/fuses/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@electron/fuses/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -2124,6 +2143,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -2145,6 +2165,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2161,6 +2182,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -2175,6 +2197,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -3645,6 +3668,23 @@ "prettier": "^1.18.2 || ^2.0.0" } }, + "node_modules/@vue/compiler-sfc/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@vue/component-compiler/-/component-compiler-4.2.4.tgz", @@ -3725,6 +3765,23 @@ "url": "https://opencollective.com/postcss/" } }, + "node_modules/@vue/component-compiler-utils/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@vue/component-compiler-utils/node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -3788,7 +3845,6 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4305,7 +4361,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -4396,6 +4451,23 @@ "node": ">=12.0.0" } }, + "node_modules/builder-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/builder-util/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -4599,16 +4671,13 @@ "license": "CC-BY-4.0" }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -4626,17 +4695,16 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -4880,6 +4948,36 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -4980,7 +5078,8 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -5234,7 +5333,6 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -5427,7 +5525,6 @@ "integrity": "sha512-MH6LK4xM6VVmmtz0nRE0Fe8l2jTKSYTvH1t0ZfbNLw3o6dlBCVTRqQha6uL8ZQVoMy74JyLguGwK7dU7rCKIhw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", @@ -5479,6 +5576,23 @@ "electron-winstaller": "5.4.0" } }, + "node_modules/electron-builder/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/electron-builder/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -5543,6 +5657,23 @@ "mime": "^2.5.2" } }, + "node_modules/electron-publish/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/electron-publish/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -5655,6 +5786,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -5675,6 +5807,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -5892,7 +6025,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6382,6 +6514,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/gifenc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz", @@ -7201,6 +7346,23 @@ "node": ">=10" } }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-worker": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", @@ -7549,6 +7711,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -8221,6 +8400,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -8441,7 +8637,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -8814,6 +9009,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -8831,27 +9027,11 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": "^12.20.0 || >=14" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -9175,14 +9355,13 @@ } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -9340,6 +9519,16 @@ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -9390,6 +9579,7 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -9420,7 +9610,6 @@ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -9502,17 +9691,49 @@ "@parcel/watcher": "^2.4.1" } }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/sdp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", - "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==", + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/sdp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", + "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==", + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -10140,6 +10361,7 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -10212,6 +10434,7 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -10347,7 +10570,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10426,6 +10648,22 @@ "utf8-byte-length": "^1.0.1" } }, + "node_modules/tscw-config": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz", + "integrity": "sha512-mrrMxCqC6kjqjuhGc7mTOB3P7JuBebZ0ZnFQTi4e+K0K+2kT1OvTXzFygWCPBor/F8WJ1IWVRrnBLKctFhFwOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-json-comments": "^5.0.1" + }, + "bin": { + "tscw": "dist/cli.js" + }, + "peerDependencies": { + "typescript": ">=2.0.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10433,6 +10671,510 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -10511,7 +11253,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10772,7 +11513,6 @@ "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" diff --git a/package.json b/package.json index e4a443124..3d4a389bf 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "prepublish-web": "npm run build-web && npm run pwa && npm run generate-docs", "prepublish-beta": "node ./scripts/enable_beta.js && npm run build-web && npm run pwa && npm run generate-docs", "webapp": "git checkout gh-pages && git pull && git merge master --no-edit && git push && git checkout master", - "generate-types": "tsc --project tsconfig.json && node ./scripts/convert_types.js", + "generate-types": "tsx scripts/generate_types.ts", "publish-types": "npm publish ./types", "generate-docs": "npm run generate-types && npx typedoc --options typedoc.json" }, @@ -159,6 +159,8 @@ "@types/spectrum": "^1.8.7", "@types/three": "^0.129.2", "@types/tinycolor2": "^1.4.6", + "chalk": "^5.6.2", + "chokidar": "^5.0.0", "command-line-args": "^6.0.1", "concurrently": "^9.1.2", "dompurify": "^3.3.1", @@ -168,6 +170,8 @@ "esbuild": "^0.25.8", "esbuild-plugin-glsl": "^1.2.2", "esbuild-vue": "^0.0.1", + "tscw-config": "^1.1.2", + "tsx": "^4.21.0", "file-saver": "^2.0.5", "gifenc": "^1.0.3", "jquery": "^3.7.1", diff --git a/scripts/convert_types.js b/scripts/convert_types.js deleted file mode 100644 index 3ff14606a..000000000 --- a/scripts/convert_types.js +++ /dev/null @@ -1,118 +0,0 @@ -import fs from 'fs'; -import PathModule from 'path' - -/** - * Convert all auto-generated type definitions to declare globally instead of exporting - * Removes empty files and excluded files - */ - -let file_count = 0; -let file_delete_count = 0; - -const config = JSON.parse(fs.readFileSync('./types/type_config.json', 'utf-8')); - -function isComment(line) { - return line.startsWith('/*') || line.startsWith(' *') || line.startsWith('//'); -} - -/** - * - * @param {string} content - * @param {string} path - */ -function processFile(content, path) { - if (content.match(/\ndeclare global {/)) return content; - - // TODO: Handle comments - let lines = content.split(/\r?\n/); - let output_lines = []; - let i = 0; - let global_scope = false; - let comment_stash = []; - for (let line of lines) { - if (global_scope) { - if (line.startsWith('}') || line.startsWith(' ')) { - output_lines.push(' ' + line); - } else if ((line.startsWith('export ') || line.startsWith('interface')) && !line.startsWith('export {}')) { - for (let comment of comment_stash) { - output_lines.push(' ' + comment); - } - comment_stash.length = 0; - let shorter_line = line.replace(/^export (default )?(declare )?/, ''); - output_lines.push(' ' + shorter_line); - - } else if (isComment(line)) { - // Comment - comment_stash.push(line); - } else { - output_lines.push('}'); - global_scope = false; - output_lines.push(line); - } - - } else if ((line.startsWith('export ') || line.startsWith('interface')) && !line.startsWith('export {}')) { - output_lines.push('declare global {'); - for (let comment of comment_stash) { - output_lines.push(' ' + comment); - } - comment_stash.length = 0; - let shorter_line = line.replace(/^export (default )?(declare )?/, ''); - output_lines.push(' ' + shorter_line); - global_scope = true; - - } else if (isComment(line)) { - // Comment - comment_stash.push(line); - - } else { - if (comment_stash.length) { - output_lines.push(...comment_stash); - comment_stash.length = 0; - } - if (line) output_lines.push(line); - } - i++; - } - if (output_lines.includes('export {};') == false) { - output_lines.push('export {};'); - } - output_lines = output_lines.map(line => { - return line.replace(/, }/g, ' }'); - }) - output_lines.push(''); - let result = output_lines.join('\n'); - return result; -} - -function convertDirectory(path) { - let files = fs.readdirSync(path) - for (let file_name of files) { - let file_path = PathModule.join(path, file_name) - let simple_file_path = file_path.replace(/[\\\/]/g, '/').replace(/(\.d\.ts)$/, ''); - - if (file_name.endsWith('.ts')) { - let file_content = fs.readFileSync(file_path, {encoding: 'utf-8'}); - let modified_file_content = processFile(file_content, file_path); - if ( - !modified_file_content || - modified_file_content.replace('export {};', '').length < 5 || - config.exclude.find(exclusion => simple_file_path.endsWith(exclusion)) - ) { - fs.unlinkSync(file_path); - file_delete_count++; - } else if (modified_file_content != file_content) { - fs.writeFileSync(file_path, modified_file_content, {encoding: 'utf-8'}); - console.log('Update '+file_path) - file_count++; - } - } else { - convertDirectory(file_path); - } - } -} -convertDirectory('./types/generated'); - - -console.log(`Converted ${file_count} type definition files, deleted ${file_delete_count} empty files`); - - diff --git a/scripts/generate_types.ts b/scripts/generate_types.ts new file mode 100644 index 000000000..714155082 --- /dev/null +++ b/scripts/generate_types.ts @@ -0,0 +1,195 @@ +import chokidar from 'chokidar' +import { spawn } from 'child_process' +import { readdir, readFile, stat, unlink, writeFile } from 'fs/promises' +import config from '../types/type_config.json' with { type: 'json' } +import chalk from 'chalk' +import { join, relative } from 'path' + +const TYPE_EXCLUSION_PATTERNS = config.exclude.map(expression => new RegExp(expression)) +const GENERATED_TYPES_DIR = './types/generated' + +/** + * Convert all auto-generated type definitions to declare globally instead of exporting + * Removes empty files and excluded files + */ + +let working = false +let processedFileCount = 0 +let deletedFileCount = 0 + +function isComment(line: string) { + return line.startsWith('/*') || line.startsWith(' *') || line.startsWith('//') +} + +async function compileTypeDefinitions() { + console.log(chalk.blue('โš™๏ธ Compiling type definitions...\n')) + return new Promise((resolve, reject) => + spawn('tsc', ['--project', 'tsconfig.json'], { stdio: 'inherit' }) + .on('close', code => { + if (code !== 0) { + console.error( + chalk.red('\n๐Ÿšจ TypeScript compilation compiled with type warnings!') + ) + } else { + console.log(chalk.blue('\nโœ… Type definitions compiled successfully.\n')) + } + resolve() + }) + .on('error', error => { + console.error( + chalk.red('โš ๏ธ Unexpected error during type definition compilation:'), + error + ) + reject(error) + }) + ) +} + +function transformFileContents(content: string, path: string) { + if (content.match(/\ndeclare global {/)) return content + + // TODO: Handle comments + let lines = content.split(/\r?\n/) + let output_lines = [] + let i = 0 + let global_scope = false + let comment_stash = [] + for (let line of lines) { + if (global_scope) { + if (line.startsWith('}') || line.startsWith(' ')) { + output_lines.push(' ' + line) + } else if ( + (line.startsWith('export ') || line.startsWith('interface')) && + !line.startsWith('export {}') + ) { + for (let comment of comment_stash) { + output_lines.push(' ' + comment) + } + comment_stash.length = 0 + let shorter_line = line.replace(/^export (default )?(declare )?/, '') + output_lines.push(' ' + shorter_line) + } else if (isComment(line)) { + // Comment + comment_stash.push(line) + } else { + output_lines.push('}') + global_scope = false + output_lines.push(line) + } + } else if ( + (line.startsWith('export ') || line.startsWith('interface')) && + !line.startsWith('export {}') + ) { + output_lines.push('declare global {') + for (let comment of comment_stash) { + output_lines.push(' ' + comment) + } + comment_stash.length = 0 + let shorter_line = line.replace(/^export (default )?(declare )?/, '') + output_lines.push(' ' + shorter_line) + global_scope = true + } else if (isComment(line)) { + // Comment + comment_stash.push(line) + } else { + if (comment_stash.length) { + output_lines.push(...comment_stash) + comment_stash.length = 0 + } + if (line) output_lines.push(line) + } + i++ + } + if (output_lines.includes('export {};') == false) { + output_lines.push('export {};') + } + output_lines = output_lines.map(line => { + return line.replace(/, }/g, ' }') + }) + output_lines.push('') + let result = output_lines.join('\n') + return result +} + +function isEmptyFile(content: string) { + return content.replace('export {};', '').trim().length == 0 +} + +function normalizePath(path: string) { + return path.replace(/[\\\/]/g, '/') +} + +function isExcluded(path: string) { + const relativePath = normalizePath(relative(GENERATED_TYPES_DIR, path)) + return TYPE_EXCLUSION_PATTERNS.some(pattern => !!relativePath.match(pattern)) +} + +async function processFile(path: string) { + const fileContent = await readFile(path, { encoding: 'utf-8' }) + const processedContent = transformFileContents(fileContent, path) + + if (!processedContent || isEmptyFile(processedContent) || isExcluded(path)) { + await unlink(path) + deletedFileCount++ + return + } + + if (processedContent != fileContent) { + await writeFile(path, processedContent, { encoding: 'utf-8' }) + processedFileCount++ + } +} + +async function processDirectoryContents(path: string) { + for (const item of await readdir(path)) { + const itemPath = join(path, item) + const stats = await stat(itemPath) + + if (stats.isDirectory()) { + await processDirectoryContents(itemPath) + } else { + await processFile(itemPath) + } + } +} + +async function convertTypes() { + processedFileCount = 0 + deletedFileCount = 0 + await compileTypeDefinitions() + await processDirectoryContents(GENERATED_TYPES_DIR) + console.log( + chalk.green( + `\nโœ… Done! ${chalk.cyan(processedFileCount)} files processed, ${chalk.red(deletedFileCount)} files deleted.` + ) + ) +} + +const onUpdate = async (path: string) => { + if (working) return // Prevent multiple simultaneous executions + working = true + + console.log(chalk.blue(`๐Ÿ“ Change detected: ${path}\n`)) + try { + await convertTypes() + } finally { + working = false + } +} + +async function main() { + await convertTypes() + + if (process.argv.includes('--watch')) { + chokidar + .watch('./js', { ignoreInitial: true, awaitWriteFinish: true }) + .on('ready', () => { + console.log(chalk.blue('๐Ÿ‘€ Watching for changes...')) + }) + .on('change', onUpdate) + .on('add', onUpdate) + .on('unlink', onUpdate) + } +} + +void main() diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 000000000..6632c7282 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["."], + "compilerOptions": { + "outDir": "./dist/", + "noImplicitAny": true, + "module": "nodenext", + "target": "ES2022", + "moduleResolution": "nodenext", + "strict": true, + "types": ["node"], + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "allowUmdGlobalAccess": true, + "allowUnreachableCode": true, + "allowImportingTsExtensions": true, + "esModuleInterop": true, + "noEmit": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "alwaysStrict": true, + "baseUrl": "src" + } +} diff --git a/types/README.md b/types/README.md index 1316b7ce0..0e95bf156 100644 --- a/types/README.md +++ b/types/README.md @@ -13,6 +13,8 @@ All type definitions in the /custom/ folder are manually written and are intende ## Generating types Run `npm run generate-types` from the main repo. Ensure typescript throws no errors, otherwise files won't export correctly. +Use `npm run generate-types -- --watch` to automatically regenerate types on source file changes. + ## Locally testing and using types To use these types in other projects on your local PC before they are published, run this command in your other project: diff --git a/types/type_config.json b/types/type_config.json index 4decc93f9..75caade36 100644 --- a/types/type_config.json +++ b/types/type_config.json @@ -44,4 +44,4 @@ "lib/VuePrismEditor", "lib/VuePrismEditor.min" ] -} \ No newline at end of file +} From 429e47f0eb97d2108784bdaca9f89df44f18592f Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Wed, 4 Mar 2026 17:48:11 -0500 Subject: [PATCH 43/47] =?UTF-8?q?=F0=9F=A9=B9=20Simplify=20excluded=20path?= =?UTF-8?q?=20filtering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate_types.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/generate_types.ts b/scripts/generate_types.ts index 714155082..1251a4d0c 100644 --- a/scripts/generate_types.ts +++ b/scripts/generate_types.ts @@ -3,7 +3,7 @@ import { spawn } from 'child_process' import { readdir, readFile, stat, unlink, writeFile } from 'fs/promises' import config from '../types/type_config.json' with { type: 'json' } import chalk from 'chalk' -import { join, relative } from 'path' +import { join } from 'path' const TYPE_EXCLUSION_PATTERNS = config.exclude.map(expression => new RegExp(expression)) const GENERATED_TYPES_DIR = './types/generated' @@ -111,24 +111,26 @@ function transformFileContents(content: string, path: string) { return result } -function isEmptyFile(content: string) { - return content.replace('export {};', '').trim().length == 0 -} - function normalizePath(path: string) { - return path.replace(/[\\\/]/g, '/') + return path.replaceAll('\\', '/') } function isExcluded(path: string) { - const relativePath = normalizePath(relative(GENERATED_TYPES_DIR, path)) - return TYPE_EXCLUSION_PATTERNS.some(pattern => !!relativePath.match(pattern)) + const base = path.replace(/(.d)?.ts$/, '') + return config.exclude.some(excluded => normalizePath(base).endsWith(excluded)) } async function processFile(path: string) { + if (isExcluded(path)) { + await unlink(path) + deletedFileCount++ + return + } + const fileContent = await readFile(path, { encoding: 'utf-8' }) const processedContent = transformFileContents(fileContent, path) - if (!processedContent || isEmptyFile(processedContent) || isExcluded(path)) { + if (fileContent == undefined || fileContent.replace('export {};', '').length < 5) { await unlink(path) deletedFileCount++ return From 9b9b2025ee7ac2e0715758e10422e5ee19852409 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Wed, 4 Mar 2026 17:48:46 -0500 Subject: [PATCH 44/47] =?UTF-8?q?=E2=9C=A8=20Add=20cleanup=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate_types.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/generate_types.ts b/scripts/generate_types.ts index 1251a4d0c..923b1534b 100644 --- a/scripts/generate_types.ts +++ b/scripts/generate_types.ts @@ -1,6 +1,6 @@ import chokidar from 'chokidar' import { spawn } from 'child_process' -import { readdir, readFile, stat, unlink, writeFile } from 'fs/promises' +import { readdir, readFile, rm, stat, unlink, writeFile } from 'fs/promises' import config from '../types/type_config.json' with { type: 'json' } import chalk from 'chalk' import { join } from 'path' @@ -180,6 +180,10 @@ const onUpdate = async (path: string) => { } async function main() { + // Cleanup old generated types before starting + console.log(chalk.blue('๐Ÿงน Cleaning up old generated types...')) + await rm(GENERATED_TYPES_DIR, { recursive: true, force: true }) + // Generate types for the first time await convertTypes() if (process.argv.includes('--watch')) { From 445bb46a5e39d57998291142d69be56e3efadfc7 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Wed, 4 Mar 2026 17:57:00 -0500 Subject: [PATCH 45/47] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20empty=20processed?= =?UTF-8?q?=20file=20filtering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate_types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_types.ts b/scripts/generate_types.ts index 923b1534b..95132e81b 100644 --- a/scripts/generate_types.ts +++ b/scripts/generate_types.ts @@ -130,7 +130,7 @@ async function processFile(path: string) { const fileContent = await readFile(path, { encoding: 'utf-8' }) const processedContent = transformFileContents(fileContent, path) - if (fileContent == undefined || fileContent.replace('export {};', '').length < 5) { + if (processedContent == undefined || processedContent.replace('export {};', '').length < 5) { await unlink(path) deletedFileCount++ return From 4116b732731b8682eec37f96554fe788e0d33fd3 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Wed, 4 Mar 2026 18:03:49 -0500 Subject: [PATCH 46/47] =?UTF-8?q?=F0=9F=8E=A8=20Cleanup=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate_types.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/generate_types.ts b/scripts/generate_types.ts index 95132e81b..fe46c8a10 100644 --- a/scripts/generate_types.ts +++ b/scripts/generate_types.ts @@ -1,11 +1,10 @@ -import chokidar from 'chokidar' +import chalk from 'chalk' import { spawn } from 'child_process' +import chokidar from 'chokidar' import { readdir, readFile, rm, stat, unlink, writeFile } from 'fs/promises' -import config from '../types/type_config.json' with { type: 'json' } -import chalk from 'chalk' import { join } from 'path' +import config from '../types/type_config.json' with { type: 'json' } -const TYPE_EXCLUSION_PATTERNS = config.exclude.map(expression => new RegExp(expression)) const GENERATED_TYPES_DIR = './types/generated' /** From f9ceea327051999c8188c194f1b52b9bec3f5244 Mon Sep 17 00:00:00 2001 From: SnaveSutit Date: Wed, 4 Mar 2026 21:02:24 -0500 Subject: [PATCH 47/47] =?UTF-8?q?=F0=9F=A9=B9=20Add=20`dontTransform`=20li?= =?UTF-8?q?st=20to=20`type=5Fconfig.json`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A list of files to keep, but not transform. --- scripts/generate_types.ts | 6 ++++++ types/type_config.json | 3 +++ 2 files changed, 9 insertions(+) diff --git a/scripts/generate_types.ts b/scripts/generate_types.ts index fe46c8a10..fe1933ac5 100644 --- a/scripts/generate_types.ts +++ b/scripts/generate_types.ts @@ -119,12 +119,18 @@ function isExcluded(path: string) { return config.exclude.some(excluded => normalizePath(base).endsWith(excluded)) } +function dontTransform(path: string) { + const base = path.replace(/(.d)?.ts$/, '') + return config.dontTransform.some(excluded => normalizePath(base).endsWith(excluded)) +} + async function processFile(path: string) { if (isExcluded(path)) { await unlink(path) deletedFileCount++ return } + if (dontTransform(path)) return const fileContent = await readFile(path, { encoding: 'utf-8' }) const processedContent = transformFileContents(fileContent, path) diff --git a/types/type_config.json b/types/type_config.json index 75caade36..441957d4d 100644 --- a/types/type_config.json +++ b/types/type_config.json @@ -43,5 +43,8 @@ "lib/lzutf8", "lib/VuePrismEditor", "lib/VuePrismEditor.min" + ], + "dontTransform": [ + "io/model_loader" ] }