From 2d223dc8bd37bdbece112e30b37ed70f193f1766 Mon Sep 17 00:00:00 2001 From: "DESKTOP-G08HS3B\\Lee Yi" Date: Tue, 6 May 2025 20:46:05 -0400 Subject: [PATCH 001/112] Rearrange everything into its own git workspace --- src/bundles/ar/package.json | 8 + src/bundles/ar/{ => src}/AR.ts | 0 src/bundles/ar/{ => src}/ObjectsHelper.ts | 0 src/bundles/ar/{ => src}/OverlayHelper.ts | 0 src/bundles/ar/{ => src}/index.ts | 0 src/bundles/ar/tsconfig.json | 16 + src/bundles/arcade_2d/package.json | 8 + src/bundles/arcade_2d/{ => src}/audio.ts | 0 src/bundles/arcade_2d/{ => src}/constants.ts | 0 src/bundles/arcade_2d/{ => src}/functions.ts | 0 src/bundles/arcade_2d/{ => src}/gameobject.ts | 0 src/bundles/arcade_2d/{ => src}/index.ts | 0 .../arcade_2d/{ => src}/phaserScene.ts | 0 src/bundles/arcade_2d/{ => src}/types.ts | 0 src/bundles/arcade_2d/tsconfig.json | 16 + src/bundles/binary_tree/package.json | 5 + .../binary_tree/{ => src}/functions.ts | 0 src/bundles/binary_tree/{ => src}/index.ts | 0 src/bundles/binary_tree/{ => src}/types.ts | 0 src/bundles/binary_tree/tsconfig.json | 16 + src/bundles/communication/package.json | 9 + .../communication/{ => src}/Communications.ts | 0 .../{ => src}/GlobalStateController.ts | 0 .../communication/{ => src}/MqttController.ts | 0 .../{ => src}/MultiUserController.ts | 0 .../communication/{ => src}/RpcController.ts | 0 .../{ => src}/__tests__/index.ts | 0 src/bundles/communication/{ => src}/index.ts | 0 src/bundles/communication/tsconfig.json | 16 + src/bundles/copy_gc/package.json | 5 + src/bundles/copy_gc/{ => src}/index.ts | 0 src/bundles/copy_gc/{ => src}/types.ts | 0 src/bundles/copy_gc/tsconfig.json | 16 + src/bundles/csg/package.json | 10 + src/bundles/csg/{ => src}/constants.ts | 0 src/bundles/csg/{ => src}/core.ts | 0 src/bundles/csg/{ => src}/functions.ts | 0 src/bundles/csg/{ => src}/index.ts | 0 src/bundles/csg/{ => src}/input_tracker.ts | 0 src/bundles/csg/{ => src}/jscad/renderer.ts | 0 src/bundles/csg/{ => src}/jscad/types.ts | 0 src/bundles/csg/{ => src}/listener_tracker.ts | 0 src/bundles/csg/{ => src}/samples/_imports.js | 0 .../csg/{ => src}/samples/christmas.js | 0 src/bundles/csg/{ => src}/samples/colours.js | 0 .../csg/{ => src}/samples/operations.js | 0 .../csg/{ => src}/samples/primitives.js | 0 src/bundles/csg/{ => src}/samples/rotation.js | 0 src/bundles/csg/{ => src}/samples/ship.js | 0 .../csg/{ => src}/samples/sierpinski.js | 0 .../csg/{ => src}/samples/snowglobe.js | 0 .../csg/{ => src}/samples/steinmetz.js | 0 .../csg/{ => src}/stateful_renderer.ts | 0 src/bundles/csg/{ => src}/types.ts | 0 src/bundles/csg/{ => src}/utilities.ts | 0 src/bundles/csg/tsconfig.json | 16 + src/bundles/curve/package.json | 8 + .../curve/{ => src}/__tests__/curve.ts | 0 src/bundles/curve/{ => src}/curves_webgl.ts | 0 src/bundles/curve/{ => src}/functions.ts | 0 src/bundles/curve/{ => src}/index.ts | 0 .../curve/{ => src}/samples/canvases.js | 0 .../curve/{ => src}/samples/imports.js | 0 src/bundles/curve/{ => src}/type_interface.ts | 0 src/bundles/curve/{ => src}/types.ts | 0 src/bundles/curve/tsconfig.json | 16 + src/bundles/game/package.json | 9 + src/bundles/game/{ => src}/functions.ts | 0 src/bundles/game/{ => src}/index.ts | 0 src/bundles/game/{ => src}/types.ts | 0 src/bundles/game/tsconfig.json | 16 + src/bundles/mark_sweep/package.json | 5 + src/bundles/mark_sweep/{ => src}/index.ts | 0 src/bundles/mark_sweep/{ => src}/types.ts | 0 src/bundles/mark_sweep/tsconfig.json | 16 + src/bundles/nbody/package.json | 8 + src/bundles/nbody/{ => src}/CelestialBody.ts | 0 src/bundles/nbody/{ => src}/Force.ts | 0 src/bundles/nbody/{ => src}/Misc.ts | 0 .../nbody/{ => src}/SimulateFunction.ts | 0 src/bundles/nbody/{ => src}/Simulation.ts | 0 src/bundles/nbody/{ => src}/State.ts | 0 src/bundles/nbody/{ => src}/Transformation.ts | 0 src/bundles/nbody/{ => src}/Universe.ts | 0 src/bundles/nbody/{ => src}/Vector.ts | 0 src/bundles/nbody/{ => src}/index.ts | 0 src/bundles/nbody/tsconfig.json | 16 + src/bundles/package.json | 7 + src/bundles/painter/package.json | 11 + src/bundles/painter/{ => src}/functions.ts | 0 src/bundles/painter/{ => src}/index.ts | 0 src/bundles/painter/{ => src}/painter.ts | 0 src/bundles/painter/tsconfig.json | 16 + src/bundles/physics_2d/package.json | 8 + .../physics_2d/{ => src}/PhysicsObject.ts | 0 .../physics_2d/{ => src}/PhysicsWorld.ts | 0 src/bundles/physics_2d/{ => src}/functions.ts | 0 src/bundles/physics_2d/{ => src}/index.ts | 0 src/bundles/physics_2d/{ => src}/types.ts | 0 src/bundles/physics_2d/tsconfig.json | 16 + src/bundles/pix_n_flix/package.json | 5 + src/bundles/pix_n_flix/{ => src}/constants.ts | 0 src/bundles/pix_n_flix/{ => src}/functions.ts | 0 src/bundles/pix_n_flix/{ => src}/index.ts | 0 src/bundles/pix_n_flix/{ => src}/types.ts | 0 src/bundles/pix_n_flix/tsconfig.json | 16 + src/bundles/plotly/package.json | 9 + .../plotly/{ => src}/curve_functions.ts | 0 src/bundles/plotly/{ => src}/functions.ts | 0 src/bundles/plotly/{ => src}/index.ts | 0 src/bundles/plotly/{ => src}/plotly.ts | 0 .../plotly/{ => src}/sound_functions.ts | 0 src/bundles/plotly/tsconfig.json | 16 + src/bundles/remote_execution/package.json | 8 + .../remote_execution/{ => src}/ev3/index.ts | 0 .../remote_execution/{ => src}/index.ts | 0 src/bundles/remote_execution/tsconfig.json | 16 + src/bundles/repeat/package.json | 5 + .../repeat/{ => src}/__tests__/index.ts | 0 src/bundles/repeat/{ => src}/functions.ts | 0 src/bundles/repeat/{ => src}/index.ts | 0 src/bundles/repeat/tsconfig.json | 16 + src/bundles/repl/package.json | 8 + src/bundles/repl/{ => src}/config.ts | 0 src/bundles/repl/{ => src}/functions.ts | 0 src/bundles/repl/{ => src}/index.ts | 0 .../repl/{ => src}/programmable_repl.ts | 0 src/bundles/repl/tsconfig.json | 16 + src/bundles/robot_simulation/package.json | 11 + .../robot_simulation/{ => src}/config.ts | 8 +- .../__tests__/ev3/components/Chassis.ts | 0 .../__tests__/ev3/components/Mesh.ts | 0 .../__tests__/ev3/components/Motor.ts | 0 .../__tests__/ev3/components/Wheel.ts | 0 .../__tests__/ev3/ev3/default/ev3.ts | 0 .../ev3/feedback_control/PidController.ts | 0 .../__tests__/ev3/sensor/ColorSensor.ts | 0 .../__tests__/ev3/sensor/UltrasonicSensor.ts | 0 .../controllers/__tests__/program/Program.ts | 0 .../__tests__/utils/mergeConfig.ts | 0 .../controllers/environment/Cuboid.ts | 0 .../controllers/environment/Paper.ts | 0 .../controllers/ev3/components/Chassis.ts | 0 .../controllers/ev3/components/Mesh.ts | 0 .../controllers/ev3/components/Motor.ts | 0 .../controllers/ev3/components/Wheel.ts | 0 .../controllers/ev3/ev3/default/config.ts | 0 .../controllers/ev3/ev3/default/ev3.ts | 0 .../controllers/ev3/ev3/default/types.ts | 0 .../ev3/feedback_control/PidController.ts | 0 .../controllers/ev3/sensor/ColorSensor.ts | 0 .../ev3/sensor/UltrasonicSensor.ts | 0 .../{ => src}/controllers/ev3/sensor/types.ts | 0 .../{ => src}/controllers/index.ts | 0 .../{ => src}/controllers/program/Program.ts | 0 .../{ => src}/controllers/program/error.ts | 0 .../{ => src}/controllers/program/evaluate.ts | 0 .../controllers/utils/mergeConfig.ts | 0 .../{ => src}/engine/Core/CallbackHandler.ts | 78 +- .../{ => src}/engine/Core/Controller.ts | 186 +-- .../{ => src}/engine/Core/Events.ts | 82 +- .../{ => src}/engine/Core/RobotConsole.ts | 54 +- .../{ => src}/engine/Core/Timer.ts | 126 +- .../{ => src}/engine/Entity/Entity.ts | 250 ++-- .../{ => src}/engine/Entity/EntityFactory.ts | 92 +- .../{ => src}/engine/Math/Convert.ts | 12 +- .../{ => src}/engine/Math/Vector.ts | 48 +- .../{ => src}/engine/Physics.ts | 338 +++--- .../{ => src}/engine/Render/Renderer.ts | 154 +-- .../engine/Render/debug/DebugArrow.ts | 0 .../{ => src}/engine/Render/helpers/Camera.ts | 0 .../{ => src}/engine/Render/helpers/GLTF.ts | 0 .../engine/Render/helpers/MeshFactory.ts | 0 .../{ => src}/engine/Render/helpers/Scene.ts | 0 .../{ => src}/engine/World.ts | 258 ++--- .../engine/__tests__/Core/CallbackHandler.ts | 0 .../engine/__tests__/Core/Controller.ts | 0 .../{ => src}/engine/__tests__/Core/Events.ts | 0 .../engine/__tests__/Core/RobotConsole.ts | 0 .../{ => src}/engine/__tests__/Core/Timer.ts | 0 .../engine/__tests__/Entity/Entity.ts | 0 .../engine/__tests__/Math/Convert.ts | 0 .../{ => src}/engine/__tests__/Physics.ts | 236 ++-- .../engine/__tests__/Render/MeshFactory.ts | 0 .../engine/__tests__/Render/helpers/Camera.ts | 0 .../{ => src}/engine/index.ts | 16 +- .../{ => src}/ev3_functions.ts | 0 .../{ => src}/helper_functions.ts | 1032 ++++++++--------- .../robot_simulation/{ => src}/index.ts | 78 +- src/bundles/robot_simulation/tsconfig.json | 16 + src/bundles/rune/package.json | 8 + src/bundles/rune/{ => src}/display.ts | 0 src/bundles/rune/{ => src}/functions.ts | 0 src/bundles/rune/{ => src}/index.ts | 0 src/bundles/rune/{ => src}/rune.ts | 0 src/bundles/rune/{ => src}/runes_ops.ts | 0 src/bundles/rune/{ => src}/runes_webgl.ts | 0 src/bundles/rune/{ => src}/ruomu_journal.md | 0 src/bundles/rune/tsconfig.json | 16 + src/bundles/rune_in_words/package.json | 5 + .../rune_in_words/{ => src}/functions.ts | 0 src/bundles/rune_in_words/{ => src}/index.ts | 0 src/bundles/rune_in_words/{ => src}/rune.ts | 0 .../rune_in_words/{ => src}/runes_ops.ts | 0 src/bundles/rune_in_words/tsconfig.json | 16 + src/bundles/scrabble/package.json | 5 + .../__tests__/__snapshots__/index.ts.snap | 0 .../scrabble/{ => src}/__tests__/index.ts | 0 src/bundles/scrabble/{ => src}/functions.ts | 0 src/bundles/scrabble/{ => src}/index.ts | 0 src/bundles/scrabble/tsconfig.json | 16 + src/bundles/scrabble/types.ts | 4 - src/bundles/sound/package.json | 5 + .../sound/{ => src}/__tests__/sound.test.ts | 0 src/bundles/sound/{ => src}/functions.ts | 0 src/bundles/sound/{ => src}/index.ts | 0 .../{stereo_sound => sound/src}/riffwave.ts | 0 src/bundles/sound/{ => src}/types.ts | 0 src/bundles/sound/tsconfig.json | 16 + src/bundles/sound_matrix/package.json | 8 + .../sound_matrix/{ => src}/functions.ts | 0 src/bundles/sound_matrix/{ => src}/index.ts | 0 src/bundles/sound_matrix/{ => src}/list.ts | 0 src/bundles/sound_matrix/{ => src}/types.ts | 0 src/bundles/sound_matrix/tsconfig.json | 16 + src/bundles/stereo_sound/package.json | 5 + .../stereo_sound/{ => src}/functions.ts | 0 src/bundles/stereo_sound/{ => src}/index.ts | 0 .../{sound => stereo_sound/src}/riffwave.ts | 0 src/bundles/stereo_sound/{ => src}/types.ts | 0 src/bundles/stereo_sound/tsconfig.json | 16 + src/bundles/tsconfig.json | 6 + src/bundles/unittest/package.json | 5 + .../unittest/{ => src}/__tests__/index.ts | 0 src/bundles/unittest/{ => src}/asserts.ts | 0 src/bundles/unittest/{ => src}/functions.ts | 0 src/bundles/unittest/{ => src}/index.ts | 0 src/bundles/unittest/{ => src}/mocks.ts | 0 src/bundles/unittest/{ => src}/types.ts | 0 src/bundles/unittest/tsconfig.json | 16 + src/bundles/unity_academy/package.json | 13 + .../unity_academy/{ => src}/UnityAcademy.tsx | 0 .../{ => src}/UnityAcademyMaths.ts | 0 src/bundles/unity_academy/{ => src}/config.ts | 0 .../unity_academy/{ => src}/functions.ts | 0 src/bundles/unity_academy/{ => src}/index.ts | 0 src/bundles/unity_academy/tsconfig.json | 16 + src/bundles/wasm/package.json | 5 + src/bundles/wasm/{ => src}/index.ts | 0 src/bundles/wasm/{ => src}/wabt.ts | 0 src/bundles/wasm/tsconfig.json | 16 + src/common/deepPartial.ts | 3 - .../__tests__/hextocolor.ts | 0 .../js-slang/context.d.ts | 0 src/{common => commons}/specialErrors.ts | 0 src/commons/tsconfig.json | 7 + src/{typings => commons}/type_map/index.ts | 0 .../type_helpers.ts => commons/types.ts} | 19 + src/{common => commons}/utilities.ts | 0 src/tabs/ArcadeTwod/package.json | 11 + src/tabs/ArcadeTwod/tsconfig.json | 5 + src/tabs/AugmentedReality/package.json | 11 + src/tabs/AugmentedReality/tsconfig.json | 5 + src/tabs/CopyGc/package.json | 11 + src/tabs/CopyGc/tsconfig.json | 5 + src/tabs/Csg/package.json | 11 + src/tabs/Csg/tsconfig.json | 5 + src/tabs/Curve/package.json | 11 + src/tabs/Curve/tsconfig.json | 5 + src/tabs/Game/package.json | 11 + src/tabs/Game/tsconfig.json | 5 + src/tabs/MarkSweep/package.json | 11 + src/tabs/MarkSweep/tsconfig.json | 5 + src/tabs/Nbody/package.json | 11 + src/tabs/Nbody/tsconfig.json | 5 + src/tabs/Painter/package.json | 11 + src/tabs/Painter/tsconfig.json | 5 + .../DebugDrawCanvas.tsx | 0 src/tabs/{physics_2d => Physics2D}/index.tsx | 0 src/tabs/Physics2D/package.json | 11 + src/tabs/Physics2D/tsconfig.json | 5 + src/tabs/Pixnflix/package.json | 11 + src/tabs/Pixnflix/tsconfig.json | 5 + src/tabs/Plotly/package.json | 11 + src/tabs/Plotly/tsconfig.json | 5 + src/tabs/Repeat/package.json | 11 + src/tabs/Repeat/tsconfig.json | 5 + src/tabs/Repl/package.json | 11 + src/tabs/Repl/tsconfig.json | 5 + src/tabs/RobotSimulation/package.json | 11 + src/tabs/RobotSimulation/tsconfig.json | 5 + src/tabs/Rune/package.json | 11 + src/tabs/Rune/tsconfig.json | 5 + src/tabs/Sound/package.json | 11 + src/tabs/Sound/tsconfig.json | 5 + src/tabs/SoundMatrix/package.json | 11 + src/tabs/SoundMatrix/tsconfig.json | 5 + src/tabs/StereoSound/package.json | 11 + src/tabs/StereoSound/tsconfig.json | 5 + src/tabs/Unittest/package.json | 11 + src/tabs/Unittest/tsconfig.json | 5 + src/tabs/UnityAcademy/package.json | 11 + src/tabs/UnityAcademy/tsconfig.json | 5 + src/tabs/common/package.json | 11 + src/tabs/common/tsconfig.json | 5 + src/tabs/package.json | 7 + src/tabs/tsconfig.json | 17 + src/tsconfig.json | 6 - src/typings/anim_types.ts | 17 - 309 files changed, 2570 insertions(+), 1554 deletions(-) create mode 100644 src/bundles/ar/package.json rename src/bundles/ar/{ => src}/AR.ts (100%) rename src/bundles/ar/{ => src}/ObjectsHelper.ts (100%) rename src/bundles/ar/{ => src}/OverlayHelper.ts (100%) rename src/bundles/ar/{ => src}/index.ts (100%) create mode 100644 src/bundles/ar/tsconfig.json create mode 100644 src/bundles/arcade_2d/package.json rename src/bundles/arcade_2d/{ => src}/audio.ts (100%) rename src/bundles/arcade_2d/{ => src}/constants.ts (100%) rename src/bundles/arcade_2d/{ => src}/functions.ts (100%) rename src/bundles/arcade_2d/{ => src}/gameobject.ts (100%) rename src/bundles/arcade_2d/{ => src}/index.ts (100%) rename src/bundles/arcade_2d/{ => src}/phaserScene.ts (100%) rename src/bundles/arcade_2d/{ => src}/types.ts (100%) create mode 100644 src/bundles/arcade_2d/tsconfig.json create mode 100644 src/bundles/binary_tree/package.json rename src/bundles/binary_tree/{ => src}/functions.ts (100%) rename src/bundles/binary_tree/{ => src}/index.ts (100%) rename src/bundles/binary_tree/{ => src}/types.ts (100%) create mode 100644 src/bundles/binary_tree/tsconfig.json create mode 100644 src/bundles/communication/package.json rename src/bundles/communication/{ => src}/Communications.ts (100%) rename src/bundles/communication/{ => src}/GlobalStateController.ts (100%) rename src/bundles/communication/{ => src}/MqttController.ts (100%) rename src/bundles/communication/{ => src}/MultiUserController.ts (100%) rename src/bundles/communication/{ => src}/RpcController.ts (100%) rename src/bundles/communication/{ => src}/__tests__/index.ts (100%) rename src/bundles/communication/{ => src}/index.ts (100%) create mode 100644 src/bundles/communication/tsconfig.json create mode 100644 src/bundles/copy_gc/package.json rename src/bundles/copy_gc/{ => src}/index.ts (100%) rename src/bundles/copy_gc/{ => src}/types.ts (100%) create mode 100644 src/bundles/copy_gc/tsconfig.json create mode 100644 src/bundles/csg/package.json rename src/bundles/csg/{ => src}/constants.ts (100%) rename src/bundles/csg/{ => src}/core.ts (100%) rename src/bundles/csg/{ => src}/functions.ts (100%) rename src/bundles/csg/{ => src}/index.ts (100%) rename src/bundles/csg/{ => src}/input_tracker.ts (100%) rename src/bundles/csg/{ => src}/jscad/renderer.ts (100%) rename src/bundles/csg/{ => src}/jscad/types.ts (100%) rename src/bundles/csg/{ => src}/listener_tracker.ts (100%) rename src/bundles/csg/{ => src}/samples/_imports.js (100%) rename src/bundles/csg/{ => src}/samples/christmas.js (100%) rename src/bundles/csg/{ => src}/samples/colours.js (100%) rename src/bundles/csg/{ => src}/samples/operations.js (100%) rename src/bundles/csg/{ => src}/samples/primitives.js (100%) rename src/bundles/csg/{ => src}/samples/rotation.js (100%) rename src/bundles/csg/{ => src}/samples/ship.js (100%) rename src/bundles/csg/{ => src}/samples/sierpinski.js (100%) rename src/bundles/csg/{ => src}/samples/snowglobe.js (100%) rename src/bundles/csg/{ => src}/samples/steinmetz.js (100%) rename src/bundles/csg/{ => src}/stateful_renderer.ts (100%) rename src/bundles/csg/{ => src}/types.ts (100%) rename src/bundles/csg/{ => src}/utilities.ts (100%) create mode 100644 src/bundles/csg/tsconfig.json create mode 100644 src/bundles/curve/package.json rename src/bundles/curve/{ => src}/__tests__/curve.ts (100%) rename src/bundles/curve/{ => src}/curves_webgl.ts (100%) rename src/bundles/curve/{ => src}/functions.ts (100%) rename src/bundles/curve/{ => src}/index.ts (100%) rename src/bundles/curve/{ => src}/samples/canvases.js (100%) rename src/bundles/curve/{ => src}/samples/imports.js (100%) rename src/bundles/curve/{ => src}/type_interface.ts (100%) rename src/bundles/curve/{ => src}/types.ts (100%) create mode 100644 src/bundles/curve/tsconfig.json create mode 100644 src/bundles/game/package.json rename src/bundles/game/{ => src}/functions.ts (100%) rename src/bundles/game/{ => src}/index.ts (100%) rename src/bundles/game/{ => src}/types.ts (100%) create mode 100644 src/bundles/game/tsconfig.json create mode 100644 src/bundles/mark_sweep/package.json rename src/bundles/mark_sweep/{ => src}/index.ts (100%) rename src/bundles/mark_sweep/{ => src}/types.ts (100%) create mode 100644 src/bundles/mark_sweep/tsconfig.json create mode 100644 src/bundles/nbody/package.json rename src/bundles/nbody/{ => src}/CelestialBody.ts (100%) rename src/bundles/nbody/{ => src}/Force.ts (100%) rename src/bundles/nbody/{ => src}/Misc.ts (100%) rename src/bundles/nbody/{ => src}/SimulateFunction.ts (100%) rename src/bundles/nbody/{ => src}/Simulation.ts (100%) rename src/bundles/nbody/{ => src}/State.ts (100%) rename src/bundles/nbody/{ => src}/Transformation.ts (100%) rename src/bundles/nbody/{ => src}/Universe.ts (100%) rename src/bundles/nbody/{ => src}/Vector.ts (100%) rename src/bundles/nbody/{ => src}/index.ts (100%) create mode 100644 src/bundles/nbody/tsconfig.json create mode 100644 src/bundles/package.json create mode 100644 src/bundles/painter/package.json rename src/bundles/painter/{ => src}/functions.ts (100%) rename src/bundles/painter/{ => src}/index.ts (100%) rename src/bundles/painter/{ => src}/painter.ts (100%) create mode 100644 src/bundles/painter/tsconfig.json create mode 100644 src/bundles/physics_2d/package.json rename src/bundles/physics_2d/{ => src}/PhysicsObject.ts (100%) rename src/bundles/physics_2d/{ => src}/PhysicsWorld.ts (100%) rename src/bundles/physics_2d/{ => src}/functions.ts (100%) rename src/bundles/physics_2d/{ => src}/index.ts (100%) rename src/bundles/physics_2d/{ => src}/types.ts (100%) create mode 100644 src/bundles/physics_2d/tsconfig.json create mode 100644 src/bundles/pix_n_flix/package.json rename src/bundles/pix_n_flix/{ => src}/constants.ts (100%) rename src/bundles/pix_n_flix/{ => src}/functions.ts (100%) rename src/bundles/pix_n_flix/{ => src}/index.ts (100%) rename src/bundles/pix_n_flix/{ => src}/types.ts (100%) create mode 100644 src/bundles/pix_n_flix/tsconfig.json create mode 100644 src/bundles/plotly/package.json rename src/bundles/plotly/{ => src}/curve_functions.ts (100%) rename src/bundles/plotly/{ => src}/functions.ts (100%) rename src/bundles/plotly/{ => src}/index.ts (100%) rename src/bundles/plotly/{ => src}/plotly.ts (100%) rename src/bundles/plotly/{ => src}/sound_functions.ts (100%) create mode 100644 src/bundles/plotly/tsconfig.json create mode 100644 src/bundles/remote_execution/package.json rename src/bundles/remote_execution/{ => src}/ev3/index.ts (100%) rename src/bundles/remote_execution/{ => src}/index.ts (100%) create mode 100644 src/bundles/remote_execution/tsconfig.json create mode 100644 src/bundles/repeat/package.json rename src/bundles/repeat/{ => src}/__tests__/index.ts (100%) rename src/bundles/repeat/{ => src}/functions.ts (100%) rename src/bundles/repeat/{ => src}/index.ts (100%) create mode 100644 src/bundles/repeat/tsconfig.json create mode 100644 src/bundles/repl/package.json rename src/bundles/repl/{ => src}/config.ts (100%) rename src/bundles/repl/{ => src}/functions.ts (100%) rename src/bundles/repl/{ => src}/index.ts (100%) rename src/bundles/repl/{ => src}/programmable_repl.ts (100%) create mode 100644 src/bundles/repl/tsconfig.json create mode 100644 src/bundles/robot_simulation/package.json rename src/bundles/robot_simulation/{ => src}/config.ts (93%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/components/Chassis.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/components/Mesh.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/components/Motor.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/components/Wheel.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/ev3/default/ev3.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/feedback_control/PidController.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/sensor/ColorSensor.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/program/Program.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/__tests__/utils/mergeConfig.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/environment/Cuboid.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/environment/Paper.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/components/Chassis.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/components/Mesh.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/components/Motor.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/components/Wheel.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/ev3/default/config.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/ev3/default/ev3.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/ev3/default/types.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/feedback_control/PidController.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/sensor/ColorSensor.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/sensor/UltrasonicSensor.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/ev3/sensor/types.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/index.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/program/Program.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/program/error.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/program/evaluate.ts (100%) rename src/bundles/robot_simulation/{ => src}/controllers/utils/mergeConfig.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/Core/CallbackHandler.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Core/Controller.ts (95%) rename src/bundles/robot_simulation/{ => src}/engine/Core/Events.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Core/RobotConsole.ts (93%) rename src/bundles/robot_simulation/{ => src}/engine/Core/Timer.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Entity/Entity.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Entity/EntityFactory.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Math/Convert.ts (98%) rename src/bundles/robot_simulation/{ => src}/engine/Math/Vector.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Physics.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Render/Renderer.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/Render/debug/DebugArrow.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/Render/helpers/Camera.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/Render/helpers/GLTF.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/Render/helpers/MeshFactory.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/Render/helpers/Scene.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/World.ts (96%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Core/CallbackHandler.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Core/Controller.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Core/Events.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Core/RobotConsole.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Core/Timer.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Entity/Entity.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Math/Convert.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Physics.ts (97%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Render/MeshFactory.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/__tests__/Render/helpers/Camera.ts (100%) rename src/bundles/robot_simulation/{ => src}/engine/index.ts (98%) rename src/bundles/robot_simulation/{ => src}/ev3_functions.ts (100%) rename src/bundles/robot_simulation/{ => src}/helper_functions.ts (96%) rename src/bundles/robot_simulation/{ => src}/index.ts (94%) create mode 100644 src/bundles/robot_simulation/tsconfig.json create mode 100644 src/bundles/rune/package.json rename src/bundles/rune/{ => src}/display.ts (100%) rename src/bundles/rune/{ => src}/functions.ts (100%) rename src/bundles/rune/{ => src}/index.ts (100%) rename src/bundles/rune/{ => src}/rune.ts (100%) rename src/bundles/rune/{ => src}/runes_ops.ts (100%) rename src/bundles/rune/{ => src}/runes_webgl.ts (100%) rename src/bundles/rune/{ => src}/ruomu_journal.md (100%) create mode 100644 src/bundles/rune/tsconfig.json create mode 100644 src/bundles/rune_in_words/package.json rename src/bundles/rune_in_words/{ => src}/functions.ts (100%) rename src/bundles/rune_in_words/{ => src}/index.ts (100%) rename src/bundles/rune_in_words/{ => src}/rune.ts (100%) rename src/bundles/rune_in_words/{ => src}/runes_ops.ts (100%) create mode 100644 src/bundles/rune_in_words/tsconfig.json create mode 100644 src/bundles/scrabble/package.json rename src/bundles/scrabble/{ => src}/__tests__/__snapshots__/index.ts.snap (100%) rename src/bundles/scrabble/{ => src}/__tests__/index.ts (100%) rename src/bundles/scrabble/{ => src}/functions.ts (100%) rename src/bundles/scrabble/{ => src}/index.ts (100%) create mode 100644 src/bundles/scrabble/tsconfig.json delete mode 100644 src/bundles/scrabble/types.ts create mode 100644 src/bundles/sound/package.json rename src/bundles/sound/{ => src}/__tests__/sound.test.ts (100%) rename src/bundles/sound/{ => src}/functions.ts (100%) rename src/bundles/sound/{ => src}/index.ts (100%) rename src/bundles/{stereo_sound => sound/src}/riffwave.ts (100%) rename src/bundles/sound/{ => src}/types.ts (100%) create mode 100644 src/bundles/sound/tsconfig.json create mode 100644 src/bundles/sound_matrix/package.json rename src/bundles/sound_matrix/{ => src}/functions.ts (100%) rename src/bundles/sound_matrix/{ => src}/index.ts (100%) rename src/bundles/sound_matrix/{ => src}/list.ts (100%) rename src/bundles/sound_matrix/{ => src}/types.ts (100%) create mode 100644 src/bundles/sound_matrix/tsconfig.json create mode 100644 src/bundles/stereo_sound/package.json rename src/bundles/stereo_sound/{ => src}/functions.ts (100%) rename src/bundles/stereo_sound/{ => src}/index.ts (100%) rename src/bundles/{sound => stereo_sound/src}/riffwave.ts (100%) rename src/bundles/stereo_sound/{ => src}/types.ts (100%) create mode 100644 src/bundles/stereo_sound/tsconfig.json create mode 100644 src/bundles/tsconfig.json create mode 100644 src/bundles/unittest/package.json rename src/bundles/unittest/{ => src}/__tests__/index.ts (100%) rename src/bundles/unittest/{ => src}/asserts.ts (100%) rename src/bundles/unittest/{ => src}/functions.ts (100%) rename src/bundles/unittest/{ => src}/index.ts (100%) rename src/bundles/unittest/{ => src}/mocks.ts (100%) rename src/bundles/unittest/{ => src}/types.ts (100%) create mode 100644 src/bundles/unittest/tsconfig.json create mode 100644 src/bundles/unity_academy/package.json rename src/bundles/unity_academy/{ => src}/UnityAcademy.tsx (100%) rename src/bundles/unity_academy/{ => src}/UnityAcademyMaths.ts (100%) rename src/bundles/unity_academy/{ => src}/config.ts (100%) rename src/bundles/unity_academy/{ => src}/functions.ts (100%) rename src/bundles/unity_academy/{ => src}/index.ts (100%) create mode 100644 src/bundles/unity_academy/tsconfig.json create mode 100644 src/bundles/wasm/package.json rename src/bundles/wasm/{ => src}/index.ts (100%) rename src/bundles/wasm/{ => src}/wabt.ts (100%) create mode 100644 src/bundles/wasm/tsconfig.json delete mode 100644 src/common/deepPartial.ts rename src/{common => commons}/__tests__/hextocolor.ts (100%) rename src/{typings => commons}/js-slang/context.d.ts (100%) rename src/{common => commons}/specialErrors.ts (100%) create mode 100644 src/commons/tsconfig.json rename src/{typings => commons}/type_map/index.ts (100%) rename src/{typings/type_helpers.ts => commons/types.ts} (54%) rename src/{common => commons}/utilities.ts (100%) create mode 100644 src/tabs/ArcadeTwod/package.json create mode 100644 src/tabs/ArcadeTwod/tsconfig.json create mode 100644 src/tabs/AugmentedReality/package.json create mode 100644 src/tabs/AugmentedReality/tsconfig.json create mode 100644 src/tabs/CopyGc/package.json create mode 100644 src/tabs/CopyGc/tsconfig.json create mode 100644 src/tabs/Csg/package.json create mode 100644 src/tabs/Csg/tsconfig.json create mode 100644 src/tabs/Curve/package.json create mode 100644 src/tabs/Curve/tsconfig.json create mode 100644 src/tabs/Game/package.json create mode 100644 src/tabs/Game/tsconfig.json create mode 100644 src/tabs/MarkSweep/package.json create mode 100644 src/tabs/MarkSweep/tsconfig.json create mode 100644 src/tabs/Nbody/package.json create mode 100644 src/tabs/Nbody/tsconfig.json create mode 100644 src/tabs/Painter/package.json create mode 100644 src/tabs/Painter/tsconfig.json rename src/tabs/{physics_2d => Physics2D}/DebugDrawCanvas.tsx (100%) rename src/tabs/{physics_2d => Physics2D}/index.tsx (100%) create mode 100644 src/tabs/Physics2D/package.json create mode 100644 src/tabs/Physics2D/tsconfig.json create mode 100644 src/tabs/Pixnflix/package.json create mode 100644 src/tabs/Pixnflix/tsconfig.json create mode 100644 src/tabs/Plotly/package.json create mode 100644 src/tabs/Plotly/tsconfig.json create mode 100644 src/tabs/Repeat/package.json create mode 100644 src/tabs/Repeat/tsconfig.json create mode 100644 src/tabs/Repl/package.json create mode 100644 src/tabs/Repl/tsconfig.json create mode 100644 src/tabs/RobotSimulation/package.json create mode 100644 src/tabs/RobotSimulation/tsconfig.json create mode 100644 src/tabs/Rune/package.json create mode 100644 src/tabs/Rune/tsconfig.json create mode 100644 src/tabs/Sound/package.json create mode 100644 src/tabs/Sound/tsconfig.json create mode 100644 src/tabs/SoundMatrix/package.json create mode 100644 src/tabs/SoundMatrix/tsconfig.json create mode 100644 src/tabs/StereoSound/package.json create mode 100644 src/tabs/StereoSound/tsconfig.json create mode 100644 src/tabs/Unittest/package.json create mode 100644 src/tabs/Unittest/tsconfig.json create mode 100644 src/tabs/UnityAcademy/package.json create mode 100644 src/tabs/UnityAcademy/tsconfig.json create mode 100644 src/tabs/common/package.json create mode 100644 src/tabs/common/tsconfig.json create mode 100644 src/tabs/package.json create mode 100644 src/tabs/tsconfig.json delete mode 100644 src/typings/anim_types.ts diff --git a/src/bundles/ar/package.json b/src/bundles/ar/package.json new file mode 100644 index 0000000000..5e473c36a1 --- /dev/null +++ b/src/bundles/ar/package.json @@ -0,0 +1,8 @@ +{ + "name": "ar", + "version": "1.0.0", + "private": true, + "dependencies": { + "saar": "^1.0.4" + } +} diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/src/AR.ts similarity index 100% rename from src/bundles/ar/AR.ts rename to src/bundles/ar/src/AR.ts diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/src/ObjectsHelper.ts similarity index 100% rename from src/bundles/ar/ObjectsHelper.ts rename to src/bundles/ar/src/ObjectsHelper.ts diff --git a/src/bundles/ar/OverlayHelper.ts b/src/bundles/ar/src/OverlayHelper.ts similarity index 100% rename from src/bundles/ar/OverlayHelper.ts rename to src/bundles/ar/src/OverlayHelper.ts diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/src/index.ts similarity index 100% rename from src/bundles/ar/index.ts rename to src/bundles/ar/src/index.ts diff --git a/src/bundles/ar/tsconfig.json b/src/bundles/ar/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/ar/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/arcade_2d/package.json b/src/bundles/arcade_2d/package.json new file mode 100644 index 0000000000..b86222958e --- /dev/null +++ b/src/bundles/arcade_2d/package.json @@ -0,0 +1,8 @@ +{ + "name": "arcade_2d", + "version": "1.0.0", + "private": true, + "dependencies": { + "phaser": "^3.54.0" + } +} diff --git a/src/bundles/arcade_2d/audio.ts b/src/bundles/arcade_2d/src/audio.ts similarity index 100% rename from src/bundles/arcade_2d/audio.ts rename to src/bundles/arcade_2d/src/audio.ts diff --git a/src/bundles/arcade_2d/constants.ts b/src/bundles/arcade_2d/src/constants.ts similarity index 100% rename from src/bundles/arcade_2d/constants.ts rename to src/bundles/arcade_2d/src/constants.ts diff --git a/src/bundles/arcade_2d/functions.ts b/src/bundles/arcade_2d/src/functions.ts similarity index 100% rename from src/bundles/arcade_2d/functions.ts rename to src/bundles/arcade_2d/src/functions.ts diff --git a/src/bundles/arcade_2d/gameobject.ts b/src/bundles/arcade_2d/src/gameobject.ts similarity index 100% rename from src/bundles/arcade_2d/gameobject.ts rename to src/bundles/arcade_2d/src/gameobject.ts diff --git a/src/bundles/arcade_2d/index.ts b/src/bundles/arcade_2d/src/index.ts similarity index 100% rename from src/bundles/arcade_2d/index.ts rename to src/bundles/arcade_2d/src/index.ts diff --git a/src/bundles/arcade_2d/phaserScene.ts b/src/bundles/arcade_2d/src/phaserScene.ts similarity index 100% rename from src/bundles/arcade_2d/phaserScene.ts rename to src/bundles/arcade_2d/src/phaserScene.ts diff --git a/src/bundles/arcade_2d/types.ts b/src/bundles/arcade_2d/src/types.ts similarity index 100% rename from src/bundles/arcade_2d/types.ts rename to src/bundles/arcade_2d/src/types.ts diff --git a/src/bundles/arcade_2d/tsconfig.json b/src/bundles/arcade_2d/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/arcade_2d/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/binary_tree/package.json b/src/bundles/binary_tree/package.json new file mode 100644 index 0000000000..2bb6dd6263 --- /dev/null +++ b/src/bundles/binary_tree/package.json @@ -0,0 +1,5 @@ +{ + "name": "binary_tree", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/binary_tree/functions.ts b/src/bundles/binary_tree/src/functions.ts similarity index 100% rename from src/bundles/binary_tree/functions.ts rename to src/bundles/binary_tree/src/functions.ts diff --git a/src/bundles/binary_tree/index.ts b/src/bundles/binary_tree/src/index.ts similarity index 100% rename from src/bundles/binary_tree/index.ts rename to src/bundles/binary_tree/src/index.ts diff --git a/src/bundles/binary_tree/types.ts b/src/bundles/binary_tree/src/types.ts similarity index 100% rename from src/bundles/binary_tree/types.ts rename to src/bundles/binary_tree/src/types.ts diff --git a/src/bundles/binary_tree/tsconfig.json b/src/bundles/binary_tree/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/binary_tree/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/communication/package.json b/src/bundles/communication/package.json new file mode 100644 index 0000000000..183a3f1cd0 --- /dev/null +++ b/src/bundles/communication/package.json @@ -0,0 +1,9 @@ +{ + "name": "communication", + "version": "1.0.0", + "private": true, + "dependencies": { + "mqtt": "^4.3.7", + "uniqid": "^5.4.0" + } +} diff --git a/src/bundles/communication/Communications.ts b/src/bundles/communication/src/Communications.ts similarity index 100% rename from src/bundles/communication/Communications.ts rename to src/bundles/communication/src/Communications.ts diff --git a/src/bundles/communication/GlobalStateController.ts b/src/bundles/communication/src/GlobalStateController.ts similarity index 100% rename from src/bundles/communication/GlobalStateController.ts rename to src/bundles/communication/src/GlobalStateController.ts diff --git a/src/bundles/communication/MqttController.ts b/src/bundles/communication/src/MqttController.ts similarity index 100% rename from src/bundles/communication/MqttController.ts rename to src/bundles/communication/src/MqttController.ts diff --git a/src/bundles/communication/MultiUserController.ts b/src/bundles/communication/src/MultiUserController.ts similarity index 100% rename from src/bundles/communication/MultiUserController.ts rename to src/bundles/communication/src/MultiUserController.ts diff --git a/src/bundles/communication/RpcController.ts b/src/bundles/communication/src/RpcController.ts similarity index 100% rename from src/bundles/communication/RpcController.ts rename to src/bundles/communication/src/RpcController.ts diff --git a/src/bundles/communication/__tests__/index.ts b/src/bundles/communication/src/__tests__/index.ts similarity index 100% rename from src/bundles/communication/__tests__/index.ts rename to src/bundles/communication/src/__tests__/index.ts diff --git a/src/bundles/communication/index.ts b/src/bundles/communication/src/index.ts similarity index 100% rename from src/bundles/communication/index.ts rename to src/bundles/communication/src/index.ts diff --git a/src/bundles/communication/tsconfig.json b/src/bundles/communication/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/communication/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/copy_gc/package.json b/src/bundles/copy_gc/package.json new file mode 100644 index 0000000000..8ced0a0c43 --- /dev/null +++ b/src/bundles/copy_gc/package.json @@ -0,0 +1,5 @@ +{ + "name": "copy_gc", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/copy_gc/index.ts b/src/bundles/copy_gc/src/index.ts similarity index 100% rename from src/bundles/copy_gc/index.ts rename to src/bundles/copy_gc/src/index.ts diff --git a/src/bundles/copy_gc/types.ts b/src/bundles/copy_gc/src/types.ts similarity index 100% rename from src/bundles/copy_gc/types.ts rename to src/bundles/copy_gc/src/types.ts diff --git a/src/bundles/copy_gc/tsconfig.json b/src/bundles/copy_gc/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/copy_gc/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/csg/package.json b/src/bundles/csg/package.json new file mode 100644 index 0000000000..3ba31a099b --- /dev/null +++ b/src/bundles/csg/package.json @@ -0,0 +1,10 @@ +{ + "name": "csg", + "version": "1.0.0", + "private": true, + "dependencies": { + "@jscad/modeling": "2.9.6", + "@jscad/regl-renderer": "^2.6.1", + "@jscad/stl-serializer": "2.1.11" + } +} diff --git a/src/bundles/csg/constants.ts b/src/bundles/csg/src/constants.ts similarity index 100% rename from src/bundles/csg/constants.ts rename to src/bundles/csg/src/constants.ts diff --git a/src/bundles/csg/core.ts b/src/bundles/csg/src/core.ts similarity index 100% rename from src/bundles/csg/core.ts rename to src/bundles/csg/src/core.ts diff --git a/src/bundles/csg/functions.ts b/src/bundles/csg/src/functions.ts similarity index 100% rename from src/bundles/csg/functions.ts rename to src/bundles/csg/src/functions.ts diff --git a/src/bundles/csg/index.ts b/src/bundles/csg/src/index.ts similarity index 100% rename from src/bundles/csg/index.ts rename to src/bundles/csg/src/index.ts diff --git a/src/bundles/csg/input_tracker.ts b/src/bundles/csg/src/input_tracker.ts similarity index 100% rename from src/bundles/csg/input_tracker.ts rename to src/bundles/csg/src/input_tracker.ts diff --git a/src/bundles/csg/jscad/renderer.ts b/src/bundles/csg/src/jscad/renderer.ts similarity index 100% rename from src/bundles/csg/jscad/renderer.ts rename to src/bundles/csg/src/jscad/renderer.ts diff --git a/src/bundles/csg/jscad/types.ts b/src/bundles/csg/src/jscad/types.ts similarity index 100% rename from src/bundles/csg/jscad/types.ts rename to src/bundles/csg/src/jscad/types.ts diff --git a/src/bundles/csg/listener_tracker.ts b/src/bundles/csg/src/listener_tracker.ts similarity index 100% rename from src/bundles/csg/listener_tracker.ts rename to src/bundles/csg/src/listener_tracker.ts diff --git a/src/bundles/csg/samples/_imports.js b/src/bundles/csg/src/samples/_imports.js similarity index 100% rename from src/bundles/csg/samples/_imports.js rename to src/bundles/csg/src/samples/_imports.js diff --git a/src/bundles/csg/samples/christmas.js b/src/bundles/csg/src/samples/christmas.js similarity index 100% rename from src/bundles/csg/samples/christmas.js rename to src/bundles/csg/src/samples/christmas.js diff --git a/src/bundles/csg/samples/colours.js b/src/bundles/csg/src/samples/colours.js similarity index 100% rename from src/bundles/csg/samples/colours.js rename to src/bundles/csg/src/samples/colours.js diff --git a/src/bundles/csg/samples/operations.js b/src/bundles/csg/src/samples/operations.js similarity index 100% rename from src/bundles/csg/samples/operations.js rename to src/bundles/csg/src/samples/operations.js diff --git a/src/bundles/csg/samples/primitives.js b/src/bundles/csg/src/samples/primitives.js similarity index 100% rename from src/bundles/csg/samples/primitives.js rename to src/bundles/csg/src/samples/primitives.js diff --git a/src/bundles/csg/samples/rotation.js b/src/bundles/csg/src/samples/rotation.js similarity index 100% rename from src/bundles/csg/samples/rotation.js rename to src/bundles/csg/src/samples/rotation.js diff --git a/src/bundles/csg/samples/ship.js b/src/bundles/csg/src/samples/ship.js similarity index 100% rename from src/bundles/csg/samples/ship.js rename to src/bundles/csg/src/samples/ship.js diff --git a/src/bundles/csg/samples/sierpinski.js b/src/bundles/csg/src/samples/sierpinski.js similarity index 100% rename from src/bundles/csg/samples/sierpinski.js rename to src/bundles/csg/src/samples/sierpinski.js diff --git a/src/bundles/csg/samples/snowglobe.js b/src/bundles/csg/src/samples/snowglobe.js similarity index 100% rename from src/bundles/csg/samples/snowglobe.js rename to src/bundles/csg/src/samples/snowglobe.js diff --git a/src/bundles/csg/samples/steinmetz.js b/src/bundles/csg/src/samples/steinmetz.js similarity index 100% rename from src/bundles/csg/samples/steinmetz.js rename to src/bundles/csg/src/samples/steinmetz.js diff --git a/src/bundles/csg/stateful_renderer.ts b/src/bundles/csg/src/stateful_renderer.ts similarity index 100% rename from src/bundles/csg/stateful_renderer.ts rename to src/bundles/csg/src/stateful_renderer.ts diff --git a/src/bundles/csg/types.ts b/src/bundles/csg/src/types.ts similarity index 100% rename from src/bundles/csg/types.ts rename to src/bundles/csg/src/types.ts diff --git a/src/bundles/csg/utilities.ts b/src/bundles/csg/src/utilities.ts similarity index 100% rename from src/bundles/csg/utilities.ts rename to src/bundles/csg/src/utilities.ts diff --git a/src/bundles/csg/tsconfig.json b/src/bundles/csg/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/csg/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/curve/package.json b/src/bundles/curve/package.json new file mode 100644 index 0000000000..d906f60f55 --- /dev/null +++ b/src/bundles/curve/package.json @@ -0,0 +1,8 @@ +{ + "name": "curve", + "version": "1.0.0", + "private": true, + "dependencies": { + "gl-matrix": "^3.3.0" + } +} diff --git a/src/bundles/curve/__tests__/curve.ts b/src/bundles/curve/src/__tests__/curve.ts similarity index 100% rename from src/bundles/curve/__tests__/curve.ts rename to src/bundles/curve/src/__tests__/curve.ts diff --git a/src/bundles/curve/curves_webgl.ts b/src/bundles/curve/src/curves_webgl.ts similarity index 100% rename from src/bundles/curve/curves_webgl.ts rename to src/bundles/curve/src/curves_webgl.ts diff --git a/src/bundles/curve/functions.ts b/src/bundles/curve/src/functions.ts similarity index 100% rename from src/bundles/curve/functions.ts rename to src/bundles/curve/src/functions.ts diff --git a/src/bundles/curve/index.ts b/src/bundles/curve/src/index.ts similarity index 100% rename from src/bundles/curve/index.ts rename to src/bundles/curve/src/index.ts diff --git a/src/bundles/curve/samples/canvases.js b/src/bundles/curve/src/samples/canvases.js similarity index 100% rename from src/bundles/curve/samples/canvases.js rename to src/bundles/curve/src/samples/canvases.js diff --git a/src/bundles/curve/samples/imports.js b/src/bundles/curve/src/samples/imports.js similarity index 100% rename from src/bundles/curve/samples/imports.js rename to src/bundles/curve/src/samples/imports.js diff --git a/src/bundles/curve/type_interface.ts b/src/bundles/curve/src/type_interface.ts similarity index 100% rename from src/bundles/curve/type_interface.ts rename to src/bundles/curve/src/type_interface.ts diff --git a/src/bundles/curve/types.ts b/src/bundles/curve/src/types.ts similarity index 100% rename from src/bundles/curve/types.ts rename to src/bundles/curve/src/types.ts diff --git a/src/bundles/curve/tsconfig.json b/src/bundles/curve/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/curve/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/game/package.json b/src/bundles/game/package.json new file mode 100644 index 0000000000..1efe8782b8 --- /dev/null +++ b/src/bundles/game/package.json @@ -0,0 +1,9 @@ +{ + "name": "game", + "version": "1.0.0", + "private": true, + "dependencies": { + "js-slang": "^1.0.81", + "phaser": "^3.54.0" + } +} diff --git a/src/bundles/game/functions.ts b/src/bundles/game/src/functions.ts similarity index 100% rename from src/bundles/game/functions.ts rename to src/bundles/game/src/functions.ts diff --git a/src/bundles/game/index.ts b/src/bundles/game/src/index.ts similarity index 100% rename from src/bundles/game/index.ts rename to src/bundles/game/src/index.ts diff --git a/src/bundles/game/types.ts b/src/bundles/game/src/types.ts similarity index 100% rename from src/bundles/game/types.ts rename to src/bundles/game/src/types.ts diff --git a/src/bundles/game/tsconfig.json b/src/bundles/game/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/game/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/mark_sweep/package.json b/src/bundles/mark_sweep/package.json new file mode 100644 index 0000000000..661d02ec03 --- /dev/null +++ b/src/bundles/mark_sweep/package.json @@ -0,0 +1,5 @@ +{ + "name": "mark_sweep", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/mark_sweep/index.ts b/src/bundles/mark_sweep/src/index.ts similarity index 100% rename from src/bundles/mark_sweep/index.ts rename to src/bundles/mark_sweep/src/index.ts diff --git a/src/bundles/mark_sweep/types.ts b/src/bundles/mark_sweep/src/types.ts similarity index 100% rename from src/bundles/mark_sweep/types.ts rename to src/bundles/mark_sweep/src/types.ts diff --git a/src/bundles/mark_sweep/tsconfig.json b/src/bundles/mark_sweep/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/mark_sweep/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/nbody/package.json b/src/bundles/nbody/package.json new file mode 100644 index 0000000000..9e067016e5 --- /dev/null +++ b/src/bundles/nbody/package.json @@ -0,0 +1,8 @@ +{ + "name": "nbody", + "version": "1.0.0", + "private": true, + "dependencies": { + "nbody": "^0.2.0" + } +} diff --git a/src/bundles/nbody/CelestialBody.ts b/src/bundles/nbody/src/CelestialBody.ts similarity index 100% rename from src/bundles/nbody/CelestialBody.ts rename to src/bundles/nbody/src/CelestialBody.ts diff --git a/src/bundles/nbody/Force.ts b/src/bundles/nbody/src/Force.ts similarity index 100% rename from src/bundles/nbody/Force.ts rename to src/bundles/nbody/src/Force.ts diff --git a/src/bundles/nbody/Misc.ts b/src/bundles/nbody/src/Misc.ts similarity index 100% rename from src/bundles/nbody/Misc.ts rename to src/bundles/nbody/src/Misc.ts diff --git a/src/bundles/nbody/SimulateFunction.ts b/src/bundles/nbody/src/SimulateFunction.ts similarity index 100% rename from src/bundles/nbody/SimulateFunction.ts rename to src/bundles/nbody/src/SimulateFunction.ts diff --git a/src/bundles/nbody/Simulation.ts b/src/bundles/nbody/src/Simulation.ts similarity index 100% rename from src/bundles/nbody/Simulation.ts rename to src/bundles/nbody/src/Simulation.ts diff --git a/src/bundles/nbody/State.ts b/src/bundles/nbody/src/State.ts similarity index 100% rename from src/bundles/nbody/State.ts rename to src/bundles/nbody/src/State.ts diff --git a/src/bundles/nbody/Transformation.ts b/src/bundles/nbody/src/Transformation.ts similarity index 100% rename from src/bundles/nbody/Transformation.ts rename to src/bundles/nbody/src/Transformation.ts diff --git a/src/bundles/nbody/Universe.ts b/src/bundles/nbody/src/Universe.ts similarity index 100% rename from src/bundles/nbody/Universe.ts rename to src/bundles/nbody/src/Universe.ts diff --git a/src/bundles/nbody/Vector.ts b/src/bundles/nbody/src/Vector.ts similarity index 100% rename from src/bundles/nbody/Vector.ts rename to src/bundles/nbody/src/Vector.ts diff --git a/src/bundles/nbody/index.ts b/src/bundles/nbody/src/index.ts similarity index 100% rename from src/bundles/nbody/index.ts rename to src/bundles/nbody/src/index.ts diff --git a/src/bundles/nbody/tsconfig.json b/src/bundles/nbody/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/nbody/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/package.json b/src/bundles/package.json new file mode 100644 index 0000000000..1fd4312d44 --- /dev/null +++ b/src/bundles/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "name": "bundles", + "workspaces": [ + "./*" + ] +} diff --git a/src/bundles/painter/package.json b/src/bundles/painter/package.json new file mode 100644 index 0000000000..4489c4b995 --- /dev/null +++ b/src/bundles/painter/package.json @@ -0,0 +1,11 @@ +{ + "name": "painter", + "version": "1.0.0", + "private": true, + "dependencies": { + "plotly.js-dist": "^2.17.1" + }, + "devDependencies": { + "@types/plotly.js": "^2.35.4" + } +} diff --git a/src/bundles/painter/functions.ts b/src/bundles/painter/src/functions.ts similarity index 100% rename from src/bundles/painter/functions.ts rename to src/bundles/painter/src/functions.ts diff --git a/src/bundles/painter/index.ts b/src/bundles/painter/src/index.ts similarity index 100% rename from src/bundles/painter/index.ts rename to src/bundles/painter/src/index.ts diff --git a/src/bundles/painter/painter.ts b/src/bundles/painter/src/painter.ts similarity index 100% rename from src/bundles/painter/painter.ts rename to src/bundles/painter/src/painter.ts diff --git a/src/bundles/painter/tsconfig.json b/src/bundles/painter/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/painter/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/physics_2d/package.json b/src/bundles/physics_2d/package.json new file mode 100644 index 0000000000..e7de1e7019 --- /dev/null +++ b/src/bundles/physics_2d/package.json @@ -0,0 +1,8 @@ +{ + "name": "physics_2d", + "version": "1.0.0", + "private": true, + "dependencies": { + "@box2d/core": "^0.10.0" + } +} diff --git a/src/bundles/physics_2d/PhysicsObject.ts b/src/bundles/physics_2d/src/PhysicsObject.ts similarity index 100% rename from src/bundles/physics_2d/PhysicsObject.ts rename to src/bundles/physics_2d/src/PhysicsObject.ts diff --git a/src/bundles/physics_2d/PhysicsWorld.ts b/src/bundles/physics_2d/src/PhysicsWorld.ts similarity index 100% rename from src/bundles/physics_2d/PhysicsWorld.ts rename to src/bundles/physics_2d/src/PhysicsWorld.ts diff --git a/src/bundles/physics_2d/functions.ts b/src/bundles/physics_2d/src/functions.ts similarity index 100% rename from src/bundles/physics_2d/functions.ts rename to src/bundles/physics_2d/src/functions.ts diff --git a/src/bundles/physics_2d/index.ts b/src/bundles/physics_2d/src/index.ts similarity index 100% rename from src/bundles/physics_2d/index.ts rename to src/bundles/physics_2d/src/index.ts diff --git a/src/bundles/physics_2d/types.ts b/src/bundles/physics_2d/src/types.ts similarity index 100% rename from src/bundles/physics_2d/types.ts rename to src/bundles/physics_2d/src/types.ts diff --git a/src/bundles/physics_2d/tsconfig.json b/src/bundles/physics_2d/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/physics_2d/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/pix_n_flix/package.json b/src/bundles/pix_n_flix/package.json new file mode 100644 index 0000000000..0c0d88f781 --- /dev/null +++ b/src/bundles/pix_n_flix/package.json @@ -0,0 +1,5 @@ +{ + "name": "pix_n_flix", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/pix_n_flix/constants.ts b/src/bundles/pix_n_flix/src/constants.ts similarity index 100% rename from src/bundles/pix_n_flix/constants.ts rename to src/bundles/pix_n_flix/src/constants.ts diff --git a/src/bundles/pix_n_flix/functions.ts b/src/bundles/pix_n_flix/src/functions.ts similarity index 100% rename from src/bundles/pix_n_flix/functions.ts rename to src/bundles/pix_n_flix/src/functions.ts diff --git a/src/bundles/pix_n_flix/index.ts b/src/bundles/pix_n_flix/src/index.ts similarity index 100% rename from src/bundles/pix_n_flix/index.ts rename to src/bundles/pix_n_flix/src/index.ts diff --git a/src/bundles/pix_n_flix/types.ts b/src/bundles/pix_n_flix/src/types.ts similarity index 100% rename from src/bundles/pix_n_flix/types.ts rename to src/bundles/pix_n_flix/src/types.ts diff --git a/src/bundles/pix_n_flix/tsconfig.json b/src/bundles/pix_n_flix/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/pix_n_flix/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/plotly/package.json b/src/bundles/plotly/package.json new file mode 100644 index 0000000000..6913303082 --- /dev/null +++ b/src/bundles/plotly/package.json @@ -0,0 +1,9 @@ +{ + "name": "plotly", + "version": "1.0.0", + "private": true, + "dependencies": { + "js-slang": "^1.0.81", + "sound": "workspace:^" + } +} diff --git a/src/bundles/plotly/curve_functions.ts b/src/bundles/plotly/src/curve_functions.ts similarity index 100% rename from src/bundles/plotly/curve_functions.ts rename to src/bundles/plotly/src/curve_functions.ts diff --git a/src/bundles/plotly/functions.ts b/src/bundles/plotly/src/functions.ts similarity index 100% rename from src/bundles/plotly/functions.ts rename to src/bundles/plotly/src/functions.ts diff --git a/src/bundles/plotly/index.ts b/src/bundles/plotly/src/index.ts similarity index 100% rename from src/bundles/plotly/index.ts rename to src/bundles/plotly/src/index.ts diff --git a/src/bundles/plotly/plotly.ts b/src/bundles/plotly/src/plotly.ts similarity index 100% rename from src/bundles/plotly/plotly.ts rename to src/bundles/plotly/src/plotly.ts diff --git a/src/bundles/plotly/sound_functions.ts b/src/bundles/plotly/src/sound_functions.ts similarity index 100% rename from src/bundles/plotly/sound_functions.ts rename to src/bundles/plotly/src/sound_functions.ts diff --git a/src/bundles/plotly/tsconfig.json b/src/bundles/plotly/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/plotly/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/remote_execution/package.json b/src/bundles/remote_execution/package.json new file mode 100644 index 0000000000..fa9c2bc4f6 --- /dev/null +++ b/src/bundles/remote_execution/package.json @@ -0,0 +1,8 @@ +{ + "name": "remote_execution", + "version": "1.0.0", + "private": true, + "dependencies": { + "js-slang": "^1.0.81" + } +} diff --git a/src/bundles/remote_execution/ev3/index.ts b/src/bundles/remote_execution/src/ev3/index.ts similarity index 100% rename from src/bundles/remote_execution/ev3/index.ts rename to src/bundles/remote_execution/src/ev3/index.ts diff --git a/src/bundles/remote_execution/index.ts b/src/bundles/remote_execution/src/index.ts similarity index 100% rename from src/bundles/remote_execution/index.ts rename to src/bundles/remote_execution/src/index.ts diff --git a/src/bundles/remote_execution/tsconfig.json b/src/bundles/remote_execution/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/remote_execution/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/repeat/package.json b/src/bundles/repeat/package.json new file mode 100644 index 0000000000..404ee4a120 --- /dev/null +++ b/src/bundles/repeat/package.json @@ -0,0 +1,5 @@ +{ + "name": "repeat", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/repeat/__tests__/index.ts b/src/bundles/repeat/src/__tests__/index.ts similarity index 100% rename from src/bundles/repeat/__tests__/index.ts rename to src/bundles/repeat/src/__tests__/index.ts diff --git a/src/bundles/repeat/functions.ts b/src/bundles/repeat/src/functions.ts similarity index 100% rename from src/bundles/repeat/functions.ts rename to src/bundles/repeat/src/functions.ts diff --git a/src/bundles/repeat/index.ts b/src/bundles/repeat/src/index.ts similarity index 100% rename from src/bundles/repeat/index.ts rename to src/bundles/repeat/src/index.ts diff --git a/src/bundles/repeat/tsconfig.json b/src/bundles/repeat/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/repeat/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/repl/package.json b/src/bundles/repl/package.json new file mode 100644 index 0000000000..7f7446d5d6 --- /dev/null +++ b/src/bundles/repl/package.json @@ -0,0 +1,8 @@ +{ + "name": "repl", + "version": "1.0.0", + "private": true, + "dependencies": { + "js-slang": "^1.0.81" + } +} diff --git a/src/bundles/repl/config.ts b/src/bundles/repl/src/config.ts similarity index 100% rename from src/bundles/repl/config.ts rename to src/bundles/repl/src/config.ts diff --git a/src/bundles/repl/functions.ts b/src/bundles/repl/src/functions.ts similarity index 100% rename from src/bundles/repl/functions.ts rename to src/bundles/repl/src/functions.ts diff --git a/src/bundles/repl/index.ts b/src/bundles/repl/src/index.ts similarity index 100% rename from src/bundles/repl/index.ts rename to src/bundles/repl/src/index.ts diff --git a/src/bundles/repl/programmable_repl.ts b/src/bundles/repl/src/programmable_repl.ts similarity index 100% rename from src/bundles/repl/programmable_repl.ts rename to src/bundles/repl/src/programmable_repl.ts diff --git a/src/bundles/repl/tsconfig.json b/src/bundles/repl/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/repl/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/robot_simulation/package.json b/src/bundles/robot_simulation/package.json new file mode 100644 index 0000000000..1c8b3d11ca --- /dev/null +++ b/src/bundles/robot_simulation/package.json @@ -0,0 +1,11 @@ +{ + "name": "robot_simulation", + "version": "1.0.0", + "private": true, + "dependencies": { + "three": "^0.175.0" + }, + "devDependencies": { + "@types/three": "^0.175.0" + } +} diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/src/config.ts similarity index 93% rename from src/bundles/robot_simulation/config.ts rename to src/bundles/robot_simulation/src/config.ts index b8c9bea5ff..1cb32996d9 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/src/config.ts @@ -1,4 +1,4 @@ -export const sceneConfig = { - width: 900, - height: 500, -}; +export const sceneConfig = { + width: 900, + height: 500, +}; diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Mesh.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Mesh.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Motor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/components/Motor.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Wheel.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Wheel.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/components/Wheel.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Wheel.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/ev3/default/ev3.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/feedback_control/PidController.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/ColorSensor.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/ColorSensor.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/ColorSensor.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts b/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/program/Program.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts diff --git a/src/bundles/robot_simulation/controllers/__tests__/utils/mergeConfig.ts b/src/bundles/robot_simulation/src/controllers/__tests__/utils/mergeConfig.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/__tests__/utils/mergeConfig.ts rename to src/bundles/robot_simulation/src/controllers/__tests__/utils/mergeConfig.ts diff --git a/src/bundles/robot_simulation/controllers/environment/Cuboid.ts b/src/bundles/robot_simulation/src/controllers/environment/Cuboid.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/environment/Cuboid.ts rename to src/bundles/robot_simulation/src/controllers/environment/Cuboid.ts diff --git a/src/bundles/robot_simulation/controllers/environment/Paper.ts b/src/bundles/robot_simulation/src/controllers/environment/Paper.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/environment/Paper.ts rename to src/bundles/robot_simulation/src/controllers/environment/Paper.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/src/controllers/ev3/components/Chassis.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts rename to src/bundles/robot_simulation/src/controllers/ev3/components/Chassis.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/src/controllers/ev3/components/Mesh.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts rename to src/bundles/robot_simulation/src/controllers/ev3/components/Mesh.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/src/controllers/ev3/components/Motor.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/components/Motor.ts rename to src/bundles/robot_simulation/src/controllers/ev3/components/Motor.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/src/controllers/ev3/components/Wheel.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts rename to src/bundles/robot_simulation/src/controllers/ev3/components/Wheel.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts b/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/config.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts rename to src/bundles/robot_simulation/src/controllers/ev3/ev3/default/config.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/ev3.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts rename to src/bundles/robot_simulation/src/controllers/ev3/ev3/default/ev3.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts b/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/types.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts rename to src/bundles/robot_simulation/src/controllers/ev3/ev3/default/types.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/src/controllers/ev3/feedback_control/PidController.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts rename to src/bundles/robot_simulation/src/controllers/ev3/feedback_control/PidController.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/src/controllers/ev3/sensor/ColorSensor.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts rename to src/bundles/robot_simulation/src/controllers/ev3/sensor/ColorSensor.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/src/controllers/ev3/sensor/UltrasonicSensor.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts rename to src/bundles/robot_simulation/src/controllers/ev3/sensor/UltrasonicSensor.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts b/src/bundles/robot_simulation/src/controllers/ev3/sensor/types.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/ev3/sensor/types.ts rename to src/bundles/robot_simulation/src/controllers/ev3/sensor/types.ts diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/src/controllers/index.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/index.ts rename to src/bundles/robot_simulation/src/controllers/index.ts diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/src/controllers/program/Program.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/program/Program.ts rename to src/bundles/robot_simulation/src/controllers/program/Program.ts diff --git a/src/bundles/robot_simulation/controllers/program/error.ts b/src/bundles/robot_simulation/src/controllers/program/error.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/program/error.ts rename to src/bundles/robot_simulation/src/controllers/program/error.ts diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/src/controllers/program/evaluate.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/program/evaluate.ts rename to src/bundles/robot_simulation/src/controllers/program/evaluate.ts diff --git a/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts b/src/bundles/robot_simulation/src/controllers/utils/mergeConfig.ts similarity index 100% rename from src/bundles/robot_simulation/controllers/utils/mergeConfig.ts rename to src/bundles/robot_simulation/src/controllers/utils/mergeConfig.ts diff --git a/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts b/src/bundles/robot_simulation/src/engine/Core/CallbackHandler.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Core/CallbackHandler.ts rename to src/bundles/robot_simulation/src/engine/Core/CallbackHandler.ts index 76e1bae8d2..f4b20b3b8a 100644 --- a/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts +++ b/src/bundles/robot_simulation/src/engine/Core/CallbackHandler.ts @@ -1,39 +1,39 @@ -import type { PhysicsTimingInfo } from '../Physics'; - -export type TimeoutCallback = () => void; -export type CallbackEntry = { callback: TimeoutCallback; delay: number }; - -export class CallbackHandler { - callbackStore: Array; - currentStepCount: number; - - constructor() { - this.callbackStore = []; - this.currentStepCount = 0; - } - - addCallback(callback: TimeoutCallback, delay: number): void { - this.callbackStore.push({ - callback, - delay, - }); - } - - checkCallbacks(frameTimingInfo: PhysicsTimingInfo): void { - if (frameTimingInfo.stepCount === this.currentStepCount) { - return; - } - - this.currentStepCount = frameTimingInfo.stepCount; - - this.callbackStore = this.callbackStore.filter((callbackEntry) => { - callbackEntry.delay -= frameTimingInfo.timestep; - - if (callbackEntry.delay <= 0) { - callbackEntry.callback(); - return false; - } - return true; - }); - } -} +import type { PhysicsTimingInfo } from '../Physics'; + +export type TimeoutCallback = () => void; +export type CallbackEntry = { callback: TimeoutCallback; delay: number }; + +export class CallbackHandler { + callbackStore: Array; + currentStepCount: number; + + constructor() { + this.callbackStore = []; + this.currentStepCount = 0; + } + + addCallback(callback: TimeoutCallback, delay: number): void { + this.callbackStore.push({ + callback, + delay, + }); + } + + checkCallbacks(frameTimingInfo: PhysicsTimingInfo): void { + if (frameTimingInfo.stepCount === this.currentStepCount) { + return; + } + + this.currentStepCount = frameTimingInfo.stepCount; + + this.callbackStore = this.callbackStore.filter((callbackEntry) => { + callbackEntry.delay -= frameTimingInfo.timestep; + + if (callbackEntry.delay <= 0) { + callbackEntry.callback(); + return false; + } + return true; + }); + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Controller.ts b/src/bundles/robot_simulation/src/engine/Core/Controller.ts similarity index 95% rename from src/bundles/robot_simulation/engine/Core/Controller.ts rename to src/bundles/robot_simulation/src/engine/Core/Controller.ts index 316389ea26..a895b4642d 100644 --- a/src/bundles/robot_simulation/engine/Core/Controller.ts +++ b/src/bundles/robot_simulation/src/engine/Core/Controller.ts @@ -1,93 +1,93 @@ -import type { PhysicsTimingInfo } from '../Physics'; - -export interface Controller { - name?: string; - start?(): Promise | void; - update?(timingInfo: PhysicsTimingInfo): void; - fixedUpdate?(timingInfo: PhysicsTimingInfo): void; - onDestroy?(): void; -} - -export class ControllerMap> -implements Controller { - map: M; - callbacks?: Partial; - - constructor(map: M, callbacks?: Partial) { - this.map = map; - this.callbacks = callbacks; - } - - get(name: K): M[K] { - return this.map[name]; - } - - async start(): Promise { - await this.callbacks?.start?.(); - await Promise.all( - Object.values(this.map) - .map(async (controller) => { - await controller.start?.(); - }), - ); - } - - update(timingInfo: PhysicsTimingInfo): void { - this.callbacks?.update?.(timingInfo); - Object.values(this.map) - .forEach((controller) => { - controller.update?.(timingInfo); - }); - } - - fixedUpdate(timingInfo: PhysicsTimingInfo): void { - this.callbacks?.fixedUpdate?.(timingInfo); - Object.values(this.map) - .forEach((controller) => { - controller.fixedUpdate?.(timingInfo); - }); - } - - onDestroy(): void { - this.callbacks?.onDestroy?.(); - Object.values(this.map) - .forEach((controller) => { - controller.onDestroy?.(); - }); - } -} - -export class ControllerGroup implements Controller { - controllers: Controller[] = []; - - public addController(...controllers: Controller[]): void { - this.controllers.push(...controllers); - } - - async start?(): Promise { - await Promise.all( - this.controllers.map(async (controller) => { - await controller.start?.(); - }), - ); - } - - update(timingInfo: PhysicsTimingInfo): void { - this.controllers.forEach((controller) => { - controller.update?.(timingInfo); - }); - } - - fixedUpdate(timingInfo: PhysicsTimingInfo): void { - this.controllers.forEach((controller) => { - controller.fixedUpdate?.(timingInfo); - }); - } - - onDestroy(): void { - this.controllers.forEach((controller) => { - controller.onDestroy?.(); - }); - this.controllers = []; - } -} +import type { PhysicsTimingInfo } from '../Physics'; + +export interface Controller { + name?: string; + start?(): Promise | void; + update?(timingInfo: PhysicsTimingInfo): void; + fixedUpdate?(timingInfo: PhysicsTimingInfo): void; + onDestroy?(): void; +} + +export class ControllerMap> +implements Controller { + map: M; + callbacks?: Partial; + + constructor(map: M, callbacks?: Partial) { + this.map = map; + this.callbacks = callbacks; + } + + get(name: K): M[K] { + return this.map[name]; + } + + async start(): Promise { + await this.callbacks?.start?.(); + await Promise.all( + Object.values(this.map) + .map(async (controller) => { + await controller.start?.(); + }), + ); + } + + update(timingInfo: PhysicsTimingInfo): void { + this.callbacks?.update?.(timingInfo); + Object.values(this.map) + .forEach((controller) => { + controller.update?.(timingInfo); + }); + } + + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + this.callbacks?.fixedUpdate?.(timingInfo); + Object.values(this.map) + .forEach((controller) => { + controller.fixedUpdate?.(timingInfo); + }); + } + + onDestroy(): void { + this.callbacks?.onDestroy?.(); + Object.values(this.map) + .forEach((controller) => { + controller.onDestroy?.(); + }); + } +} + +export class ControllerGroup implements Controller { + controllers: Controller[] = []; + + public addController(...controllers: Controller[]): void { + this.controllers.push(...controllers); + } + + async start?(): Promise { + await Promise.all( + this.controllers.map(async (controller) => { + await controller.start?.(); + }), + ); + } + + update(timingInfo: PhysicsTimingInfo): void { + this.controllers.forEach((controller) => { + controller.update?.(timingInfo); + }); + } + + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + this.controllers.forEach((controller) => { + controller.fixedUpdate?.(timingInfo); + }); + } + + onDestroy(): void { + this.controllers.forEach((controller) => { + controller.onDestroy?.(); + }); + this.controllers = []; + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Events.ts b/src/bundles/robot_simulation/src/engine/Core/Events.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Core/Events.ts rename to src/bundles/robot_simulation/src/engine/Core/Events.ts index 58402cdbd1..8cf4340249 100644 --- a/src/bundles/robot_simulation/engine/Core/Events.ts +++ b/src/bundles/robot_simulation/src/engine/Core/Events.ts @@ -1,41 +1,41 @@ -export type Listener = ( - event: EventMap[EventName] -) => Promise | void; - -type ValueIsEvent = { - [EventName in keyof EventMap]: Event; -}; - -export class TypedEventTarget> { - private listeners: { - [EventName in keyof EventMap]?: Array>; - }; - - constructor() { - this.listeners = {}; - } - - addEventListener( - type: EventName, - listener: Listener, - ): void { - if (!this.listeners[type]) { - this.listeners[type] = []; - } - // Non-null assertion is safe because we just checked if it's undefined - this.listeners[type]!.push(listener); - } - - public dispatchEvent( - _type: EventName, - event: EventMap[EventName], - ): boolean { - const listeners = this.listeners[_type]; - if (listeners) { - listeners.forEach((listener) => { - listener(event); - }); - } - return true; - } -} +export type Listener = ( + event: EventMap[EventName] +) => Promise | void; + +type ValueIsEvent = { + [EventName in keyof EventMap]: Event; +}; + +export class TypedEventTarget> { + private listeners: { + [EventName in keyof EventMap]?: Array>; + }; + + constructor() { + this.listeners = {}; + } + + addEventListener( + type: EventName, + listener: Listener, + ): void { + if (!this.listeners[type]) { + this.listeners[type] = []; + } + // Non-null assertion is safe because we just checked if it's undefined + this.listeners[type]!.push(listener); + } + + public dispatchEvent( + _type: EventName, + event: EventMap[EventName], + ): boolean { + const listeners = this.listeners[_type]; + if (listeners) { + listeners.forEach((listener) => { + listener(event); + }); + } + return true; + } +} diff --git a/src/bundles/robot_simulation/engine/Core/RobotConsole.ts b/src/bundles/robot_simulation/src/engine/Core/RobotConsole.ts similarity index 93% rename from src/bundles/robot_simulation/engine/Core/RobotConsole.ts rename to src/bundles/robot_simulation/src/engine/Core/RobotConsole.ts index f3658b15af..950e601264 100644 --- a/src/bundles/robot_simulation/engine/Core/RobotConsole.ts +++ b/src/bundles/robot_simulation/src/engine/Core/RobotConsole.ts @@ -1,27 +1,27 @@ -type LogLevel = 'error' | 'source'; - -export type LogEntry = { - message: string; - level: LogLevel; - timestamp: number; -}; - -export class RobotConsole { - logs: LogEntry[]; - - constructor() { - this.logs = []; - } - - log(message: string, level) { - this.logs.push({ - message, - level, - timestamp: Date.now(), - }); - } - - getLogs() { - return this.logs; - } -} +type LogLevel = 'error' | 'source'; + +export type LogEntry = { + message: string; + level: LogLevel; + timestamp: number; +}; + +export class RobotConsole { + logs: LogEntry[]; + + constructor() { + this.logs = []; + } + + log(message: string, level) { + this.logs.push({ + message, + level, + timestamp: Date.now(), + }); + } + + getLogs() { + return this.logs; + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Timer.ts b/src/bundles/robot_simulation/src/engine/Core/Timer.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Core/Timer.ts rename to src/bundles/robot_simulation/src/engine/Core/Timer.ts index 4f899984d1..c8c99eb2a7 100644 --- a/src/bundles/robot_simulation/engine/Core/Timer.ts +++ b/src/bundles/robot_simulation/src/engine/Core/Timer.ts @@ -1,63 +1,63 @@ -export type FrameTimingInfo = { - elapsedTimeReal: number; - elapsedTimeSimulated: number; - frameDuration: number; - framesPerSecond: number; -}; - -export class Timer { - private _isRunning = false; - private _elapsedSimulatedTime = 0; - private _timeSpentPaused = 0; - private _frameDuration = 0; - - private _startTime: number | null = null; - private _pausedAt: number | null = null; - private _currentTime: number | null = null; - - /** - * Pauses the timer and marks the pause time. - */ - pause(): void { - if (this._currentTime === null) { - return; - } - - if (this._isRunning) { - this._isRunning = false; - this._pausedAt = this._currentTime; - } - } - - /** - * Steps the timer forward, calculates frame timing info. - * @param timestamp - The current timestamp. - * @returns The frame timing information. - */ - step(timestamp: number): FrameTimingInfo { - if (this._startTime === null) { - this._startTime = timestamp; - } - - if (!this._isRunning) { - this._isRunning = true; - } - - if (this._pausedAt !== null) { - this._timeSpentPaused += timestamp - this._pausedAt; - this._pausedAt = null; - } - - this._frameDuration = this._currentTime ? timestamp - this._currentTime : 0; - this._currentTime = timestamp; - - this._elapsedSimulatedTime = timestamp - this._startTime - this._timeSpentPaused; - - return { - elapsedTimeReal: this._currentTime - this._startTime, - elapsedTimeSimulated: this._elapsedSimulatedTime, - frameDuration: this._frameDuration, - framesPerSecond: 1000 / this._frameDuration, - }; - } -} +export type FrameTimingInfo = { + elapsedTimeReal: number; + elapsedTimeSimulated: number; + frameDuration: number; + framesPerSecond: number; +}; + +export class Timer { + private _isRunning = false; + private _elapsedSimulatedTime = 0; + private _timeSpentPaused = 0; + private _frameDuration = 0; + + private _startTime: number | null = null; + private _pausedAt: number | null = null; + private _currentTime: number | null = null; + + /** + * Pauses the timer and marks the pause time. + */ + pause(): void { + if (this._currentTime === null) { + return; + } + + if (this._isRunning) { + this._isRunning = false; + this._pausedAt = this._currentTime; + } + } + + /** + * Steps the timer forward, calculates frame timing info. + * @param timestamp - The current timestamp. + * @returns The frame timing information. + */ + step(timestamp: number): FrameTimingInfo { + if (this._startTime === null) { + this._startTime = timestamp; + } + + if (!this._isRunning) { + this._isRunning = true; + } + + if (this._pausedAt !== null) { + this._timeSpentPaused += timestamp - this._pausedAt; + this._pausedAt = null; + } + + this._frameDuration = this._currentTime ? timestamp - this._currentTime : 0; + this._currentTime = timestamp; + + this._elapsedSimulatedTime = timestamp - this._startTime - this._timeSpentPaused; + + return { + elapsedTimeReal: this._currentTime - this._startTime, + elapsedTimeSimulated: this._elapsedSimulatedTime, + frameDuration: this._frameDuration, + framesPerSecond: 1000 / this._frameDuration, + }; + } +} diff --git a/src/bundles/robot_simulation/engine/Entity/Entity.ts b/src/bundles/robot_simulation/src/engine/Entity/Entity.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Entity/Entity.ts rename to src/bundles/robot_simulation/src/engine/Entity/Entity.ts index bb3bd40ff3..69f7dc0437 100644 --- a/src/bundles/robot_simulation/engine/Entity/Entity.ts +++ b/src/bundles/robot_simulation/src/engine/Entity/Entity.ts @@ -1,125 +1,125 @@ -import type Rapier from '@dimforge/rapier3d-compat'; -import type * as THREE from 'three'; -import { vec3 } from '../Math/Convert'; -import { - simpleVectorLength, - type Orientation, - type SimpleQuaternion, - type SimpleVector, -} from '../Math/Vector'; - -type EntityConfig = { - rapierRigidBody: Rapier.RigidBody; - rapierCollider: Rapier.Collider; -}; - -export class Entity { - #rapierRigidBody: Rapier.RigidBody; - #rapierCollider: Rapier.Collider; - - constructor(configuration: EntityConfig) { - this.#rapierRigidBody = configuration.rapierRigidBody; - this.#rapierCollider = configuration.rapierCollider; - } - - getCollider(): Rapier.Collider { - return this.#rapierCollider; - } - - getRigidBody(): Rapier.RigidBody { - return this.#rapierRigidBody; - } - - getTranslation(): SimpleVector { - return this.#rapierRigidBody.translation(); - } - - getRotation(): SimpleQuaternion { - return this.#rapierRigidBody.rotation(); - } - - setOrientation(orientation: Orientation): void { - this.#rapierRigidBody.setTranslation(orientation.position, true); - this.#rapierRigidBody.setRotation(orientation.rotation, true); - } - - setMass(mass: number): void { - this.#rapierCollider.setMass(mass); - } - - getMass(): number { - return this.#rapierCollider.mass(); - } - getVelocity(): SimpleVector { - return this.#rapierRigidBody.linvel(); - } - - getAngularVelocity(): SimpleVector { - return this.#rapierRigidBody.angvel(); - } - - applyImpulse(impulse: SimpleVector, point: SimpleVector): void { - return this.#rapierRigidBody.applyImpulseAtPoint(impulse, point, true); - } - - worldTranslation( - localTranslation: THREE.Vector3, - ): THREE.Vector3 { - const rotation = this.getRotation(); - const translation = this.getTranslation(); - - return localTranslation.applyQuaternion(rotation) - .add(translation); - } - - transformDirection(localDirection: THREE.Vector3): THREE.Vector3 { - const rotation = this.getRotation(); - return localDirection.clone() - .applyQuaternion(rotation); - } - - distanceVectorOfPointToRotationalAxis( - localPoint: THREE.Vector3, - ): SimpleVector { - return localPoint - .clone() - .projectOnVector(vec3(this.getAngularVelocity())) - .negate() - .add(localPoint); - } - - /** - * Calculates the tangential velocity of a point in a rotating system. - * @param {THREE.Vector3} localPoint - The point for which to calculate the tangential velocity. - * @returns {THREE.Vector3} The tangential velocity vector of the point. - */ - tangentialVelocityOfPoint(localPoint): THREE.Vector3 { - // Calculate the distance vector from the point to the rotational axis - const distanceVector - = this.distanceVectorOfPointToRotationalAxis(localPoint); - - // Retrieve the angular velocity of the system - const angularVelocity = this.getAngularVelocity(); - - // Calculate the magnitude of the tangential velocity - const velocityMagnitude - = simpleVectorLength(distanceVector) * simpleVectorLength(angularVelocity); - - // Calculate the tangential velocity vector - const tangentialVelocity = this.transformDirection(localPoint) - .cross(angularVelocity) - .negate() - .normalize() - .multiplyScalar(velocityMagnitude); - - // Return the tangential velocity vector - return tangentialVelocity; - } - - worldVelocity( - localPoint: THREE.Vector3, - ): THREE.Vector3 { - return this.tangentialVelocityOfPoint(localPoint) - .add(this.getVelocity()); - } -} +import type Rapier from '@dimforge/rapier3d-compat'; +import type * as THREE from 'three'; +import { vec3 } from '../Math/Convert'; +import { + simpleVectorLength, + type Orientation, + type SimpleQuaternion, + type SimpleVector, +} from '../Math/Vector'; + +type EntityConfig = { + rapierRigidBody: Rapier.RigidBody; + rapierCollider: Rapier.Collider; +}; + +export class Entity { + #rapierRigidBody: Rapier.RigidBody; + #rapierCollider: Rapier.Collider; + + constructor(configuration: EntityConfig) { + this.#rapierRigidBody = configuration.rapierRigidBody; + this.#rapierCollider = configuration.rapierCollider; + } + + getCollider(): Rapier.Collider { + return this.#rapierCollider; + } + + getRigidBody(): Rapier.RigidBody { + return this.#rapierRigidBody; + } + + getTranslation(): SimpleVector { + return this.#rapierRigidBody.translation(); + } + + getRotation(): SimpleQuaternion { + return this.#rapierRigidBody.rotation(); + } + + setOrientation(orientation: Orientation): void { + this.#rapierRigidBody.setTranslation(orientation.position, true); + this.#rapierRigidBody.setRotation(orientation.rotation, true); + } + + setMass(mass: number): void { + this.#rapierCollider.setMass(mass); + } + + getMass(): number { + return this.#rapierCollider.mass(); + } + getVelocity(): SimpleVector { + return this.#rapierRigidBody.linvel(); + } + + getAngularVelocity(): SimpleVector { + return this.#rapierRigidBody.angvel(); + } + + applyImpulse(impulse: SimpleVector, point: SimpleVector): void { + return this.#rapierRigidBody.applyImpulseAtPoint(impulse, point, true); + } + + worldTranslation( + localTranslation: THREE.Vector3, + ): THREE.Vector3 { + const rotation = this.getRotation(); + const translation = this.getTranslation(); + + return localTranslation.applyQuaternion(rotation) + .add(translation); + } + + transformDirection(localDirection: THREE.Vector3): THREE.Vector3 { + const rotation = this.getRotation(); + return localDirection.clone() + .applyQuaternion(rotation); + } + + distanceVectorOfPointToRotationalAxis( + localPoint: THREE.Vector3, + ): SimpleVector { + return localPoint + .clone() + .projectOnVector(vec3(this.getAngularVelocity())) + .negate() + .add(localPoint); + } + + /** + * Calculates the tangential velocity of a point in a rotating system. + * @param {THREE.Vector3} localPoint - The point for which to calculate the tangential velocity. + * @returns {THREE.Vector3} The tangential velocity vector of the point. + */ + tangentialVelocityOfPoint(localPoint): THREE.Vector3 { + // Calculate the distance vector from the point to the rotational axis + const distanceVector + = this.distanceVectorOfPointToRotationalAxis(localPoint); + + // Retrieve the angular velocity of the system + const angularVelocity = this.getAngularVelocity(); + + // Calculate the magnitude of the tangential velocity + const velocityMagnitude + = simpleVectorLength(distanceVector) * simpleVectorLength(angularVelocity); + + // Calculate the tangential velocity vector + const tangentialVelocity = this.transformDirection(localPoint) + .cross(angularVelocity) + .negate() + .normalize() + .multiplyScalar(velocityMagnitude); + + // Return the tangential velocity vector + return tangentialVelocity; + } + + worldVelocity( + localPoint: THREE.Vector3, + ): THREE.Vector3 { + return this.tangentialVelocityOfPoint(localPoint) + .add(this.getVelocity()); + } +} diff --git a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts b/src/bundles/robot_simulation/src/engine/Entity/EntityFactory.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Entity/EntityFactory.ts rename to src/bundles/robot_simulation/src/engine/Entity/EntityFactory.ts index f75d7d57d3..b3d84e8f80 100644 --- a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts +++ b/src/bundles/robot_simulation/src/engine/Entity/EntityFactory.ts @@ -1,46 +1,46 @@ -import type { Dimension, Orientation } from '../Math/Vector'; -import type { Physics } from '../Physics'; -import { Entity } from './Entity'; - -export const rigidBodyTypes = ['fixed', 'dynamic'] as const; - -export type RigidBodyType = typeof rigidBodyTypes[number]; - -export type EntityCuboidOptions = { - orientation: Orientation; - dimension: Dimension; - mass: number; - type: RigidBodyType; -}; - -export function isRigidBodyType(bodyType: string): bodyType is RigidBodyType { - return rigidBodyTypes.includes(bodyType as RigidBodyType); -} - -export function addCuboid( - physics: Physics, - options: EntityCuboidOptions, -): Entity { - const { orientation, dimension: { width, height, length }, type, mass } = options; - - const rigidBodyDesc = physics.RAPIER.RigidBodyDesc[type](); - const colliderDesc = physics.RAPIER.ColliderDesc.cuboid( - width / 2, - height / 2, - length / 2, - ); - - colliderDesc.mass = mass; - - const rigidBody = physics.createRigidBody(rigidBodyDesc); - const collider = physics.createCollider(colliderDesc, rigidBody); - - const entity = new Entity({ - rapierRigidBody: rigidBody, - rapierCollider: collider, - }); - - entity.setOrientation(orientation); - - return entity; -} +import type { Dimension, Orientation } from '../Math/Vector'; +import type { Physics } from '../Physics'; +import { Entity } from './Entity'; + +export const rigidBodyTypes = ['fixed', 'dynamic'] as const; + +export type RigidBodyType = typeof rigidBodyTypes[number]; + +export type EntityCuboidOptions = { + orientation: Orientation; + dimension: Dimension; + mass: number; + type: RigidBodyType; +}; + +export function isRigidBodyType(bodyType: string): bodyType is RigidBodyType { + return rigidBodyTypes.includes(bodyType as RigidBodyType); +} + +export function addCuboid( + physics: Physics, + options: EntityCuboidOptions, +): Entity { + const { orientation, dimension: { width, height, length }, type, mass } = options; + + const rigidBodyDesc = physics.RAPIER.RigidBodyDesc[type](); + const colliderDesc = physics.RAPIER.ColliderDesc.cuboid( + width / 2, + height / 2, + length / 2, + ); + + colliderDesc.mass = mass; + + const rigidBody = physics.createRigidBody(rigidBodyDesc); + const collider = physics.createCollider(colliderDesc, rigidBody); + + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + entity.setOrientation(orientation); + + return entity; +} diff --git a/src/bundles/robot_simulation/engine/Math/Convert.ts b/src/bundles/robot_simulation/src/engine/Math/Convert.ts similarity index 98% rename from src/bundles/robot_simulation/engine/Math/Convert.ts rename to src/bundles/robot_simulation/src/engine/Math/Convert.ts index f4155a5d71..84ca825a5e 100644 --- a/src/bundles/robot_simulation/engine/Math/Convert.ts +++ b/src/bundles/robot_simulation/src/engine/Math/Convert.ts @@ -1,6 +1,6 @@ -import { Euler, Quaternion, Vector3 } from 'three'; -import type { SimpleQuaternion, SimpleVector } from './Vector'; - -export const quat = ({ x, y, z, w }: SimpleQuaternion) => new Quaternion(x, y, z, w); -export const vec3 = ({ x, y, z }: SimpleVector) => new Vector3(x, y, z); -export const euler = ({ x, y, z }:SimpleVector) => new Euler(x, y, z); +import { Euler, Quaternion, Vector3 } from 'three'; +import type { SimpleQuaternion, SimpleVector } from './Vector'; + +export const quat = ({ x, y, z, w }: SimpleQuaternion) => new Quaternion(x, y, z, w); +export const vec3 = ({ x, y, z }: SimpleVector) => new Vector3(x, y, z); +export const euler = ({ x, y, z }:SimpleVector) => new Euler(x, y, z); diff --git a/src/bundles/robot_simulation/engine/Math/Vector.ts b/src/bundles/robot_simulation/src/engine/Math/Vector.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Math/Vector.ts rename to src/bundles/robot_simulation/src/engine/Math/Vector.ts index 9504405974..464ac84b95 100644 --- a/src/bundles/robot_simulation/engine/Math/Vector.ts +++ b/src/bundles/robot_simulation/src/engine/Math/Vector.ts @@ -1,24 +1,24 @@ -export type SimpleVector = { x: number; y: number; z: number }; -export type SimpleQuaternion = { x: number; y: number; z: number; w: number }; - -export const simpleVectorLength = (vector: SimpleVector) => Math.sqrt( - vector.x * vector.x + vector.y * vector.y + vector.z * vector.z, -); - -export type Orientation = { - position: SimpleVector; - rotation: SimpleQuaternion; -}; - -export type Dimension = { - height: number; - width: number; - length: number; -}; - -// Vector3 and Quaternion already extends SimpleVector and SimpleQuaternion -// so we can just add the interface to the existing declaration -declare module 'three' { - interface Vector3 extends SimpleVector {} - interface Quaternion extends SimpleQuaternion {} -} +export type SimpleVector = { x: number; y: number; z: number }; +export type SimpleQuaternion = { x: number; y: number; z: number; w: number }; + +export const simpleVectorLength = (vector: SimpleVector) => Math.sqrt( + vector.x * vector.x + vector.y * vector.y + vector.z * vector.z, +); + +export type Orientation = { + position: SimpleVector; + rotation: SimpleQuaternion; +}; + +export type Dimension = { + height: number; + width: number; + length: number; +}; + +// Vector3 and Quaternion already extends SimpleVector and SimpleQuaternion +// so we can just add the interface to the existing declaration +declare module 'three' { + interface Vector3 extends SimpleVector {} + interface Quaternion extends SimpleQuaternion {} +} diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/src/engine/Physics.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Physics.ts rename to src/bundles/robot_simulation/src/engine/Physics.ts index 52f368f16d..748621750e 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/src/engine/Physics.ts @@ -1,169 +1,169 @@ -import rapier from '@dimforge/rapier3d-compat'; - -import type * as THREE from 'three'; - -import { TypedEventTarget } from './Core/Events'; -import type { FrameTimingInfo } from './Core/Timer'; -import type { SimpleVector } from './Math/Vector'; - -export type PhysicsTimingInfo = FrameTimingInfo & { - stepCount: number; - timestep: number; - residualFactor: number; -}; - -export class TimeStampedEvent extends Event { - frameTimingInfo: PhysicsTimingInfo; - - constructor(type: string, frameTimingInfo: PhysicsTimingInfo) { - super(type); - this.frameTimingInfo = frameTimingInfo; - } -} - -type PhysicsEventMap = { - beforePhysicsUpdate: TimeStampedEvent; - afterPhysicsUpdate: TimeStampedEvent; -}; - -export type PhysicsConfig = { - gravity: SimpleVector; - timestep: number; -}; - -type NotInitializedInternals = { - initialized: false; -}; - -type InitializedInternals = { - initialized: true; - world: rapier.World; - accumulator: number; - stepCount: number; -}; - -type PhysicsInternals = InitializedInternals | NotInitializedInternals; - -export class Physics extends TypedEventTarget { - RAPIER: typeof rapier; - configuration: PhysicsConfig; - internals: PhysicsInternals; - - constructor(configuration: PhysicsConfig) { - super(); - this.configuration = configuration; - this.RAPIER = rapier; - - this.internals = { initialized: false }; - } - - async start() { - await rapier.init(); - - this.RAPIER = rapier; - - const world = new rapier.World(this.configuration.gravity); - world.timestep = this.configuration.timestep; - - this.internals = { - initialized: true, - world, - accumulator: world.timestep, - stepCount: 0, - }; - } - - createRigidBody(rigidBodyDesc: rapier.RigidBodyDesc): rapier.RigidBody { - if (this.internals.initialized === false) { - throw Error("Physics engine hasn't been initialized yet"); - } - - return this.internals.world.createRigidBody(rigidBodyDesc); - } - - createCollider( - colliderDesc: rapier.ColliderDesc, - rigidBody: rapier.RigidBody, - ): rapier.Collider { - if (this.internals.initialized === false) { - throw Error("Physics engine hasn't been initialized yet"); - } - return this.internals.world.createCollider(colliderDesc, rigidBody); - } - - castRay( - globalPosition: THREE.Vector3, - globalDirection: THREE.Vector3, - maxDistance: number, - excludeCollider?: rapier.Collider, - ): { - distance: number; - normal: SimpleVector; - } | null { - if (this.internals.initialized === false) { - throw Error("Physics engine hasn't been initialized yet"); - } - - const ray = new this.RAPIER.Ray(globalPosition, globalDirection); - - // https://rapier.rs/javascript3d/classes/World.html#castRayAndGetNormal - const result = this.internals.world.castRayAndGetNormal( - ray, - maxDistance, - true, - undefined, - undefined, - excludeCollider, - ); - - this.internals.world.castRay(ray, maxDistance, false); - - if (result === null) { - return null; - } - - return { - distance: result.toi, - normal: result.normal, - }; - } - - step(timing: FrameTimingInfo): PhysicsTimingInfo { - if (this.internals.initialized === false) { - throw Error("Physics engine hasn't been initialized yet"); - } - - const maxFrameTime = 0.05; - const frameDuration = timing.frameDuration / 1000; - this.internals.accumulator += Math.min(frameDuration, maxFrameTime); - - const currentPhysicsTimingInfo = { - ...timing, - stepCount: this.internals.stepCount, - timestep: this.configuration.timestep * 1000, - residualFactor: this.internals.accumulator / this.configuration.timestep, - }; - - while (this.internals.accumulator >= this.configuration.timestep) { - this.dispatchEvent( - 'beforePhysicsUpdate', - new TimeStampedEvent('beforePhysicsUpdate', currentPhysicsTimingInfo), - ); - - this.internals.world.step(); - - this.internals.stepCount += 1; - currentPhysicsTimingInfo.stepCount = this.internals.stepCount; - - this.dispatchEvent( - 'afterPhysicsUpdate', - new TimeStampedEvent('afterPhysicsUpdate', currentPhysicsTimingInfo), - ); - - this.internals.accumulator -= this.configuration.timestep; - currentPhysicsTimingInfo.residualFactor = this.internals.accumulator / this.configuration.timestep; - } - - return currentPhysicsTimingInfo; - } -} +import rapier from '@dimforge/rapier3d-compat'; + +import type * as THREE from 'three'; + +import { TypedEventTarget } from './Core/Events'; +import type { FrameTimingInfo } from './Core/Timer'; +import type { SimpleVector } from './Math/Vector'; + +export type PhysicsTimingInfo = FrameTimingInfo & { + stepCount: number; + timestep: number; + residualFactor: number; +}; + +export class TimeStampedEvent extends Event { + frameTimingInfo: PhysicsTimingInfo; + + constructor(type: string, frameTimingInfo: PhysicsTimingInfo) { + super(type); + this.frameTimingInfo = frameTimingInfo; + } +} + +type PhysicsEventMap = { + beforePhysicsUpdate: TimeStampedEvent; + afterPhysicsUpdate: TimeStampedEvent; +}; + +export type PhysicsConfig = { + gravity: SimpleVector; + timestep: number; +}; + +type NotInitializedInternals = { + initialized: false; +}; + +type InitializedInternals = { + initialized: true; + world: rapier.World; + accumulator: number; + stepCount: number; +}; + +type PhysicsInternals = InitializedInternals | NotInitializedInternals; + +export class Physics extends TypedEventTarget { + RAPIER: typeof rapier; + configuration: PhysicsConfig; + internals: PhysicsInternals; + + constructor(configuration: PhysicsConfig) { + super(); + this.configuration = configuration; + this.RAPIER = rapier; + + this.internals = { initialized: false }; + } + + async start() { + await rapier.init(); + + this.RAPIER = rapier; + + const world = new rapier.World(this.configuration.gravity); + world.timestep = this.configuration.timestep; + + this.internals = { + initialized: true, + world, + accumulator: world.timestep, + stepCount: 0, + }; + } + + createRigidBody(rigidBodyDesc: rapier.RigidBodyDesc): rapier.RigidBody { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + + return this.internals.world.createRigidBody(rigidBodyDesc); + } + + createCollider( + colliderDesc: rapier.ColliderDesc, + rigidBody: rapier.RigidBody, + ): rapier.Collider { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + return this.internals.world.createCollider(colliderDesc, rigidBody); + } + + castRay( + globalPosition: THREE.Vector3, + globalDirection: THREE.Vector3, + maxDistance: number, + excludeCollider?: rapier.Collider, + ): { + distance: number; + normal: SimpleVector; + } | null { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + + const ray = new this.RAPIER.Ray(globalPosition, globalDirection); + + // https://rapier.rs/javascript3d/classes/World.html#castRayAndGetNormal + const result = this.internals.world.castRayAndGetNormal( + ray, + maxDistance, + true, + undefined, + undefined, + excludeCollider, + ); + + this.internals.world.castRay(ray, maxDistance, false); + + if (result === null) { + return null; + } + + return { + distance: result.toi, + normal: result.normal, + }; + } + + step(timing: FrameTimingInfo): PhysicsTimingInfo { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + + const maxFrameTime = 0.05; + const frameDuration = timing.frameDuration / 1000; + this.internals.accumulator += Math.min(frameDuration, maxFrameTime); + + const currentPhysicsTimingInfo = { + ...timing, + stepCount: this.internals.stepCount, + timestep: this.configuration.timestep * 1000, + residualFactor: this.internals.accumulator / this.configuration.timestep, + }; + + while (this.internals.accumulator >= this.configuration.timestep) { + this.dispatchEvent( + 'beforePhysicsUpdate', + new TimeStampedEvent('beforePhysicsUpdate', currentPhysicsTimingInfo), + ); + + this.internals.world.step(); + + this.internals.stepCount += 1; + currentPhysicsTimingInfo.stepCount = this.internals.stepCount; + + this.dispatchEvent( + 'afterPhysicsUpdate', + new TimeStampedEvent('afterPhysicsUpdate', currentPhysicsTimingInfo), + ); + + this.internals.accumulator -= this.configuration.timestep; + currentPhysicsTimingInfo.residualFactor = this.internals.accumulator / this.configuration.timestep; + } + + return currentPhysicsTimingInfo; + } +} diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/src/engine/Render/Renderer.ts similarity index 96% rename from src/bundles/robot_simulation/engine/Render/Renderer.ts rename to src/bundles/robot_simulation/src/engine/Render/Renderer.ts index b26609b13b..e983a72a53 100644 --- a/src/bundles/robot_simulation/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/src/engine/Render/Renderer.ts @@ -1,77 +1,77 @@ -import * as THREE from 'three'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { - type GLTF, - GLTFLoader, -} from 'three/examples/jsm/loaders/GLTFLoader.js'; -import type { FrameTimingInfo } from '../Core/Timer'; - -type ControlType = 'none' | 'orbit'; - -export type RenderConfig = { - width: number; - height: number; - control: ControlType -}; - -export class Renderer { - element?: HTMLElement; - - #scene: THREE.Scene; - #camera: THREE.Camera; - #renderer: THREE.WebGLRenderer; - #controls: OrbitControls; - - constructor( - scene: THREE.Scene, - camera: THREE.Camera, - configuration: RenderConfig, - ) { - this.#camera = camera; - this.#scene = scene; - this.#renderer = new THREE.WebGLRenderer({ antialias: true }); - this.#renderer.shadowMap.enabled = true; - - this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); - - this.#renderer.setSize(configuration.width, configuration.height); - this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); - - const light = new THREE.PointLight(0xffffff, 1); - const ambient = new THREE.AmbientLight(0xffffff, 0.20); - light.position.set(0, 1, 0); - this.#scene.add(light); - this.#scene.add(ambient); - this.#scene.background = new THREE.Color(0xffffff); - } - - static loadGTLF(url: string): Promise { - const loader = new GLTFLoader(); - return new Promise((resolve, reject) => { - loader.load(url, resolve, () => {}, reject); - }); - } - - scene(): THREE.Scene { - return this.#scene; - } - - render() { - return this.#renderer.render(this.#scene, this.#camera); - } - - getElement(): HTMLCanvasElement { - return this.#renderer.domElement; - } - - add( - ...input: Parameters - ): ReturnType { - return this.#scene.add(...input); - } - - step(_: FrameTimingInfo) { - this.render(); - this.#controls.update(); - } -} +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import { + type GLTF, + GLTFLoader, +} from 'three/examples/jsm/loaders/GLTFLoader.js'; +import type { FrameTimingInfo } from '../Core/Timer'; + +type ControlType = 'none' | 'orbit'; + +export type RenderConfig = { + width: number; + height: number; + control: ControlType +}; + +export class Renderer { + element?: HTMLElement; + + #scene: THREE.Scene; + #camera: THREE.Camera; + #renderer: THREE.WebGLRenderer; + #controls: OrbitControls; + + constructor( + scene: THREE.Scene, + camera: THREE.Camera, + configuration: RenderConfig, + ) { + this.#camera = camera; + this.#scene = scene; + this.#renderer = new THREE.WebGLRenderer({ antialias: true }); + this.#renderer.shadowMap.enabled = true; + + this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); + + this.#renderer.setSize(configuration.width, configuration.height); + this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); + + const light = new THREE.PointLight(0xffffff, 1); + const ambient = new THREE.AmbientLight(0xffffff, 0.20); + light.position.set(0, 1, 0); + this.#scene.add(light); + this.#scene.add(ambient); + this.#scene.background = new THREE.Color(0xffffff); + } + + static loadGTLF(url: string): Promise { + const loader = new GLTFLoader(); + return new Promise((resolve, reject) => { + loader.load(url, resolve, () => {}, reject); + }); + } + + scene(): THREE.Scene { + return this.#scene; + } + + render() { + return this.#renderer.render(this.#scene, this.#camera); + } + + getElement(): HTMLCanvasElement { + return this.#renderer.domElement; + } + + add( + ...input: Parameters + ): ReturnType { + return this.#scene.add(...input); + } + + step(_: FrameTimingInfo) { + this.render(); + this.#controls.update(); + } +} diff --git a/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts b/src/bundles/robot_simulation/src/engine/Render/debug/DebugArrow.ts similarity index 100% rename from src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts rename to src/bundles/robot_simulation/src/engine/Render/debug/DebugArrow.ts diff --git a/src/bundles/robot_simulation/engine/Render/helpers/Camera.ts b/src/bundles/robot_simulation/src/engine/Render/helpers/Camera.ts similarity index 100% rename from src/bundles/robot_simulation/engine/Render/helpers/Camera.ts rename to src/bundles/robot_simulation/src/engine/Render/helpers/Camera.ts diff --git a/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts b/src/bundles/robot_simulation/src/engine/Render/helpers/GLTF.ts similarity index 100% rename from src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts rename to src/bundles/robot_simulation/src/engine/Render/helpers/GLTF.ts diff --git a/src/bundles/robot_simulation/engine/Render/helpers/MeshFactory.ts b/src/bundles/robot_simulation/src/engine/Render/helpers/MeshFactory.ts similarity index 100% rename from src/bundles/robot_simulation/engine/Render/helpers/MeshFactory.ts rename to src/bundles/robot_simulation/src/engine/Render/helpers/MeshFactory.ts diff --git a/src/bundles/robot_simulation/engine/Render/helpers/Scene.ts b/src/bundles/robot_simulation/src/engine/Render/helpers/Scene.ts similarity index 100% rename from src/bundles/robot_simulation/engine/Render/helpers/Scene.ts rename to src/bundles/robot_simulation/src/engine/Render/helpers/Scene.ts diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/src/engine/World.ts similarity index 96% rename from src/bundles/robot_simulation/engine/World.ts rename to src/bundles/robot_simulation/src/engine/World.ts index 6a0b1bba9c..321b876f02 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/src/engine/World.ts @@ -1,129 +1,129 @@ -import { ProgramError } from '../controllers/program/error'; -import { type Controller, ControllerGroup } from './Core/Controller'; -import { TypedEventTarget } from './Core/Events'; -import type { RobotConsole } from './Core/RobotConsole'; -import type { Timer } from './Core/Timer'; -import { TimeStampedEvent, type Physics } from './Physics'; -import type { Renderer } from './Render/Renderer'; - -export const worldStates = [ - 'unintialized', - 'loading', - 'ready', - 'running', - 'error', -] as const; -export type WorldState = (typeof worldStates)[number]; - -type WorldEventMap = { - worldStart: Event; - worldStateChange: Event; - beforeRender: TimeStampedEvent; - afterRender: TimeStampedEvent; -}; - -export class World extends TypedEventTarget { - state: WorldState; - physics: Physics; - render: Renderer; - timer: Timer; - robotConsole: RobotConsole; - controllers: ControllerGroup; - - constructor( - physics: Physics, - render: Renderer, - timer: Timer, - robotConsole: RobotConsole - ) { - super(); - this.state = 'unintialized'; - this.physics = physics; - this.render = render; - this.timer = timer; - this.controllers = new ControllerGroup(); - this.robotConsole = robotConsole; - } - - addController(...controllers: Controller[]) { - this.controllers.addController(...controllers); - - this.addEventListener('worldStart', () => { - controllers.forEach((controller) => { - controller.start?.(); - }); - }); - - this.addEventListener('beforeRender', (e) => { - controllers.forEach((controller) => { - controller.update?.(e.frameTimingInfo); - }); - }); - - this.physics.addEventListener('beforePhysicsUpdate', (e) => { - controllers.forEach((controller) => { - controller.fixedUpdate?.(e.frameTimingInfo); - }); - }); - } - - async init() { - this.setState('loading'); - await this.physics.start(); - this.dispatchEvent('worldStart', new Event('worldStart')); - this.setState('ready'); - } - - private setState(newState: WorldState) { - if (this.state !== newState) { - this.dispatchEvent('worldStateChange', new Event('worldStateChange')); - this.state = newState; - } - } - - pause() { - this.setState('ready'); - this.timer.pause(); - } - - start() { - if (this.state === 'ready') { - this.setState('running'); - window.requestAnimationFrame(this.step.bind(this)); - } - } - - step(timestamp: number) { - try { - const frameTimingInfo = this.timer.step(timestamp); - - const physicsTimingInfo = this.physics.step(frameTimingInfo); - - // Update render - this.dispatchEvent( - 'beforeRender', - new TimeStampedEvent('beforeRender', physicsTimingInfo) - ); - this.render.step(frameTimingInfo); - this.dispatchEvent( - 'afterRender', - new TimeStampedEvent('afterRender', physicsTimingInfo) - ); - - if (this.state === 'running') { - window.requestAnimationFrame(this.step.bind(this)); - } - } catch (e) { - console.log('Error caught', e); - if (e instanceof Error) { - this.robotConsole.log(e.message, 'error'); - } else if (e instanceof ProgramError) { - this.robotConsole.log(e.message, 'source'); - } else { - // e is not an error. Just log a generic error message - this.robotConsole.log('An error occurred', 'error'); - } - this.setState('error'); - } - } -} +import { ProgramError } from '../controllers/program/error'; +import { type Controller, ControllerGroup } from './Core/Controller'; +import { TypedEventTarget } from './Core/Events'; +import type { RobotConsole } from './Core/RobotConsole'; +import type { Timer } from './Core/Timer'; +import { TimeStampedEvent, type Physics } from './Physics'; +import type { Renderer } from './Render/Renderer'; + +export const worldStates = [ + 'unintialized', + 'loading', + 'ready', + 'running', + 'error', +] as const; +export type WorldState = (typeof worldStates)[number]; + +type WorldEventMap = { + worldStart: Event; + worldStateChange: Event; + beforeRender: TimeStampedEvent; + afterRender: TimeStampedEvent; +}; + +export class World extends TypedEventTarget { + state: WorldState; + physics: Physics; + render: Renderer; + timer: Timer; + robotConsole: RobotConsole; + controllers: ControllerGroup; + + constructor( + physics: Physics, + render: Renderer, + timer: Timer, + robotConsole: RobotConsole + ) { + super(); + this.state = 'unintialized'; + this.physics = physics; + this.render = render; + this.timer = timer; + this.controllers = new ControllerGroup(); + this.robotConsole = robotConsole; + } + + addController(...controllers: Controller[]) { + this.controllers.addController(...controllers); + + this.addEventListener('worldStart', () => { + controllers.forEach((controller) => { + controller.start?.(); + }); + }); + + this.addEventListener('beforeRender', (e) => { + controllers.forEach((controller) => { + controller.update?.(e.frameTimingInfo); + }); + }); + + this.physics.addEventListener('beforePhysicsUpdate', (e) => { + controllers.forEach((controller) => { + controller.fixedUpdate?.(e.frameTimingInfo); + }); + }); + } + + async init() { + this.setState('loading'); + await this.physics.start(); + this.dispatchEvent('worldStart', new Event('worldStart')); + this.setState('ready'); + } + + private setState(newState: WorldState) { + if (this.state !== newState) { + this.dispatchEvent('worldStateChange', new Event('worldStateChange')); + this.state = newState; + } + } + + pause() { + this.setState('ready'); + this.timer.pause(); + } + + start() { + if (this.state === 'ready') { + this.setState('running'); + window.requestAnimationFrame(this.step.bind(this)); + } + } + + step(timestamp: number) { + try { + const frameTimingInfo = this.timer.step(timestamp); + + const physicsTimingInfo = this.physics.step(frameTimingInfo); + + // Update render + this.dispatchEvent( + 'beforeRender', + new TimeStampedEvent('beforeRender', physicsTimingInfo) + ); + this.render.step(frameTimingInfo); + this.dispatchEvent( + 'afterRender', + new TimeStampedEvent('afterRender', physicsTimingInfo) + ); + + if (this.state === 'running') { + window.requestAnimationFrame(this.step.bind(this)); + } + } catch (e) { + console.log('Error caught', e); + if (e instanceof Error) { + this.robotConsole.log(e.message, 'error'); + } else if (e instanceof ProgramError) { + this.robotConsole.log(e.message, 'source'); + } else { + // e is not an error. Just log a generic error message + this.robotConsole.log('An error occurred', 'error'); + } + this.setState('error'); + } + } +} diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/CallbackHandler.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Core/CallbackHandler.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Core/Events.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/RobotConsole.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Core/RobotConsole.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Timer.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Core/Timer.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts b/src/bundles/robot_simulation/src/engine/__tests__/Entity/Entity.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Entity/Entity.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Math/Convert.ts b/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Math/Convert.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/src/engine/__tests__/Physics.ts similarity index 97% rename from src/bundles/robot_simulation/engine/__tests__/Physics.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Physics.ts index 8530350c21..67db39fc48 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Physics.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Physics.ts @@ -1,118 +1,118 @@ -// physics.test.js -import rapier from '@dimforge/rapier3d-compat'; -import { Physics } from '../Physics'; - -// Mock rapier -jest.mock('@dimforge/rapier3d-compat', () => { - return { - init: jest.fn(), - World: jest.fn().mockImplementation(() => ({ - timestep: jest.fn(), - createRigidBody: jest.fn(), - createCollider: jest.fn(), - castRayAndGetNormal: jest.fn(), - step: jest.fn(), - castRay: jest.fn(), - })), - Ray: jest.fn(), - RigidBodyDesc: jest.fn(), - ColliderDesc: jest.fn(), - }; -}); - -describe('Physics', () => { - let physics; - const config = { gravity: { x: 0, y: -9.81, z: 0 }, timestep: 1 / 60 }; - - beforeEach(() => { - physics = new Physics(config); - }); - - test('constructor initializes configuration', () => { - expect(physics.configuration).toEqual(config); - }); - - test('start initializes the physics world', async () => { - await physics.start(); - expect(rapier.init).toHaveBeenCalled(); - expect(physics.internals).toHaveProperty('initialized', true); - expect(physics.internals).toHaveProperty('world'); - expect(physics.internals).toHaveProperty('accumulator', physics.configuration.timestep); - }); - - test('createRigidBody throws if not initialized', () => { - expect(() => physics.createRigidBody({})).toThrow("Physics engine hasn't been initialized yet"); - }); - - test('createRigidBody creates a rigid body when initialized', async () => { - await physics.start(); // Initialize - const rigidBodyDesc = {}; // Mocked rigid body descriptor - physics.createRigidBody(rigidBodyDesc); - expect(physics.internals.world.createRigidBody).toHaveBeenCalledWith(rigidBodyDesc); - }); - - test('createCollider creates a collider when initialized', async () => { - await physics.start(); // Initialize - const colliderDesc = {}; // Mocked collider descriptor - const rigidBody = {}; // Mocked rigid body - physics.createCollider(colliderDesc, rigidBody); - expect(physics.internals.world.createCollider).toHaveBeenCalledWith(colliderDesc, rigidBody); - }); - - test('castRay returns correct result when initialized', async () => { - await physics.start(); // Initialize - const globalPosition = {}; // Mocked global position - const globalDirection = {}; // Mocked global direction - const maxDistance = 100; - - // Mock the return value of castRayAndGetNormal - const expectedResult = { toi: 10, normal: { x: 0, y: 1, z: 0 } }; - physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); - - const result = physics.castRay(globalPosition, globalDirection, maxDistance); - expect(result).toEqual({ - distance: expectedResult.toi, - normal: expectedResult.normal, - }); - expect(physics.internals.world.castRayAndGetNormal).toHaveBeenCalledWith(expect.anything(), maxDistance, true, undefined, undefined, undefined); - }); - - test('castRay returns null result castRayAndGetNormal returns null', async () => { - await physics.start(); // Initialize - const globalPosition = {}; // Mocked global position - const globalDirection = {}; // Mocked global direction - const maxDistance = 100; - - // Mock the return value of castRayAndGetNormal - const expectedResult = null; - physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); - - const result = physics.castRay(globalPosition, globalDirection, maxDistance); - expect(result).toEqual(null); - }); - - test('step advances physics world by correct timestep', async () => { - await physics.start(); // Initialize - const frameTimingInfo = { frameDuration: 1000 / 60 }; // 60 FPS - physics.step(frameTimingInfo); - expect(physics.internals.world.step).toHaveBeenCalledTimes(2); - }); - - test('castRay throws if not initialized', () => { - expect(() => { - physics.castRay({}, {}, 100); - }).toThrow("Physics engine hasn't been initialized yet"); - }); - - test('createCollider throws if not initialized', () => { - expect(() => { - physics.createCollider({}, {}, 100); - }).toThrow("Physics engine hasn't been initialized yet"); - }); - - test('step throws if not initialized', () => { - expect(() => { - physics.step({ frameDuration: 1000 / 60 }); - }).toThrow("Physics engine hasn't been initialized yet"); - }); -}); +// physics.test.js +import rapier from '@dimforge/rapier3d-compat'; +import { Physics } from '../Physics'; + +// Mock rapier +jest.mock('@dimforge/rapier3d-compat', () => { + return { + init: jest.fn(), + World: jest.fn().mockImplementation(() => ({ + timestep: jest.fn(), + createRigidBody: jest.fn(), + createCollider: jest.fn(), + castRayAndGetNormal: jest.fn(), + step: jest.fn(), + castRay: jest.fn(), + })), + Ray: jest.fn(), + RigidBodyDesc: jest.fn(), + ColliderDesc: jest.fn(), + }; +}); + +describe('Physics', () => { + let physics; + const config = { gravity: { x: 0, y: -9.81, z: 0 }, timestep: 1 / 60 }; + + beforeEach(() => { + physics = new Physics(config); + }); + + test('constructor initializes configuration', () => { + expect(physics.configuration).toEqual(config); + }); + + test('start initializes the physics world', async () => { + await physics.start(); + expect(rapier.init).toHaveBeenCalled(); + expect(physics.internals).toHaveProperty('initialized', true); + expect(physics.internals).toHaveProperty('world'); + expect(physics.internals).toHaveProperty('accumulator', physics.configuration.timestep); + }); + + test('createRigidBody throws if not initialized', () => { + expect(() => physics.createRigidBody({})).toThrow("Physics engine hasn't been initialized yet"); + }); + + test('createRigidBody creates a rigid body when initialized', async () => { + await physics.start(); // Initialize + const rigidBodyDesc = {}; // Mocked rigid body descriptor + physics.createRigidBody(rigidBodyDesc); + expect(physics.internals.world.createRigidBody).toHaveBeenCalledWith(rigidBodyDesc); + }); + + test('createCollider creates a collider when initialized', async () => { + await physics.start(); // Initialize + const colliderDesc = {}; // Mocked collider descriptor + const rigidBody = {}; // Mocked rigid body + physics.createCollider(colliderDesc, rigidBody); + expect(physics.internals.world.createCollider).toHaveBeenCalledWith(colliderDesc, rigidBody); + }); + + test('castRay returns correct result when initialized', async () => { + await physics.start(); // Initialize + const globalPosition = {}; // Mocked global position + const globalDirection = {}; // Mocked global direction + const maxDistance = 100; + + // Mock the return value of castRayAndGetNormal + const expectedResult = { toi: 10, normal: { x: 0, y: 1, z: 0 } }; + physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); + + const result = physics.castRay(globalPosition, globalDirection, maxDistance); + expect(result).toEqual({ + distance: expectedResult.toi, + normal: expectedResult.normal, + }); + expect(physics.internals.world.castRayAndGetNormal).toHaveBeenCalledWith(expect.anything(), maxDistance, true, undefined, undefined, undefined); + }); + + test('castRay returns null result castRayAndGetNormal returns null', async () => { + await physics.start(); // Initialize + const globalPosition = {}; // Mocked global position + const globalDirection = {}; // Mocked global direction + const maxDistance = 100; + + // Mock the return value of castRayAndGetNormal + const expectedResult = null; + physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); + + const result = physics.castRay(globalPosition, globalDirection, maxDistance); + expect(result).toEqual(null); + }); + + test('step advances physics world by correct timestep', async () => { + await physics.start(); // Initialize + const frameTimingInfo = { frameDuration: 1000 / 60 }; // 60 FPS + physics.step(frameTimingInfo); + expect(physics.internals.world.step).toHaveBeenCalledTimes(2); + }); + + test('castRay throws if not initialized', () => { + expect(() => { + physics.castRay({}, {}, 100); + }).toThrow("Physics engine hasn't been initialized yet"); + }); + + test('createCollider throws if not initialized', () => { + expect(() => { + physics.createCollider({}, {}, 100); + }).toThrow("Physics engine hasn't been initialized yet"); + }); + + test('step throws if not initialized', () => { + expect(() => { + physics.step({ frameDuration: 1000 / 60 }); + }).toThrow("Physics engine hasn't been initialized yet"); + }); +}); diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts b/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts similarity index 100% rename from src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts rename to src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts diff --git a/src/bundles/robot_simulation/engine/index.ts b/src/bundles/robot_simulation/src/engine/index.ts similarity index 98% rename from src/bundles/robot_simulation/engine/index.ts rename to src/bundles/robot_simulation/src/engine/index.ts index 5cdb152adf..9e27f6dd54 100644 --- a/src/bundles/robot_simulation/engine/index.ts +++ b/src/bundles/robot_simulation/src/engine/index.ts @@ -1,8 +1,8 @@ -export { World } from './World'; -export { Physics } from './Physics'; -export { Renderer } from './Render/Renderer'; -export { Timer, type FrameTimingInfo } from './Core/Timer'; -export { ControllerGroup, type Controller, ControllerMap } from './Core/Controller'; -export { Entity } from './Entity/Entity'; -export * as EntityFactory from './Entity/EntityFactory'; -export * as MeshFactory from './Render/helpers/MeshFactory'; +export { World } from './World'; +export { Physics } from './Physics'; +export { Renderer } from './Render/Renderer'; +export { Timer, type FrameTimingInfo } from './Core/Timer'; +export { ControllerGroup, type Controller, ControllerMap } from './Core/Controller'; +export { Entity } from './Entity/Entity'; +export * as EntityFactory from './Entity/EntityFactory'; +export * as MeshFactory from './Render/helpers/MeshFactory'; diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/src/ev3_functions.ts similarity index 100% rename from src/bundles/robot_simulation/ev3_functions.ts rename to src/bundles/robot_simulation/src/ev3_functions.ts diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/src/helper_functions.ts similarity index 96% rename from src/bundles/robot_simulation/helper_functions.ts rename to src/bundles/robot_simulation/src/helper_functions.ts index d2f2aa2131..474d5cdd80 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/src/helper_functions.ts @@ -1,516 +1,516 @@ -import context from 'js-slang/context'; -import { interrupt } from '../../common/specialErrors'; -import { sceneConfig } from './config'; -import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; -import { Paper, type PaperConfig } from './controllers/environment/Paper'; -import { ev3Config } from './controllers/ev3/ev3/default/config'; -import { - createDefaultEv3, - type DefaultEv3, -} from './controllers/ev3/ev3/default/ev3'; -import { Program } from './controllers/program/Program'; -import { type Controller, Physics, Renderer, Timer, World } from './engine'; - -import { RobotConsole } from './engine/Core/RobotConsole'; -import { - isRigidBodyType, - type RigidBodyType, -} from './engine/Entity/EntityFactory'; -import type { PhysicsConfig } from './engine/Physics'; -import type { RenderConfig } from './engine/Render/Renderer'; -import { getCamera, type CameraOptions } from './engine/Render/helpers/Camera'; -import { createScene } from './engine/Render/helpers/Scene'; - -/** - * @categoryDescription Configuration - * These functions are use to configure the simulation world. - * @module - */ - -/** - * A helper function that retrieves the world from the context - * - * @private - * @category helper - */ -export function getWorldFromContext(): World { - const world = context.moduleContexts.robot_simulation.state?.world; - if (world === undefined) { - throw new Error('World not initialized'); - } - return world as World; -} - -/** - * A helper function that retrieves the EV3 from context - * - * @private - * @category helper - */ -export function getEv3FromContext(): DefaultEv3 { - const ev3 = context.moduleContexts.robot_simulation.state?.ev3; - if (ev3 === undefined) { - throw new Error('ev3 not initialized'); - } - return ev3 as DefaultEv3; -} - -/** - * Create a physics engine with the provided gravity and timestep. A physics engine - * with default gravity and timestep can be created using {@link createPhysics}. - * - * The returned Physics object is designed to be passed into {@link createWorld}. - * - * **This is a configuration function and should be called within {@link init_simulation}.** - * - * @param gravity The gravity of the world - * @param timestep The timestep of the world - * @returns Physics - * - * @category Configuration - */ -export function createCustomPhysics( - gravity: number, - timestep: number -): Physics { - const physicsConfig: PhysicsConfig = { - gravity: { - x: 0, - y: gravity, - z: 0, - }, - timestep, - }; - const physics = new Physics(physicsConfig); - return physics; -} - -/** - * Create a physics engine with default gravity and timestep. Default gravity is -9.81 and timestep is 1/20. - * A custom physics engine can be created using {@link createCustomPhysics}. - * - * The returned Physics object is designed to be passed into {@link createWorld}. - * - * **This is a configuration function and should be called within {@link init_simulation}.** - * - * @returns Physics - * - * @category Configuration - */ -export function createPhysics(): Physics { - return createCustomPhysics(-9.81, 1 / 20); -} - -/** - * Creates a renderer for the simulation. - * - * The returned Renderer object is designed to be passed into {@link createWorld}. - * - * **This is a configuration function and should be called within {@link init_simulation}.** - * - * @returns Renderer - * - * @category Configuration - */ -export function createRenderer(): Renderer { - const sceneCameraOptions: CameraOptions = { - type: 'perspective', - aspect: sceneConfig.width / sceneConfig.height, - fov: 75, - near: 0.1, - far: 1000, - }; - - const renderConfig: RenderConfig = { - width: sceneConfig.width, - height: sceneConfig.height, - control: 'orbit', - }; - - const scene = createScene(); - const camera = getCamera(sceneCameraOptions); - const renderer = new Renderer(scene, camera, renderConfig); - return renderer; -} - -/** - * Creates a Timer for the simulation. - * - * The returned Timer object is designed to be passed into {@link createWorld}. - * - * **This is a configuration function and should be called within {@link init_simulation}.** - * - * @returns Timer - * - * @category Configuration - */ -export function createTimer(): Timer { - const timer = new Timer(); - return timer; -} - -/** - * Creates a RobotConsole for the simulation. - * - * The RobotConsole is used to display messages and errors to the user. The console - * messages can be seen in the console tab of the simulator. - * - * The returned RobotConsole object is designed to be passed into {@link createWorld}. - * - * **This is a configuration function and should be called within {@link init_simulation}.** - * - * @returns RobotConsole - * - * @category Configuration - */ -export function createRobotConsole(): RobotConsole { - const robot_console = new RobotConsole(); - return robot_console; -} - -/** - * Creates a custom world with the provided {@link createPhysics | physics}, {@link createRenderer | renderer}, {@link createTimer | timer} and {@link createRobotConsole | console} . - * - * A world is responsible for managing the physics, rendering, timing and console of the simulation. - * It also manages the controllers that are added to the world, ensuring that the appropriate functions - * are called at the correct time. - * - * The returned World object is designed to be returned by the {@link init_simulation} callback. - * - * You can add controllers to the world using {@link addControllerToWorld}. - * - * **This is a configuration function and should be called within {@link init_simulation}.** - * - * @example - * An empty simulation - * ``` - * init_simulation(() => { - * const physics = createPhysics(); - * const renderer = createRenderer(); - * const timer = createTimer(); - * const robot_console = createRobotConsole(); - * const world = createWorld(physics, renderer, timer, robot_console); - * - * return world; - * }); - * ``` - * - * @param physics The physics engine of the world. See {@link createPhysics} - * @param renderer The renderer engine of the world. See {@link createRenderer} - * @param timer The timer of the world. See {@link createTimer} - * @param robotConsole The console of the world. See {@link createRobotConsole} - * @returns World - * - * @category Configuration - */ -export function createWorld( - physics: Physics, - renderer: Renderer, - timer: Timer, - robotConsole: RobotConsole -): World { - const world = new World(physics, renderer, timer, robotConsole); - return world; -} - -/** - * Creates a cuboid. joel-todo: The dynamic version wont work - * - * This function is used to create the {@link createFloor | floor} and {@link createWall | wall} controllers. - * - * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. - * - * **This is a Controller function and should be called within {@link init_simulation}.** - * - * @param physics The physics engine passed to the world - * @param renderer The renderer engine of the world. See {@link createRenderer} - * @param position_x The x position of the cuboid - * @param position_y The y position of the cuboid - * @param position_z The z position of the cuboid - * @param width The width of the cuboid in meters - * @param length The length of the cuboid in meters - * @param height The height of the cuboid in meters - * @param mass The mass of the cuboid in kg - * @param color The color of the cuboid. Can be a hex code or a string. {@see https://threejs.org/docs/#api/en/math/Color} - * @param bodyType "rigid" or "dynamic". Determines if the cuboid is fixed or can move. - * @returns Cuboid - * - * @example - * ``` - * init_simulation(() => { - * const physics = createPhysics(); - * const renderer = createRenderer(); - * const timer = createTimer(); - * const robot_console = createRobotConsole(); - * const world = createWorld(physics, renderer, timer, robot_console); - * - * const cuboid = createCuboid(...); - * addControllerToWorld(cuboid, world); - * - * return world; - * }); - * ``` - * - * @category Controller - */ -export function createCuboid( - physics: Physics, - renderer: Renderer, - position_x: number, - position_y: number, - position_z: number, - width: number, - length: number, - height: number, - mass: number, - color: number | string, - bodyType: string -) { - if (isRigidBodyType(bodyType) === false) { - throw new Error('Invalid body type'); - } - - const narrowedBodyType = bodyType as RigidBodyType; - - const config: CuboidConfig = { - position: { - x: position_x, - y: position_y, - z: position_z, - }, - dimension: { - height, - width, - length, - }, - mass, - color, - type: narrowedBodyType, - }; - - const cuboid = new Cuboid(physics, renderer, config); - return cuboid; -} - -/** - * Create a floor. This function is a wrapper around {@link createCuboid}. - * - * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. - * - * **This is a Controller function and should be called within {@link init_simulation}.** - * - * @param physics The physics engine of the world. See {@link createPhysics} - * @param renderer The renderer engine of the world. See {@link createRenderer} - * @returns Cuboid - * - * @category Controller - */ -export function createFloor(physics: Physics, renderer: Renderer) { - const floor = createCuboid( - physics, - renderer, - 0, // position_x - -0.5, // position_y - 0, // position_z - 20, // width - 20, // length - 1, // height - 1, // mass - 'white', // color - 'fixed' // bodyType - ); - return floor; -} - -/** - * Creates a wall. This function is a wrapper around {@link createCuboid}. - * - * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. - * - * **This is a Controller function and should be called within {@link init_simulation}.** - * - * @param physics The physics engine of the world. See {@link createPhysics} - * @param renderer The renderer engine of the world. See {@link createRenderer} - * @param x The x position of the wall - * @param y The y position of the wall - * @param width The width of the wall in meters - * @param length The length of the wall in meters - * @param height The height of the wall in meters - * @returns Cuboid - * - * @category Controller - */ -export function createWall( - physics: Physics, - renderer: Renderer, - x: number, - y: number, - width: number, - length: number, - height: number -) { - const wall = createCuboid( - physics, - renderer, - x, // position_x - height / 2, - y, // position_y - width, // width - length, // length - height, // height - 1, // mass - 'yellow', // color - 'fixed' // bodyType - ); - return wall; -} - -/** - * Creates a paper on the floor. - * - * The returned Paper object is designed to be added to the world using {@link addControllerToWorld}. - * - * **This is a Controller function and should be called within {@link init_simulation}.** - * - * @param render The renderer engine of the world. See {@link createRenderer} - * @param url The url of the image to be displayed on the paper. - * @param width The width of the paper in meters. - * @param height The height of the paper in meters. - * @param x The x position of the paper. - * @param y The y position of the paper. - * @param rotation The rotation of the paper in degrees. - * - * @returns Paper - * - * @category Controller - */ -export function createPaper( - render: Renderer, - url: string, - width: number, - height: number, - x: number, - y: number, - rotation: number -) { - const paperConfig: PaperConfig = { - url, - dimension: { - width, - height, - }, - position: { x, y }, - rotation: (rotation * Math.PI) / 180, - }; - const paper = new Paper(render, paperConfig); - return paper; -} - -/** - * Creates a CSE machine as a Program Object. The CSE machine is used to evaluate the code written - * by the user. The execution of the code will be automatically synchronized with the simulation - * to ensure that the code is executed at the correct time. - * - * The returned Program object is designed to be added to the world using {@link addControllerToWorld}. - * - * **This is a Controller function and should be called within {@link init_simulation}.** - * - * @returns Program - * - * @category Controller - * - */ -export function createCSE() { - const code = context.unTypecheckedCode[0]; - const program = new Program(code); - return program; -} - -/** - * Add a controller to the world. - * - * The controller is a unit of computation modelled after Unity's MonoBehaviour. It is used to - * encapsulate the logic of the simulation. Controllers can be used to create robots, sensors, - * actuators, and other objects in the simulation. - * - * The controller should be added to the world using this function in order for the simulation to - * access the controller's logic. - * - * **This is a Utility function and should be called within {@link init_simulation}.* - * - * @param controller - * @param world - * - * @category Utility - */ -export function addControllerToWorld(controller: Controller, world: World) { - world.addController(controller); -} - -/** - * Save a value to the context. - * - * There are 2 important values to be saved. The world and the ev3. - * The world needs to be saved in order for the simulation to access the physics, renderer, timer and console. - * The ev3 needs to be saved in order for the "ev3_" functions to access the EV3 - * - * @param key The key to save the value as - * @param value The value to save - * - * @returns void - */ -export function saveToContext(key: string, value: any) { - if (!context.moduleContexts.robot_simulation.state) { - context.moduleContexts.robot_simulation.state = {}; - } - context.moduleContexts.robot_simulation.state[key] = value; -} - -/** - * Create an EV3. - * - * The resulting EV3 should be saved to the context using {@link saveToContext}. - * - * The returned EV3 object is designed to be added to the world using {@link addControllerToWorld}. - * - * **This is a Controller function and should be called within {@link init_simulation}.** - * - * @example - * ``` - * init_simulation(() => { - * ... - * const ev3 = createEv3(physics, renderer); - * saveToContext('ev3', ev3); - * }) - * ``` - * - * @param physics The physics engine of the world. See {@link createPhysics} - * @param renderer The renderer engine of the world. See {@link createRenderer} - * @returns EV3 - */ -export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { - const ev3 = createDefaultEv3(physics, renderer, ev3Config); - return ev3; -} - -/** - * Initialize the simulation world. This function is to be called before the robot code. - * This function is used to describe the simulation environment and the controllers. - * - * The callback function takes in no parameters and returns a world created by {@link createWorld}. - * The world should be configured with the physics, renderer, timer and console. - * The controllers should be added to the world using {@link addControllerToWorld}. - * The world should be saved to the context using {@link saveToContext}. - * - * @param worldFactory A callback function that returns the world object. Type signature: () => World - * @returns void - */ -export function init_simulation(worldFactory: () => World) { - const storedWorld = context.moduleContexts.robot_simulation.state?.world; - if (storedWorld !== undefined) { - return; - } - const world = worldFactory(); - world.init(); - interrupt(); -} +import context from 'js-slang/context'; +import { interrupt } from '../../common/specialErrors'; +import { sceneConfig } from './config'; +import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; +import { Paper, type PaperConfig } from './controllers/environment/Paper'; +import { ev3Config } from './controllers/ev3/ev3/default/config'; +import { + createDefaultEv3, + type DefaultEv3, +} from './controllers/ev3/ev3/default/ev3'; +import { Program } from './controllers/program/Program'; +import { type Controller, Physics, Renderer, Timer, World } from './engine'; + +import { RobotConsole } from './engine/Core/RobotConsole'; +import { + isRigidBodyType, + type RigidBodyType, +} from './engine/Entity/EntityFactory'; +import type { PhysicsConfig } from './engine/Physics'; +import type { RenderConfig } from './engine/Render/Renderer'; +import { getCamera, type CameraOptions } from './engine/Render/helpers/Camera'; +import { createScene } from './engine/Render/helpers/Scene'; + +/** + * @categoryDescription Configuration + * These functions are use to configure the simulation world. + * @module + */ + +/** + * A helper function that retrieves the world from the context + * + * @private + * @category helper + */ +export function getWorldFromContext(): World { + const world = context.moduleContexts.robot_simulation.state?.world; + if (world === undefined) { + throw new Error('World not initialized'); + } + return world as World; +} + +/** + * A helper function that retrieves the EV3 from context + * + * @private + * @category helper + */ +export function getEv3FromContext(): DefaultEv3 { + const ev3 = context.moduleContexts.robot_simulation.state?.ev3; + if (ev3 === undefined) { + throw new Error('ev3 not initialized'); + } + return ev3 as DefaultEv3; +} + +/** + * Create a physics engine with the provided gravity and timestep. A physics engine + * with default gravity and timestep can be created using {@link createPhysics}. + * + * The returned Physics object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @param gravity The gravity of the world + * @param timestep The timestep of the world + * @returns Physics + * + * @category Configuration + */ +export function createCustomPhysics( + gravity: number, + timestep: number +): Physics { + const physicsConfig: PhysicsConfig = { + gravity: { + x: 0, + y: gravity, + z: 0, + }, + timestep, + }; + const physics = new Physics(physicsConfig); + return physics; +} + +/** + * Create a physics engine with default gravity and timestep. Default gravity is -9.81 and timestep is 1/20. + * A custom physics engine can be created using {@link createCustomPhysics}. + * + * The returned Physics object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns Physics + * + * @category Configuration + */ +export function createPhysics(): Physics { + return createCustomPhysics(-9.81, 1 / 20); +} + +/** + * Creates a renderer for the simulation. + * + * The returned Renderer object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns Renderer + * + * @category Configuration + */ +export function createRenderer(): Renderer { + const sceneCameraOptions: CameraOptions = { + type: 'perspective', + aspect: sceneConfig.width / sceneConfig.height, + fov: 75, + near: 0.1, + far: 1000, + }; + + const renderConfig: RenderConfig = { + width: sceneConfig.width, + height: sceneConfig.height, + control: 'orbit', + }; + + const scene = createScene(); + const camera = getCamera(sceneCameraOptions); + const renderer = new Renderer(scene, camera, renderConfig); + return renderer; +} + +/** + * Creates a Timer for the simulation. + * + * The returned Timer object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns Timer + * + * @category Configuration + */ +export function createTimer(): Timer { + const timer = new Timer(); + return timer; +} + +/** + * Creates a RobotConsole for the simulation. + * + * The RobotConsole is used to display messages and errors to the user. The console + * messages can be seen in the console tab of the simulator. + * + * The returned RobotConsole object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns RobotConsole + * + * @category Configuration + */ +export function createRobotConsole(): RobotConsole { + const robot_console = new RobotConsole(); + return robot_console; +} + +/** + * Creates a custom world with the provided {@link createPhysics | physics}, {@link createRenderer | renderer}, {@link createTimer | timer} and {@link createRobotConsole | console} . + * + * A world is responsible for managing the physics, rendering, timing and console of the simulation. + * It also manages the controllers that are added to the world, ensuring that the appropriate functions + * are called at the correct time. + * + * The returned World object is designed to be returned by the {@link init_simulation} callback. + * + * You can add controllers to the world using {@link addControllerToWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @example + * An empty simulation + * ``` + * init_simulation(() => { + * const physics = createPhysics(); + * const renderer = createRenderer(); + * const timer = createTimer(); + * const robot_console = createRobotConsole(); + * const world = createWorld(physics, renderer, timer, robot_console); + * + * return world; + * }); + * ``` + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @param timer The timer of the world. See {@link createTimer} + * @param robotConsole The console of the world. See {@link createRobotConsole} + * @returns World + * + * @category Configuration + */ +export function createWorld( + physics: Physics, + renderer: Renderer, + timer: Timer, + robotConsole: RobotConsole +): World { + const world = new World(physics, renderer, timer, robotConsole); + return world; +} + +/** + * Creates a cuboid. joel-todo: The dynamic version wont work + * + * This function is used to create the {@link createFloor | floor} and {@link createWall | wall} controllers. + * + * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param physics The physics engine passed to the world + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @param position_x The x position of the cuboid + * @param position_y The y position of the cuboid + * @param position_z The z position of the cuboid + * @param width The width of the cuboid in meters + * @param length The length of the cuboid in meters + * @param height The height of the cuboid in meters + * @param mass The mass of the cuboid in kg + * @param color The color of the cuboid. Can be a hex code or a string. {@see https://threejs.org/docs/#api/en/math/Color} + * @param bodyType "rigid" or "dynamic". Determines if the cuboid is fixed or can move. + * @returns Cuboid + * + * @example + * ``` + * init_simulation(() => { + * const physics = createPhysics(); + * const renderer = createRenderer(); + * const timer = createTimer(); + * const robot_console = createRobotConsole(); + * const world = createWorld(physics, renderer, timer, robot_console); + * + * const cuboid = createCuboid(...); + * addControllerToWorld(cuboid, world); + * + * return world; + * }); + * ``` + * + * @category Controller + */ +export function createCuboid( + physics: Physics, + renderer: Renderer, + position_x: number, + position_y: number, + position_z: number, + width: number, + length: number, + height: number, + mass: number, + color: number | string, + bodyType: string +) { + if (isRigidBodyType(bodyType) === false) { + throw new Error('Invalid body type'); + } + + const narrowedBodyType = bodyType as RigidBodyType; + + const config: CuboidConfig = { + position: { + x: position_x, + y: position_y, + z: position_z, + }, + dimension: { + height, + width, + length, + }, + mass, + color, + type: narrowedBodyType, + }; + + const cuboid = new Cuboid(physics, renderer, config); + return cuboid; +} + +/** + * Create a floor. This function is a wrapper around {@link createCuboid}. + * + * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @returns Cuboid + * + * @category Controller + */ +export function createFloor(physics: Physics, renderer: Renderer) { + const floor = createCuboid( + physics, + renderer, + 0, // position_x + -0.5, // position_y + 0, // position_z + 20, // width + 20, // length + 1, // height + 1, // mass + 'white', // color + 'fixed' // bodyType + ); + return floor; +} + +/** + * Creates a wall. This function is a wrapper around {@link createCuboid}. + * + * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @param x The x position of the wall + * @param y The y position of the wall + * @param width The width of the wall in meters + * @param length The length of the wall in meters + * @param height The height of the wall in meters + * @returns Cuboid + * + * @category Controller + */ +export function createWall( + physics: Physics, + renderer: Renderer, + x: number, + y: number, + width: number, + length: number, + height: number +) { + const wall = createCuboid( + physics, + renderer, + x, // position_x + height / 2, + y, // position_y + width, // width + length, // length + height, // height + 1, // mass + 'yellow', // color + 'fixed' // bodyType + ); + return wall; +} + +/** + * Creates a paper on the floor. + * + * The returned Paper object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param render The renderer engine of the world. See {@link createRenderer} + * @param url The url of the image to be displayed on the paper. + * @param width The width of the paper in meters. + * @param height The height of the paper in meters. + * @param x The x position of the paper. + * @param y The y position of the paper. + * @param rotation The rotation of the paper in degrees. + * + * @returns Paper + * + * @category Controller + */ +export function createPaper( + render: Renderer, + url: string, + width: number, + height: number, + x: number, + y: number, + rotation: number +) { + const paperConfig: PaperConfig = { + url, + dimension: { + width, + height, + }, + position: { x, y }, + rotation: (rotation * Math.PI) / 180, + }; + const paper = new Paper(render, paperConfig); + return paper; +} + +/** + * Creates a CSE machine as a Program Object. The CSE machine is used to evaluate the code written + * by the user. The execution of the code will be automatically synchronized with the simulation + * to ensure that the code is executed at the correct time. + * + * The returned Program object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @returns Program + * + * @category Controller + * + */ +export function createCSE() { + const code = context.unTypecheckedCode[0]; + const program = new Program(code); + return program; +} + +/** + * Add a controller to the world. + * + * The controller is a unit of computation modelled after Unity's MonoBehaviour. It is used to + * encapsulate the logic of the simulation. Controllers can be used to create robots, sensors, + * actuators, and other objects in the simulation. + * + * The controller should be added to the world using this function in order for the simulation to + * access the controller's logic. + * + * **This is a Utility function and should be called within {@link init_simulation}.* + * + * @param controller + * @param world + * + * @category Utility + */ +export function addControllerToWorld(controller: Controller, world: World) { + world.addController(controller); +} + +/** + * Save a value to the context. + * + * There are 2 important values to be saved. The world and the ev3. + * The world needs to be saved in order for the simulation to access the physics, renderer, timer and console. + * The ev3 needs to be saved in order for the "ev3_" functions to access the EV3 + * + * @param key The key to save the value as + * @param value The value to save + * + * @returns void + */ +export function saveToContext(key: string, value: any) { + if (!context.moduleContexts.robot_simulation.state) { + context.moduleContexts.robot_simulation.state = {}; + } + context.moduleContexts.robot_simulation.state[key] = value; +} + +/** + * Create an EV3. + * + * The resulting EV3 should be saved to the context using {@link saveToContext}. + * + * The returned EV3 object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @example + * ``` + * init_simulation(() => { + * ... + * const ev3 = createEv3(physics, renderer); + * saveToContext('ev3', ev3); + * }) + * ``` + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @returns EV3 + */ +export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { + const ev3 = createDefaultEv3(physics, renderer, ev3Config); + return ev3; +} + +/** + * Initialize the simulation world. This function is to be called before the robot code. + * This function is used to describe the simulation environment and the controllers. + * + * The callback function takes in no parameters and returns a world created by {@link createWorld}. + * The world should be configured with the physics, renderer, timer and console. + * The controllers should be added to the world using {@link addControllerToWorld}. + * The world should be saved to the context using {@link saveToContext}. + * + * @param worldFactory A callback function that returns the world object. Type signature: () => World + * @returns void + */ +export function init_simulation(worldFactory: () => World) { + const storedWorld = context.moduleContexts.robot_simulation.state?.world; + if (storedWorld !== undefined) { + return; + } + const world = worldFactory(); + world.init(); + interrupt(); +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/src/index.ts similarity index 94% rename from src/bundles/robot_simulation/index.ts rename to src/bundles/robot_simulation/src/index.ts index d74463c628..359190f580 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/src/index.ts @@ -1,39 +1,39 @@ -/** - * Robot simulator for EV3. - * - * @module robot_simulation - * @author Joel Chan - */ - -export { - ev3_motorA, - ev3_motorB, - ev3_motorC, - ev3_motorD, - ev3_runToRelativePosition, - ev3_colorSensorRed, - ev3_colorSensorGreen, - ev3_pause, - ev3_colorSensor, - ev3_colorSensorBlue, - ev3_ultrasonicSensor, - ev3_ultrasonicSensorDistance, -} from './ev3_functions'; - -export { - createCustomPhysics, - createPhysics, - createRenderer, - init_simulation, - createCuboid, - createTimer, - createWorld, - createWall, - createEv3, - createPaper, - createFloor, - createCSE, - addControllerToWorld, - createRobotConsole, - saveToContext, -} from './helper_functions'; +/** + * Robot simulator for EV3. + * + * @module robot_simulation + * @author Joel Chan + */ + +export { + ev3_motorA, + ev3_motorB, + ev3_motorC, + ev3_motorD, + ev3_runToRelativePosition, + ev3_colorSensorRed, + ev3_colorSensorGreen, + ev3_pause, + ev3_colorSensor, + ev3_colorSensorBlue, + ev3_ultrasonicSensor, + ev3_ultrasonicSensorDistance, +} from './ev3_functions'; + +export { + createCustomPhysics, + createPhysics, + createRenderer, + init_simulation, + createCuboid, + createTimer, + createWorld, + createWall, + createEv3, + createPaper, + createFloor, + createCSE, + addControllerToWorld, + createRobotConsole, + saveToContext, +} from './helper_functions'; diff --git a/src/bundles/robot_simulation/tsconfig.json b/src/bundles/robot_simulation/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/robot_simulation/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/rune/package.json b/src/bundles/rune/package.json new file mode 100644 index 0000000000..edcb0455b1 --- /dev/null +++ b/src/bundles/rune/package.json @@ -0,0 +1,8 @@ +{ + "name": "rune", + "version": "1.0.0", + "private": true, + "dependencies": { + "gl-matrix": "^3.3.0" + } +} diff --git a/src/bundles/rune/display.ts b/src/bundles/rune/src/display.ts similarity index 100% rename from src/bundles/rune/display.ts rename to src/bundles/rune/src/display.ts diff --git a/src/bundles/rune/functions.ts b/src/bundles/rune/src/functions.ts similarity index 100% rename from src/bundles/rune/functions.ts rename to src/bundles/rune/src/functions.ts diff --git a/src/bundles/rune/index.ts b/src/bundles/rune/src/index.ts similarity index 100% rename from src/bundles/rune/index.ts rename to src/bundles/rune/src/index.ts diff --git a/src/bundles/rune/rune.ts b/src/bundles/rune/src/rune.ts similarity index 100% rename from src/bundles/rune/rune.ts rename to src/bundles/rune/src/rune.ts diff --git a/src/bundles/rune/runes_ops.ts b/src/bundles/rune/src/runes_ops.ts similarity index 100% rename from src/bundles/rune/runes_ops.ts rename to src/bundles/rune/src/runes_ops.ts diff --git a/src/bundles/rune/runes_webgl.ts b/src/bundles/rune/src/runes_webgl.ts similarity index 100% rename from src/bundles/rune/runes_webgl.ts rename to src/bundles/rune/src/runes_webgl.ts diff --git a/src/bundles/rune/ruomu_journal.md b/src/bundles/rune/src/ruomu_journal.md similarity index 100% rename from src/bundles/rune/ruomu_journal.md rename to src/bundles/rune/src/ruomu_journal.md diff --git a/src/bundles/rune/tsconfig.json b/src/bundles/rune/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/rune/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/rune_in_words/package.json b/src/bundles/rune_in_words/package.json new file mode 100644 index 0000000000..d033941e20 --- /dev/null +++ b/src/bundles/rune_in_words/package.json @@ -0,0 +1,5 @@ +{ + "name": "rune_in_words", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/rune_in_words/functions.ts b/src/bundles/rune_in_words/src/functions.ts similarity index 100% rename from src/bundles/rune_in_words/functions.ts rename to src/bundles/rune_in_words/src/functions.ts diff --git a/src/bundles/rune_in_words/index.ts b/src/bundles/rune_in_words/src/index.ts similarity index 100% rename from src/bundles/rune_in_words/index.ts rename to src/bundles/rune_in_words/src/index.ts diff --git a/src/bundles/rune_in_words/rune.ts b/src/bundles/rune_in_words/src/rune.ts similarity index 100% rename from src/bundles/rune_in_words/rune.ts rename to src/bundles/rune_in_words/src/rune.ts diff --git a/src/bundles/rune_in_words/runes_ops.ts b/src/bundles/rune_in_words/src/runes_ops.ts similarity index 100% rename from src/bundles/rune_in_words/runes_ops.ts rename to src/bundles/rune_in_words/src/runes_ops.ts diff --git a/src/bundles/rune_in_words/tsconfig.json b/src/bundles/rune_in_words/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/rune_in_words/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/scrabble/package.json b/src/bundles/scrabble/package.json new file mode 100644 index 0000000000..1ca0aad31d --- /dev/null +++ b/src/bundles/scrabble/package.json @@ -0,0 +1,5 @@ +{ + "name": "scrabble", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/scrabble/__tests__/__snapshots__/index.ts.snap b/src/bundles/scrabble/src/__tests__/__snapshots__/index.ts.snap similarity index 100% rename from src/bundles/scrabble/__tests__/__snapshots__/index.ts.snap rename to src/bundles/scrabble/src/__tests__/__snapshots__/index.ts.snap diff --git a/src/bundles/scrabble/__tests__/index.ts b/src/bundles/scrabble/src/__tests__/index.ts similarity index 100% rename from src/bundles/scrabble/__tests__/index.ts rename to src/bundles/scrabble/src/__tests__/index.ts diff --git a/src/bundles/scrabble/functions.ts b/src/bundles/scrabble/src/functions.ts similarity index 100% rename from src/bundles/scrabble/functions.ts rename to src/bundles/scrabble/src/functions.ts diff --git a/src/bundles/scrabble/index.ts b/src/bundles/scrabble/src/index.ts similarity index 100% rename from src/bundles/scrabble/index.ts rename to src/bundles/scrabble/src/index.ts diff --git a/src/bundles/scrabble/tsconfig.json b/src/bundles/scrabble/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/scrabble/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/scrabble/types.ts b/src/bundles/scrabble/types.ts deleted file mode 100644 index 771c27b3a9..0000000000 --- a/src/bundles/scrabble/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Pair = [H, T]; -export type EmptyList = null; -export type NonEmptyList = Pair; -export type List = EmptyList | NonEmptyList; diff --git a/src/bundles/sound/package.json b/src/bundles/sound/package.json new file mode 100644 index 0000000000..b395021874 --- /dev/null +++ b/src/bundles/sound/package.json @@ -0,0 +1,5 @@ +{ + "name": "sound", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/sound/__tests__/sound.test.ts b/src/bundles/sound/src/__tests__/sound.test.ts similarity index 100% rename from src/bundles/sound/__tests__/sound.test.ts rename to src/bundles/sound/src/__tests__/sound.test.ts diff --git a/src/bundles/sound/functions.ts b/src/bundles/sound/src/functions.ts similarity index 100% rename from src/bundles/sound/functions.ts rename to src/bundles/sound/src/functions.ts diff --git a/src/bundles/sound/index.ts b/src/bundles/sound/src/index.ts similarity index 100% rename from src/bundles/sound/index.ts rename to src/bundles/sound/src/index.ts diff --git a/src/bundles/stereo_sound/riffwave.ts b/src/bundles/sound/src/riffwave.ts similarity index 100% rename from src/bundles/stereo_sound/riffwave.ts rename to src/bundles/sound/src/riffwave.ts diff --git a/src/bundles/sound/types.ts b/src/bundles/sound/src/types.ts similarity index 100% rename from src/bundles/sound/types.ts rename to src/bundles/sound/src/types.ts diff --git a/src/bundles/sound/tsconfig.json b/src/bundles/sound/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/sound/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/sound_matrix/package.json b/src/bundles/sound_matrix/package.json new file mode 100644 index 0000000000..69ce7e3b68 --- /dev/null +++ b/src/bundles/sound_matrix/package.json @@ -0,0 +1,8 @@ +{ + "name": "sound_matrix", + "version": "1.0.0", + "private": true, + "dependencies": { + "js-slang": "^1.0.81" + } +} diff --git a/src/bundles/sound_matrix/functions.ts b/src/bundles/sound_matrix/src/functions.ts similarity index 100% rename from src/bundles/sound_matrix/functions.ts rename to src/bundles/sound_matrix/src/functions.ts diff --git a/src/bundles/sound_matrix/index.ts b/src/bundles/sound_matrix/src/index.ts similarity index 100% rename from src/bundles/sound_matrix/index.ts rename to src/bundles/sound_matrix/src/index.ts diff --git a/src/bundles/sound_matrix/list.ts b/src/bundles/sound_matrix/src/list.ts similarity index 100% rename from src/bundles/sound_matrix/list.ts rename to src/bundles/sound_matrix/src/list.ts diff --git a/src/bundles/sound_matrix/types.ts b/src/bundles/sound_matrix/src/types.ts similarity index 100% rename from src/bundles/sound_matrix/types.ts rename to src/bundles/sound_matrix/src/types.ts diff --git a/src/bundles/sound_matrix/tsconfig.json b/src/bundles/sound_matrix/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/sound_matrix/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/stereo_sound/package.json b/src/bundles/stereo_sound/package.json new file mode 100644 index 0000000000..89f2a91f01 --- /dev/null +++ b/src/bundles/stereo_sound/package.json @@ -0,0 +1,5 @@ +{ + "name": "stereo_sound", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/stereo_sound/functions.ts b/src/bundles/stereo_sound/src/functions.ts similarity index 100% rename from src/bundles/stereo_sound/functions.ts rename to src/bundles/stereo_sound/src/functions.ts diff --git a/src/bundles/stereo_sound/index.ts b/src/bundles/stereo_sound/src/index.ts similarity index 100% rename from src/bundles/stereo_sound/index.ts rename to src/bundles/stereo_sound/src/index.ts diff --git a/src/bundles/sound/riffwave.ts b/src/bundles/stereo_sound/src/riffwave.ts similarity index 100% rename from src/bundles/sound/riffwave.ts rename to src/bundles/stereo_sound/src/riffwave.ts diff --git a/src/bundles/stereo_sound/types.ts b/src/bundles/stereo_sound/src/types.ts similarity index 100% rename from src/bundles/stereo_sound/types.ts rename to src/bundles/stereo_sound/src/types.ts diff --git a/src/bundles/stereo_sound/tsconfig.json b/src/bundles/stereo_sound/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/stereo_sound/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/tsconfig.json b/src/bundles/tsconfig.json new file mode 100644 index 0000000000..d0f238849a --- /dev/null +++ b/src/bundles/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "composite": true + }, + "include": ["*/**/*.ts"] +} \ No newline at end of file diff --git a/src/bundles/unittest/package.json b/src/bundles/unittest/package.json new file mode 100644 index 0000000000..ed0679e0b7 --- /dev/null +++ b/src/bundles/unittest/package.json @@ -0,0 +1,5 @@ +{ + "name": "unittest", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/unittest/__tests__/index.ts b/src/bundles/unittest/src/__tests__/index.ts similarity index 100% rename from src/bundles/unittest/__tests__/index.ts rename to src/bundles/unittest/src/__tests__/index.ts diff --git a/src/bundles/unittest/asserts.ts b/src/bundles/unittest/src/asserts.ts similarity index 100% rename from src/bundles/unittest/asserts.ts rename to src/bundles/unittest/src/asserts.ts diff --git a/src/bundles/unittest/functions.ts b/src/bundles/unittest/src/functions.ts similarity index 100% rename from src/bundles/unittest/functions.ts rename to src/bundles/unittest/src/functions.ts diff --git a/src/bundles/unittest/index.ts b/src/bundles/unittest/src/index.ts similarity index 100% rename from src/bundles/unittest/index.ts rename to src/bundles/unittest/src/index.ts diff --git a/src/bundles/unittest/mocks.ts b/src/bundles/unittest/src/mocks.ts similarity index 100% rename from src/bundles/unittest/mocks.ts rename to src/bundles/unittest/src/mocks.ts diff --git a/src/bundles/unittest/types.ts b/src/bundles/unittest/src/types.ts similarity index 100% rename from src/bundles/unittest/types.ts rename to src/bundles/unittest/src/types.ts diff --git a/src/bundles/unittest/tsconfig.json b/src/bundles/unittest/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/unittest/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/unity_academy/package.json b/src/bundles/unity_academy/package.json new file mode 100644 index 0000000000..c2684afc97 --- /dev/null +++ b/src/bundles/unity_academy/package.json @@ -0,0 +1,13 @@ +{ + "name": "unity_academy", + "version": "1.0.0", + "private": true, + "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.2" + } +} diff --git a/src/bundles/unity_academy/UnityAcademy.tsx b/src/bundles/unity_academy/src/UnityAcademy.tsx similarity index 100% rename from src/bundles/unity_academy/UnityAcademy.tsx rename to src/bundles/unity_academy/src/UnityAcademy.tsx diff --git a/src/bundles/unity_academy/UnityAcademyMaths.ts b/src/bundles/unity_academy/src/UnityAcademyMaths.ts similarity index 100% rename from src/bundles/unity_academy/UnityAcademyMaths.ts rename to src/bundles/unity_academy/src/UnityAcademyMaths.ts diff --git a/src/bundles/unity_academy/config.ts b/src/bundles/unity_academy/src/config.ts similarity index 100% rename from src/bundles/unity_academy/config.ts rename to src/bundles/unity_academy/src/config.ts diff --git a/src/bundles/unity_academy/functions.ts b/src/bundles/unity_academy/src/functions.ts similarity index 100% rename from src/bundles/unity_academy/functions.ts rename to src/bundles/unity_academy/src/functions.ts diff --git a/src/bundles/unity_academy/index.ts b/src/bundles/unity_academy/src/index.ts similarity index 100% rename from src/bundles/unity_academy/index.ts rename to src/bundles/unity_academy/src/index.ts diff --git a/src/bundles/unity_academy/tsconfig.json b/src/bundles/unity_academy/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/unity_academy/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/bundles/wasm/package.json b/src/bundles/wasm/package.json new file mode 100644 index 0000000000..eda3d3d43f --- /dev/null +++ b/src/bundles/wasm/package.json @@ -0,0 +1,5 @@ +{ + "name": "wasm", + "version": "1.0.0", + "private": true +} diff --git a/src/bundles/wasm/index.ts b/src/bundles/wasm/src/index.ts similarity index 100% rename from src/bundles/wasm/index.ts rename to src/bundles/wasm/src/index.ts diff --git a/src/bundles/wasm/wabt.ts b/src/bundles/wasm/src/wabt.ts similarity index 100% rename from src/bundles/wasm/wabt.ts rename to src/bundles/wasm/src/wabt.ts diff --git a/src/bundles/wasm/tsconfig.json b/src/bundles/wasm/tsconfig.json new file mode 100644 index 0000000000..adc10992dc --- /dev/null +++ b/src/bundles/wasm/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true + }, + "extends": [ + "../tsconfig.json" + ], + "includes": [ + "./src/*" + ], + "references": [ + { + "path": "../commons/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/src/common/deepPartial.ts b/src/common/deepPartial.ts deleted file mode 100644 index a8e0138413..0000000000 --- a/src/common/deepPartial.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type DeepPartial = T extends object ? { - [P in keyof T]?: DeepPartial; -} : T; diff --git a/src/common/__tests__/hextocolor.ts b/src/commons/__tests__/hextocolor.ts similarity index 100% rename from src/common/__tests__/hextocolor.ts rename to src/commons/__tests__/hextocolor.ts diff --git a/src/typings/js-slang/context.d.ts b/src/commons/js-slang/context.d.ts similarity index 100% rename from src/typings/js-slang/context.d.ts rename to src/commons/js-slang/context.d.ts diff --git a/src/common/specialErrors.ts b/src/commons/specialErrors.ts similarity index 100% rename from src/common/specialErrors.ts rename to src/commons/specialErrors.ts diff --git a/src/commons/tsconfig.json b/src/commons/tsconfig.json new file mode 100644 index 0000000000..58ff875272 --- /dev/null +++ b/src/commons/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "composite": true + }, + "extends": ["../tsconfig.json"], + "include": ["*/**/*.ts"] +} \ No newline at end of file diff --git a/src/typings/type_map/index.ts b/src/commons/type_map/index.ts similarity index 100% rename from src/typings/type_map/index.ts rename to src/commons/type_map/index.ts diff --git a/src/typings/type_helpers.ts b/src/commons/types.ts similarity index 54% rename from src/typings/type_helpers.ts rename to src/commons/types.ts index e7b51cbed6..d032ecfcf9 100644 --- a/src/typings/type_helpers.ts +++ b/src/commons/types.ts @@ -1,6 +1,25 @@ import type { Context } from 'js-slang'; import type { FC } from 'react'; +/** + * Represents an animation drawn using WebGL + * @field duration Duration of the animation in secondss + * @field fps Framerate in frames per second + */ +export abstract class glAnimation { + constructor(public readonly duration: number, public readonly fps: number) { } + + public abstract getFrame(timestamp: number): AnimFrame; + + public static isAnimation = (obj: any): obj is glAnimation => obj.fps !== undefined; +} +export interface AnimFrame { + draw: (canvas: HTMLCanvasElement) => void; +} +export type DeepPartial = T extends object ? { + [P in keyof T]?: DeepPartial; +} : T; + /** * DebuggerContext type used by frontend to assist typing information */ diff --git a/src/common/utilities.ts b/src/commons/utilities.ts similarity index 100% rename from src/common/utilities.ts rename to src/commons/utilities.ts diff --git a/src/tabs/ArcadeTwod/package.json b/src/tabs/ArcadeTwod/package.json new file mode 100644 index 0000000000..4228106946 --- /dev/null +++ b/src/tabs/ArcadeTwod/package.json @@ -0,0 +1,11 @@ +{ + "name": "ArcadeTwod", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/ArcadeTwod/tsconfig.json b/src/tabs/ArcadeTwod/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/ArcadeTwod/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/AugmentedReality/package.json b/src/tabs/AugmentedReality/package.json new file mode 100644 index 0000000000..5cafaf29bb --- /dev/null +++ b/src/tabs/AugmentedReality/package.json @@ -0,0 +1,11 @@ +{ + "name": "AugmentedReality", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/AugmentedReality/tsconfig.json b/src/tabs/AugmentedReality/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/AugmentedReality/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/CopyGc/package.json b/src/tabs/CopyGc/package.json new file mode 100644 index 0000000000..9637b9490d --- /dev/null +++ b/src/tabs/CopyGc/package.json @@ -0,0 +1,11 @@ +{ + "name": "CopyGc", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/CopyGc/tsconfig.json b/src/tabs/CopyGc/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/CopyGc/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Csg/package.json b/src/tabs/Csg/package.json new file mode 100644 index 0000000000..e12180d51c --- /dev/null +++ b/src/tabs/Csg/package.json @@ -0,0 +1,11 @@ +{ + "name": "Csg", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Csg/tsconfig.json b/src/tabs/Csg/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Csg/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Curve/package.json b/src/tabs/Curve/package.json new file mode 100644 index 0000000000..3f4b7bd176 --- /dev/null +++ b/src/tabs/Curve/package.json @@ -0,0 +1,11 @@ +{ + "name": "Curve", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Curve/tsconfig.json b/src/tabs/Curve/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Curve/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Game/package.json b/src/tabs/Game/package.json new file mode 100644 index 0000000000..a07d98ee13 --- /dev/null +++ b/src/tabs/Game/package.json @@ -0,0 +1,11 @@ +{ + "name": "Game", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Game/tsconfig.json b/src/tabs/Game/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Game/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/MarkSweep/package.json b/src/tabs/MarkSweep/package.json new file mode 100644 index 0000000000..3c05c3b275 --- /dev/null +++ b/src/tabs/MarkSweep/package.json @@ -0,0 +1,11 @@ +{ + "name": "MarkSweep", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/MarkSweep/tsconfig.json b/src/tabs/MarkSweep/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/MarkSweep/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Nbody/package.json b/src/tabs/Nbody/package.json new file mode 100644 index 0000000000..128773ff56 --- /dev/null +++ b/src/tabs/Nbody/package.json @@ -0,0 +1,11 @@ +{ + "name": "Nbody", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Nbody/tsconfig.json b/src/tabs/Nbody/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Nbody/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Painter/package.json b/src/tabs/Painter/package.json new file mode 100644 index 0000000000..26b753d39e --- /dev/null +++ b/src/tabs/Painter/package.json @@ -0,0 +1,11 @@ +{ + "name": "Painter", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Painter/tsconfig.json b/src/tabs/Painter/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Painter/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/physics_2d/DebugDrawCanvas.tsx b/src/tabs/Physics2D/DebugDrawCanvas.tsx similarity index 100% rename from src/tabs/physics_2d/DebugDrawCanvas.tsx rename to src/tabs/Physics2D/DebugDrawCanvas.tsx diff --git a/src/tabs/physics_2d/index.tsx b/src/tabs/Physics2D/index.tsx similarity index 100% rename from src/tabs/physics_2d/index.tsx rename to src/tabs/Physics2D/index.tsx diff --git a/src/tabs/Physics2D/package.json b/src/tabs/Physics2D/package.json new file mode 100644 index 0000000000..ba9adda38c --- /dev/null +++ b/src/tabs/Physics2D/package.json @@ -0,0 +1,11 @@ +{ + "name": "Physics2D", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Physics2D/tsconfig.json b/src/tabs/Physics2D/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Physics2D/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Pixnflix/package.json b/src/tabs/Pixnflix/package.json new file mode 100644 index 0000000000..b284a7555f --- /dev/null +++ b/src/tabs/Pixnflix/package.json @@ -0,0 +1,11 @@ +{ + "name": "Pixnflix", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Pixnflix/tsconfig.json b/src/tabs/Pixnflix/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Pixnflix/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Plotly/package.json b/src/tabs/Plotly/package.json new file mode 100644 index 0000000000..d25c398603 --- /dev/null +++ b/src/tabs/Plotly/package.json @@ -0,0 +1,11 @@ +{ + "name": "Plotly", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Plotly/tsconfig.json b/src/tabs/Plotly/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Plotly/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Repeat/package.json b/src/tabs/Repeat/package.json new file mode 100644 index 0000000000..f8b8b979d2 --- /dev/null +++ b/src/tabs/Repeat/package.json @@ -0,0 +1,11 @@ +{ + "name": "Repeat", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Repeat/tsconfig.json b/src/tabs/Repeat/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Repeat/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Repl/package.json b/src/tabs/Repl/package.json new file mode 100644 index 0000000000..6faf425db8 --- /dev/null +++ b/src/tabs/Repl/package.json @@ -0,0 +1,11 @@ +{ + "name": "Repl", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Repl/tsconfig.json b/src/tabs/Repl/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Repl/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/RobotSimulation/package.json b/src/tabs/RobotSimulation/package.json new file mode 100644 index 0000000000..c861b31049 --- /dev/null +++ b/src/tabs/RobotSimulation/package.json @@ -0,0 +1,11 @@ +{ + "name": "RobotSimulation", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/RobotSimulation/tsconfig.json b/src/tabs/RobotSimulation/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/RobotSimulation/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Rune/package.json b/src/tabs/Rune/package.json new file mode 100644 index 0000000000..0df4521225 --- /dev/null +++ b/src/tabs/Rune/package.json @@ -0,0 +1,11 @@ +{ + "name": "Rune", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Rune/tsconfig.json b/src/tabs/Rune/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Rune/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Sound/package.json b/src/tabs/Sound/package.json new file mode 100644 index 0000000000..58d6b7b834 --- /dev/null +++ b/src/tabs/Sound/package.json @@ -0,0 +1,11 @@ +{ + "name": "Sound", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Sound/tsconfig.json b/src/tabs/Sound/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Sound/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/SoundMatrix/package.json b/src/tabs/SoundMatrix/package.json new file mode 100644 index 0000000000..8305ca1ce9 --- /dev/null +++ b/src/tabs/SoundMatrix/package.json @@ -0,0 +1,11 @@ +{ + "name": "SoundMatrix", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/SoundMatrix/tsconfig.json b/src/tabs/SoundMatrix/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/SoundMatrix/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/StereoSound/package.json b/src/tabs/StereoSound/package.json new file mode 100644 index 0000000000..f18427476a --- /dev/null +++ b/src/tabs/StereoSound/package.json @@ -0,0 +1,11 @@ +{ + "name": "StereoSound", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/StereoSound/tsconfig.json b/src/tabs/StereoSound/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/StereoSound/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/Unittest/package.json b/src/tabs/Unittest/package.json new file mode 100644 index 0000000000..1efa7855f0 --- /dev/null +++ b/src/tabs/Unittest/package.json @@ -0,0 +1,11 @@ +{ + "name": "Unittest", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/Unittest/tsconfig.json b/src/tabs/Unittest/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/Unittest/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/UnityAcademy/package.json b/src/tabs/UnityAcademy/package.json new file mode 100644 index 0000000000..d4441d0b5d --- /dev/null +++ b/src/tabs/UnityAcademy/package.json @@ -0,0 +1,11 @@ +{ + "name": "UnityAcademy", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/UnityAcademy/tsconfig.json b/src/tabs/UnityAcademy/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/UnityAcademy/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/common/package.json b/src/tabs/common/package.json new file mode 100644 index 0000000000..84f2b1dbd3 --- /dev/null +++ b/src/tabs/common/package.json @@ -0,0 +1,11 @@ +{ + "name": "common", + "version": "1.0.0", + "private": true, + "dependencies": { + "react": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1" + } +} diff --git a/src/tabs/common/tsconfig.json b/src/tabs/common/tsconfig.json new file mode 100644 index 0000000000..caffc81dd2 --- /dev/null +++ b/src/tabs/common/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": { + "modules": "../tsconfig.json" + } +} \ No newline at end of file diff --git a/src/tabs/package.json b/src/tabs/package.json new file mode 100644 index 0000000000..a127c8e311 --- /dev/null +++ b/src/tabs/package.json @@ -0,0 +1,7 @@ +{ + "name": "tabs", + "private": true, + "workspaces": [ + "./*" + ] +} diff --git a/src/tabs/tsconfig.json b/src/tabs/tsconfig.json new file mode 100644 index 0000000000..467aee112a --- /dev/null +++ b/src/tabs/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": false, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "paths": { + "@src/*": ["./src/*"] + }, + "target": "ESNext", + "skipLibCheck": true, + "composite": true + }, + "include": ["./src", "jest.setup.ts"] +} diff --git a/src/tsconfig.json b/src/tsconfig.json index bf2a08f2e6..48a4ed7a1e 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -6,20 +6,14 @@ "allowSyntheticDefaultImports": true, /* See https://www.typescriptlang.org/tsconfig#esModuleInterop */ "esModuleInterop": true, - /* Controls how JSX constructs are emitted in JavaScript files. This only affects output of JS files that started in .tsx files. */ - "jsx": "react-jsx", /* See https://www.typescriptlang.org/tsconfig#lib */ "lib": ["es6", "dom", "es2016", "ESNext", "scripthost"], /* Sets the module system for the program. See the Modules reference page for more information. */ "module": "esnext", /* Specify the module resolution strategy: 'node' (Node.js) or 'classic' (used in TypeScript before the release of 1.6). */ "moduleResolution": "node", - /* Do not emit compiler output files like JavaScript source code, source-maps or declarations. */ - "noEmit": true, /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ "resolveJsonModule": true, - /* The longest common path of all non-declaration input files. */ - "rootDir": "./", /* Enables the generation of sourcemap files. These files allow debuggers and other tools to display the original TypeScript source code when actually working with the emitted JavaScript files. */ "sourceMap": false, /* Skip running typescript on declaration files. This option is needed due to a known bug in react-ace */ diff --git a/src/typings/anim_types.ts b/src/typings/anim_types.ts deleted file mode 100644 index 82ffd3c1c3..0000000000 --- a/src/typings/anim_types.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface AnimFrame { - draw: (canvas: HTMLCanvasElement) => void; -} - -/** - * Represents an animation drawn using WebGL - * @field duration Duration of the animation in secondss - * @field fps Framerate in frames per second - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export abstract class glAnimation { - constructor(public readonly duration: number, public readonly fps: number) {} - - public abstract getFrame(timestamp: number): AnimFrame; - - public static isAnimation = (obj: any): obj is glAnimation => obj.fps !== undefined; -} From fe8853be156b928474eab3ec1ba327589842415e Mon Sep 17 00:00:00 2001 From: "DESKTOP-G08HS3B\\Lee Yi" Date: Thu, 8 May 2025 20:03:38 -0400 Subject: [PATCH 002/112] Some more tidying up --- buildtools/manifest.schema.json | 20 ++++++ buildtools/package.json | 19 +++++ buildtools/src/build/modules/bundle.ts | 21 ++++++ buildtools/src/build/modules/commons.ts | 65 +++++++++++++++++ buildtools/src/build/modules/tab.ts | 29 ++++++++ .../src/commands/__tests__/hextocolor.ts | 17 +++++ buildtools/src/commands/specialErrors.ts | 8 +++ buildtools/src/commands/utilities.ts | 16 +++++ buildtools/src/type_map/index.ts | 69 +++++++++++++++++++ buildtools/src/types/js-slang/context.d.ts | 4 ++ buildtools/src/types/types.ts | 46 +++++++++++++ buildtools/tsconfig.json | 16 +++++ src/bundles/ar/package.json | 7 +- src/bundles/ar/tsconfig.json | 5 +- src/bundles/arcade_2d/package.json | 7 +- src/bundles/arcade_2d/tsconfig.json | 5 +- src/bundles/binary_tree/package.json | 9 ++- src/bundles/binary_tree/tsconfig.json | 5 +- src/bundles/communication/package.json | 7 +- src/bundles/communication/tsconfig.json | 5 +- src/bundles/copy_gc/package.json | 9 ++- src/bundles/copy_gc/tsconfig.json | 5 +- src/bundles/csg/package.json | 7 +- src/bundles/csg/tsconfig.json | 5 +- src/bundles/curve/manifest.json | 3 + src/bundles/curve/package.json | 7 +- src/bundles/curve/tsconfig.json | 8 --- src/bundles/game/package.json | 7 +- src/bundles/game/tsconfig.json | 5 +- src/bundles/mark_sweep/package.json | 9 ++- src/bundles/mark_sweep/tsconfig.json | 5 +- src/bundles/nbody/package.json | 7 +- src/bundles/nbody/tsconfig.json | 5 +- src/bundles/package.json | 4 +- src/bundles/painter/package.json | 7 +- src/bundles/painter/tsconfig.json | 5 +- src/bundles/physics_2d/package.json | 7 +- src/bundles/physics_2d/tsconfig.json | 5 +- src/bundles/pix_n_flix/package.json | 9 ++- src/bundles/pix_n_flix/tsconfig.json | 5 +- src/bundles/plotly/package.json | 7 +- src/bundles/plotly/tsconfig.json | 5 +- src/bundles/remote_execution/package.json | 7 +- src/bundles/remote_execution/tsconfig.json | 5 +- src/bundles/repeat/package.json | 9 ++- src/bundles/repeat/tsconfig.json | 5 +- src/bundles/repl/package.json | 7 +- src/bundles/repl/tsconfig.json | 5 +- src/bundles/robot_simulation/package.json | 7 +- src/bundles/robot_simulation/tsconfig.json | 5 +- src/bundles/rune/package.json | 7 +- src/bundles/rune/tsconfig.json | 5 +- src/bundles/rune_in_words/package.json | 9 ++- src/bundles/rune_in_words/tsconfig.json | 5 +- src/bundles/scrabble/package.json | 9 ++- src/bundles/scrabble/tsconfig.json | 5 +- src/bundles/sound/package.json | 9 ++- src/bundles/sound/tsconfig.json | 5 +- src/bundles/sound_matrix/package.json | 7 +- src/bundles/sound_matrix/tsconfig.json | 5 +- src/bundles/stereo_sound/package.json | 9 ++- src/bundles/stereo_sound/tsconfig.json | 5 +- src/bundles/tsconfig.json | 6 +- src/bundles/unittest/package.json | 9 ++- src/bundles/unittest/tsconfig.json | 5 +- src/bundles/unity_academy/package.json | 7 +- src/bundles/unity_academy/tsconfig.json | 5 +- src/bundles/wasm/package.json | 9 ++- src/bundles/wasm/tsconfig.json | 5 +- src/commons/tsconfig.json | 7 -- src/tsconfig.json | 4 +- 71 files changed, 509 insertions(+), 194 deletions(-) create mode 100644 buildtools/manifest.schema.json create mode 100644 buildtools/package.json create mode 100644 buildtools/src/build/modules/bundle.ts create mode 100644 buildtools/src/build/modules/commons.ts create mode 100644 buildtools/src/build/modules/tab.ts create mode 100644 buildtools/src/commands/__tests__/hextocolor.ts create mode 100644 buildtools/src/commands/specialErrors.ts create mode 100644 buildtools/src/commands/utilities.ts create mode 100644 buildtools/src/type_map/index.ts create mode 100644 buildtools/src/types/js-slang/context.d.ts create mode 100644 buildtools/src/types/types.ts create mode 100644 buildtools/tsconfig.json create mode 100644 src/bundles/curve/manifest.json delete mode 100644 src/commons/tsconfig.json diff --git a/buildtools/manifest.schema.json b/buildtools/manifest.schema.json new file mode 100644 index 0000000000..76c02488eb --- /dev/null +++ b/buildtools/manifest.schema.json @@ -0,0 +1,20 @@ +{ + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of your bundle", + "pattern": "\b[a-z0-9]+(?:_[a-z0-9]+)*\b" + }, + "tabs": { + "description": "Tabs that will be loaded with this bundle", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["name"] + } +} \ No newline at end of file diff --git a/buildtools/package.json b/buildtools/package.json new file mode 100644 index 0000000000..3a4c05f8d2 --- /dev/null +++ b/buildtools/package.json @@ -0,0 +1,19 @@ +{ + "name": "@sourceacademy/module-buildtools", + "version": "1.0.0", + "devDependencies": { + "@commander-js/extra-typings": "^13.0.0", + "@types/node": "^20.12.12" + }, + "bin": "./dist/commands/index.js", + "type": "module", + "dependencies": { + "acorn": "^8.8.1", + "astring": "^1.8.6", + "chalk": "^5.0.1", + "commander": "^13.0.0", + "console-table-printer": "^2.11.1", + "esbuild": "^0.25.0", + "typedoc": "^0.25.12" + } +} diff --git a/buildtools/src/build/modules/bundle.ts b/buildtools/src/build/modules/bundle.ts new file mode 100644 index 0000000000..98bb41c72b --- /dev/null +++ b/buildtools/src/build/modules/bundle.ts @@ -0,0 +1,21 @@ +import fs from 'fs/promises' +import { build as esbuild } from 'esbuild' +import { commonEsbuildOptions, outputBundleOrTab } from './commons' + +export async function buildBundle(bundleDir: string) { + let actualManifest; + try { + const rawManifest = await fs.readFile(`${bundleDir}/manifest.json`, 'utf-8') + actualManifest = JSON.parse(rawManifest) + } catch (error) { + throw new Error(`${bundleDir} is not a valid bundle!`) + } + + const { outputFiles: [result]} = await esbuild({ + ...commonEsbuildOptions, + entryPoints: [`${bundleDir}/src/index.ts`], + tsconfig: `${bundleDir}/tsconfig.json` + }) + + await outputBundleOrTab(result, 'build') +} \ No newline at end of file diff --git a/buildtools/src/build/modules/commons.ts b/buildtools/src/build/modules/commons.ts new file mode 100644 index 0000000000..0154b8e0ca --- /dev/null +++ b/buildtools/src/build/modules/commons.ts @@ -0,0 +1,65 @@ +import fs from 'fs/promises'; +import pathlib from 'path'; +import { parse } from 'acorn'; +import { generate } from 'astring'; +import type { BuildOptions as ESBuildOptions, OutputFile } from 'esbuild'; +import type es from 'estree'; + +export const commonEsbuildOptions: ESBuildOptions = { + bundle: true, + format: 'iife', + define: { + process: JSON.stringify({ + env: { + NODE_ENV: 'production' + } + }) + }, + external: ['js-slang*'], + globalName: 'module', + platform: 'browser', + target: 'es6', + write: false +}; + +export async function outputBundleOrTab({ path, text }: OutputFile, outDir: string) { + const [type, name] = path.split(pathlib.sep) + .slice(-3, -1); + + const parsed = parse(text, { ecmaVersion: 6 }) as es.Program; + + // Account for 'use strict'; directives + let declStatement: es.VariableDeclaration; + if (parsed.body[0].type === 'VariableDeclaration') { + declStatement = parsed.body[0]; + } else { + declStatement = parsed.body[1] as es.VariableDeclaration; + } + + const { init: callExpression } = declStatement.declarations[0]; + if (callExpression.type !== 'CallExpression') { + throw new Error(`Expected a CallExpression, got ${callExpression.type}`) + } + + const moduleCode = callExpression.callee + + if (moduleCode.type !== 'FunctionExpression' && moduleCode.type !== 'ArrowFunctionExpression') { + throw new Error(`Expected a function, got ${moduleCode.type}`) + } + + const output: es.ExportDefaultDeclaration = { + type: 'ExportDefaultDeclaration', + declaration: { + ...moduleCode, + params: [{ + type: 'Identifier', + name: 'require' + }] + } + }; + + const file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); + const writeStream = file.createWriteStream(); + generate(output, { output: writeStream }); +} + diff --git a/buildtools/src/build/modules/tab.ts b/buildtools/src/build/modules/tab.ts new file mode 100644 index 0000000000..4814496c0d --- /dev/null +++ b/buildtools/src/build/modules/tab.ts @@ -0,0 +1,29 @@ +import fs from 'fs/promises' +import { build as esbuild } from 'esbuild' +import { commonEsbuildOptions, outputBundleOrTab } from './commons' + +export async function buildTab(tabDir: string) { + let actualManifest; + try { + const rawManifest = await fs.readFile(`${tabDir}/manifest.json`, 'utf-8') + actualManifest = JSON.parse(rawManifest) + } catch (error) { + throw new Error(`${tabDir} is not a valid tab!`) + } + + const { outputFiles: [result]} = await esbuild({ + ...commonEsbuildOptions, + entryPoints: [`${tabDir}/src/index.ts`], + external: [ + ...commonEsbuildOptions.external, + 'react', + 'react-ace', + 'react-dom', + 'react/jsx-runtime', + '@blueprintjs/*' + ], + tsconfig: `${tabDir}/tsconfig.json` + }) + + await outputBundleOrTab(result, 'build') +} \ No newline at end of file diff --git a/buildtools/src/commands/__tests__/hextocolor.ts b/buildtools/src/commands/__tests__/hextocolor.ts new file mode 100644 index 0000000000..835095350a --- /dev/null +++ b/buildtools/src/commands/__tests__/hextocolor.ts @@ -0,0 +1,17 @@ +import { hexToColor } from '../utilities'; + +describe('Test hexToColor', () => { + test.each([ + ['#FFFFFF', [1, 1, 1]], + ['ffffff', [1, 1, 1]], + ['0088ff', [0, 0.53, 1]], + ['#000000', [0, 0, 0]], + ['#GGGGGG', [0, 0, 0]], + ['888888', [0.53, 0.53, 0.53]] + ])('Testing %s', (c, expected) => { + const result = hexToColor(c); + for (let i = 0; i < expected.length; i++) { + expect(result[i]).toBeCloseTo(expected[i]); + } + }); +}); diff --git a/buildtools/src/commands/specialErrors.ts b/buildtools/src/commands/specialErrors.ts new file mode 100644 index 0000000000..54aa1fbbfa --- /dev/null +++ b/buildtools/src/commands/specialErrors.ts @@ -0,0 +1,8 @@ +/** + * This function is used to interrupt the frontend execution. + * When called, the frontend will notify that the program has ended successfully + * and display a message that the program is stopped by a module. + */ +export function interrupt() { + throw 'source_academy_interrupt'; +} diff --git a/buildtools/src/commands/utilities.ts b/buildtools/src/commands/utilities.ts new file mode 100644 index 0000000000..f25780ffdd --- /dev/null +++ b/buildtools/src/commands/utilities.ts @@ -0,0 +1,16 @@ +/* [Exports] */ +export function degreesToRadians(degrees: number): number { + return (degrees / 360) * (2 * Math.PI); +} + +export function hexToColor(hex: string): [number, number, number] { + const regex = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/igu; + const groups = regex.exec(hex); + + if (groups == undefined) return [0, 0, 0]; + return [ + parseInt(groups[1], 16) / 0xff, + parseInt(groups[2], 16) / 0xff, + parseInt(groups[3], 16) / 0xff + ]; +} diff --git a/buildtools/src/type_map/index.ts b/buildtools/src/type_map/index.ts new file mode 100644 index 0000000000..014138baa9 --- /dev/null +++ b/buildtools/src/type_map/index.ts @@ -0,0 +1,69 @@ +export const type_map : Record = {}; + +const registerType = (name: string, declaration: string) => { + if (name == 'prelude') { + type_map['prelude'] = type_map['prelude'] != undefined ? type_map['prelude'] + '\n' + declaration : declaration; + } else { + type_map[name] = declaration; + } +}; + +export const classDeclaration = (name: string) => { + return (_target: any) => { + registerType('prelude', `class ${name} {}`); + }; +}; + +export const typeDeclaration = (type: string, declaration = null) => { + return (_target: any) => { + const typeAlias = `type ${_target.name} = ${type}`; + let variableDeclaration = `const ${_target.name} = ${declaration === null ? type : declaration}`; + + switch (type) { + case 'number': + variableDeclaration = `const ${_target.name} = 0`; + break; + case 'string': + variableDeclaration = `const ${_target.name} = ''`; + break; + case 'boolean': + variableDeclaration = `const ${_target.name} = false`; + break; + case 'void': + variableDeclaration = ''; + break; + } + + registerType('prelude', `${typeAlias};\n${variableDeclaration};`); + }; +}; + +export const functionDeclaration = (paramTypes: string, returnType: string) => { + return (_target: any, propertyKey: string, _descriptor: PropertyDescriptor) => { + let returnValue = ''; + switch (returnType) { + case 'number': + returnValue = 'return 0'; + break; + case 'string': + returnValue = "return ''"; + break; + case 'boolean': + returnValue = 'return false'; + break; + case 'void': + returnValue = ''; + break; + default: + returnValue = `return ${returnType}`; + break; + } + registerType(propertyKey, `function ${propertyKey} (${paramTypes}) : ${returnType} { ${returnValue} }`); + }; +}; + +export const variableDeclaration = (type: string) => { + return (_target: any, propertyKey: string) => { + registerType(propertyKey, `const ${propertyKey}: ${type} = ${type}`); + }; +}; diff --git a/buildtools/src/types/js-slang/context.d.ts b/buildtools/src/types/js-slang/context.d.ts new file mode 100644 index 0000000000..841849bbc1 --- /dev/null +++ b/buildtools/src/types/js-slang/context.d.ts @@ -0,0 +1,4 @@ +import type { Context } from 'js-slang'; + +const ctx: Context; +export default ctx; diff --git a/buildtools/src/types/types.ts b/buildtools/src/types/types.ts new file mode 100644 index 0000000000..d032ecfcf9 --- /dev/null +++ b/buildtools/src/types/types.ts @@ -0,0 +1,46 @@ +import type { Context } from 'js-slang'; +import type { FC } from 'react'; + +/** + * Represents an animation drawn using WebGL + * @field duration Duration of the animation in secondss + * @field fps Framerate in frames per second + */ +export abstract class glAnimation { + constructor(public readonly duration: number, public readonly fps: number) { } + + public abstract getFrame(timestamp: number): AnimFrame; + + public static isAnimation = (obj: any): obj is glAnimation => obj.fps !== undefined; +} +export interface AnimFrame { + draw: (canvas: HTMLCanvasElement) => void; +} +export type DeepPartial = T extends object ? { + [P in keyof T]?: DeepPartial; +} : T; + +/** + * DebuggerContext type used by frontend to assist typing information + */ +export type DebuggerContext = { + result: any; + lastDebuggerResult: any; + code: string; + context: Context; + workspaceLocation?: any; +}; + +export type ModuleContexts = Context['moduleContexts']; + +/** + * Interface to represent objects that require a string representation in the + * REPL + */ +export interface ReplResult { + toReplString: () => string; +} + +export type ModuleTab = FC<{ context: DebuggerContext }>; + +export const getModuleState = ({ context: { moduleContexts } }: DebuggerContext, moduleName: string) => moduleContexts[moduleName].state as T; diff --git a/buildtools/tsconfig.json b/buildtools/tsconfig.json new file mode 100644 index 0000000000..4144761d5a --- /dev/null +++ b/buildtools/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "dist", + "target": "ESNext", + "strict": false + }, + "include": ["./src", "jest.setup.ts"], + "exclude": [ + "./src/templates/templates/**", + "./src/build/docs/__tests__/test_mocks" + ] +} diff --git a/src/bundles/ar/package.json b/src/bundles/ar/package.json index 5e473c36a1..fae06622cb 100644 --- a/src/bundles/ar/package.json +++ b/src/bundles/ar/package.json @@ -1,8 +1,11 @@ { - "name": "ar", + "name": "@sourceacademy/bundle-ar", "version": "1.0.0", "private": true, "dependencies": { "saar": "^1.0.4" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/ar/tsconfig.json b/src/bundles/ar/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/ar/tsconfig.json +++ b/src/bundles/ar/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/arcade_2d/package.json b/src/bundles/arcade_2d/package.json index b86222958e..a6ff3bb632 100644 --- a/src/bundles/arcade_2d/package.json +++ b/src/bundles/arcade_2d/package.json @@ -1,8 +1,11 @@ { - "name": "arcade_2d", + "name": "@sourceacademy/bundle-arcade_2d", "version": "1.0.0", "private": true, "dependencies": { "phaser": "^3.54.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/arcade_2d/tsconfig.json b/src/bundles/arcade_2d/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/arcade_2d/tsconfig.json +++ b/src/bundles/arcade_2d/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/binary_tree/package.json b/src/bundles/binary_tree/package.json index 2bb6dd6263..6bbe15ebd3 100644 --- a/src/bundles/binary_tree/package.json +++ b/src/bundles/binary_tree/package.json @@ -1,5 +1,8 @@ { - "name": "binary_tree", + "name": "@sourceacademy/bundle-binary_tree", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/binary_tree/tsconfig.json b/src/bundles/binary_tree/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/binary_tree/tsconfig.json +++ b/src/bundles/binary_tree/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/communication/package.json b/src/bundles/communication/package.json index 183a3f1cd0..86299b6693 100644 --- a/src/bundles/communication/package.json +++ b/src/bundles/communication/package.json @@ -1,9 +1,12 @@ { - "name": "communication", + "name": "@sourceacademy/bundle-communication", "version": "1.0.0", "private": true, "dependencies": { "mqtt": "^4.3.7", "uniqid": "^5.4.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/communication/tsconfig.json b/src/bundles/communication/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/communication/tsconfig.json +++ b/src/bundles/communication/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/copy_gc/package.json b/src/bundles/copy_gc/package.json index 8ced0a0c43..6a7b6128a1 100644 --- a/src/bundles/copy_gc/package.json +++ b/src/bundles/copy_gc/package.json @@ -1,5 +1,8 @@ { - "name": "copy_gc", + "name": "@sourceacademy/bundle-copy_gc", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/copy_gc/tsconfig.json b/src/bundles/copy_gc/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/copy_gc/tsconfig.json +++ b/src/bundles/copy_gc/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/csg/package.json b/src/bundles/csg/package.json index 3ba31a099b..cd4a7271ee 100644 --- a/src/bundles/csg/package.json +++ b/src/bundles/csg/package.json @@ -1,10 +1,13 @@ { - "name": "csg", + "name": "@sourceacademy/bundle-csg", "version": "1.0.0", "private": true, "dependencies": { "@jscad/modeling": "2.9.6", "@jscad/regl-renderer": "^2.6.1", "@jscad/stl-serializer": "2.1.11" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/csg/tsconfig.json b/src/bundles/csg/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/csg/tsconfig.json +++ b/src/bundles/csg/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/curve/manifest.json b/src/bundles/curve/manifest.json new file mode 100644 index 0000000000..2f37dc5b09 --- /dev/null +++ b/src/bundles/curve/manifest.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} \ No newline at end of file diff --git a/src/bundles/curve/package.json b/src/bundles/curve/package.json index d906f60f55..1a9ee2ba62 100644 --- a/src/bundles/curve/package.json +++ b/src/bundles/curve/package.json @@ -1,8 +1,11 @@ { - "name": "curve", + "name": "@sourceacademy/bundle-curve", "version": "1.0.0", "private": true, "dependencies": { "gl-matrix": "^3.3.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/curve/tsconfig.json b/src/bundles/curve/tsconfig.json index adc10992dc..c0e5b1b93b 100644 --- a/src/bundles/curve/tsconfig.json +++ b/src/bundles/curve/tsconfig.json @@ -1,16 +1,8 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], "includes": [ "./src/*" - ], - "references": [ - { - "path": "../commons/tsconfig.json" - } ] } \ No newline at end of file diff --git a/src/bundles/game/package.json b/src/bundles/game/package.json index 1efe8782b8..44b147c64c 100644 --- a/src/bundles/game/package.json +++ b/src/bundles/game/package.json @@ -1,9 +1,12 @@ { - "name": "game", + "name": "@sourceacademy/bundle-game", "version": "1.0.0", "private": true, "dependencies": { "js-slang": "^1.0.81", "phaser": "^3.54.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/game/tsconfig.json b/src/bundles/game/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/game/tsconfig.json +++ b/src/bundles/game/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/mark_sweep/package.json b/src/bundles/mark_sweep/package.json index 661d02ec03..4c944440a5 100644 --- a/src/bundles/mark_sweep/package.json +++ b/src/bundles/mark_sweep/package.json @@ -1,5 +1,8 @@ { - "name": "mark_sweep", + "name": "@sourceacademy/bundle-mark_sweep", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/mark_sweep/tsconfig.json b/src/bundles/mark_sweep/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/mark_sweep/tsconfig.json +++ b/src/bundles/mark_sweep/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/nbody/package.json b/src/bundles/nbody/package.json index 9e067016e5..51c14acf45 100644 --- a/src/bundles/nbody/package.json +++ b/src/bundles/nbody/package.json @@ -1,8 +1,11 @@ { - "name": "nbody", + "name": "@sourceacademy/bundle-nbody", "version": "1.0.0", "private": true, "dependencies": { "nbody": "^0.2.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/nbody/tsconfig.json b/src/bundles/nbody/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/nbody/tsconfig.json +++ b/src/bundles/nbody/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/package.json b/src/bundles/package.json index 1fd4312d44..839d7476a0 100644 --- a/src/bundles/package.json +++ b/src/bundles/package.json @@ -1,7 +1,5 @@ { "private": true, "name": "bundles", - "workspaces": [ - "./*" - ] + "workspaces": ["./*"] } diff --git a/src/bundles/painter/package.json b/src/bundles/painter/package.json index 4489c4b995..424f602a0e 100644 --- a/src/bundles/painter/package.json +++ b/src/bundles/painter/package.json @@ -1,11 +1,12 @@ { - "name": "painter", + "name": "@sourceacademy/bundle-painter", "version": "1.0.0", "private": true, "dependencies": { "plotly.js-dist": "^2.17.1" }, "devDependencies": { - "@types/plotly.js": "^2.35.4" + "@types/plotly.js": "^2.35.4", + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/painter/tsconfig.json b/src/bundles/painter/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/painter/tsconfig.json +++ b/src/bundles/painter/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/physics_2d/package.json b/src/bundles/physics_2d/package.json index e7de1e7019..e2bc91c745 100644 --- a/src/bundles/physics_2d/package.json +++ b/src/bundles/physics_2d/package.json @@ -1,8 +1,11 @@ { - "name": "physics_2d", + "name": "@sourceacademy/bundle-physics_2d", "version": "1.0.0", "private": true, "dependencies": { "@box2d/core": "^0.10.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/physics_2d/tsconfig.json b/src/bundles/physics_2d/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/physics_2d/tsconfig.json +++ b/src/bundles/physics_2d/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/pix_n_flix/package.json b/src/bundles/pix_n_flix/package.json index 0c0d88f781..0166daafe9 100644 --- a/src/bundles/pix_n_flix/package.json +++ b/src/bundles/pix_n_flix/package.json @@ -1,5 +1,8 @@ { - "name": "pix_n_flix", + "name": "@sourceacademy/bundle-pix_n_flix", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/pix_n_flix/tsconfig.json b/src/bundles/pix_n_flix/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/pix_n_flix/tsconfig.json +++ b/src/bundles/pix_n_flix/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/plotly/package.json b/src/bundles/plotly/package.json index 6913303082..422b7b5120 100644 --- a/src/bundles/plotly/package.json +++ b/src/bundles/plotly/package.json @@ -1,9 +1,12 @@ { - "name": "plotly", + "name": "@sourceacademy/bundle-plotly", "version": "1.0.0", "private": true, "dependencies": { "js-slang": "^1.0.81", "sound": "workspace:^" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/plotly/tsconfig.json b/src/bundles/plotly/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/plotly/tsconfig.json +++ b/src/bundles/plotly/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/remote_execution/package.json b/src/bundles/remote_execution/package.json index fa9c2bc4f6..1792f0f664 100644 --- a/src/bundles/remote_execution/package.json +++ b/src/bundles/remote_execution/package.json @@ -1,8 +1,11 @@ { - "name": "remote_execution", + "name": "@sourceacademy/bundle-remote_execution", "version": "1.0.0", "private": true, "dependencies": { "js-slang": "^1.0.81" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/remote_execution/tsconfig.json b/src/bundles/remote_execution/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/remote_execution/tsconfig.json +++ b/src/bundles/remote_execution/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/repeat/package.json b/src/bundles/repeat/package.json index 404ee4a120..c2e4afd842 100644 --- a/src/bundles/repeat/package.json +++ b/src/bundles/repeat/package.json @@ -1,5 +1,8 @@ { - "name": "repeat", + "name": "@sourceacademy/bundle-repeat", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/repeat/tsconfig.json b/src/bundles/repeat/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/repeat/tsconfig.json +++ b/src/bundles/repeat/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/repl/package.json b/src/bundles/repl/package.json index 7f7446d5d6..9567127d32 100644 --- a/src/bundles/repl/package.json +++ b/src/bundles/repl/package.json @@ -1,8 +1,11 @@ { - "name": "repl", + "name": "@sourceacademy/bundle-repl", "version": "1.0.0", "private": true, "dependencies": { "js-slang": "^1.0.81" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/repl/tsconfig.json b/src/bundles/repl/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/repl/tsconfig.json +++ b/src/bundles/repl/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/robot_simulation/package.json b/src/bundles/robot_simulation/package.json index 1c8b3d11ca..4dbe2e5ff3 100644 --- a/src/bundles/robot_simulation/package.json +++ b/src/bundles/robot_simulation/package.json @@ -1,11 +1,12 @@ { - "name": "robot_simulation", + "name": "@sourceacademy/bundle-robot_simulation", "version": "1.0.0", "private": true, "dependencies": { "three": "^0.175.0" }, "devDependencies": { - "@types/three": "^0.175.0" + "@types/three": "^0.175.0", + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/robot_simulation/tsconfig.json b/src/bundles/robot_simulation/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/robot_simulation/tsconfig.json +++ b/src/bundles/robot_simulation/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/rune/package.json b/src/bundles/rune/package.json index edcb0455b1..72b91b0fcb 100644 --- a/src/bundles/rune/package.json +++ b/src/bundles/rune/package.json @@ -1,8 +1,11 @@ { - "name": "rune", + "name": "@sourceacademy/bundle-rune", "version": "1.0.0", "private": true, "dependencies": { "gl-matrix": "^3.3.0" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/rune/tsconfig.json b/src/bundles/rune/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/rune/tsconfig.json +++ b/src/bundles/rune/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/rune_in_words/package.json b/src/bundles/rune_in_words/package.json index d033941e20..c517f50b4e 100644 --- a/src/bundles/rune_in_words/package.json +++ b/src/bundles/rune_in_words/package.json @@ -1,5 +1,8 @@ { - "name": "rune_in_words", + "name": "@sourceacademy/bundle-rune_in_words", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/rune_in_words/tsconfig.json b/src/bundles/rune_in_words/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/rune_in_words/tsconfig.json +++ b/src/bundles/rune_in_words/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/scrabble/package.json b/src/bundles/scrabble/package.json index 1ca0aad31d..908f788f1c 100644 --- a/src/bundles/scrabble/package.json +++ b/src/bundles/scrabble/package.json @@ -1,5 +1,8 @@ { - "name": "scrabble", + "name": "@sourceacademy/bundle-scrabble", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/scrabble/tsconfig.json b/src/bundles/scrabble/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/scrabble/tsconfig.json +++ b/src/bundles/scrabble/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/sound/package.json b/src/bundles/sound/package.json index b395021874..fa27677c73 100644 --- a/src/bundles/sound/package.json +++ b/src/bundles/sound/package.json @@ -1,5 +1,8 @@ { - "name": "sound", + "name": "@sourceacademy/bundle-sound", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/sound/tsconfig.json b/src/bundles/sound/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/sound/tsconfig.json +++ b/src/bundles/sound/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/sound_matrix/package.json b/src/bundles/sound_matrix/package.json index 69ce7e3b68..5f1ab7936e 100644 --- a/src/bundles/sound_matrix/package.json +++ b/src/bundles/sound_matrix/package.json @@ -1,8 +1,11 @@ { - "name": "sound_matrix", + "name": "@sourceacademy/bundle-sound_matrix", "version": "1.0.0", "private": true, "dependencies": { "js-slang": "^1.0.81" + }, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/sound_matrix/tsconfig.json b/src/bundles/sound_matrix/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/sound_matrix/tsconfig.json +++ b/src/bundles/sound_matrix/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/stereo_sound/package.json b/src/bundles/stereo_sound/package.json index 89f2a91f01..70f0adbc05 100644 --- a/src/bundles/stereo_sound/package.json +++ b/src/bundles/stereo_sound/package.json @@ -1,5 +1,8 @@ { - "name": "stereo_sound", + "name": "@sourceacademy/bundle-stereo_sound", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/stereo_sound/tsconfig.json b/src/bundles/stereo_sound/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/stereo_sound/tsconfig.json +++ b/src/bundles/stereo_sound/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/tsconfig.json b/src/bundles/tsconfig.json index d0f238849a..48e69311c2 100644 --- a/src/bundles/tsconfig.json +++ b/src/bundles/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { - "composite": true + "paths": { + "js-slang/context": ["../../buildtools/src/types/js-slang/context.d.ts"] + } }, - "include": ["*/**/*.ts"] + "extends": ["../tsconfig.json"] } \ No newline at end of file diff --git a/src/bundles/unittest/package.json b/src/bundles/unittest/package.json index ed0679e0b7..0efda0f4fa 100644 --- a/src/bundles/unittest/package.json +++ b/src/bundles/unittest/package.json @@ -1,5 +1,8 @@ { - "name": "unittest", + "name": "@sourceacademy/bundle-unittest", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/unittest/tsconfig.json b/src/bundles/unittest/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/unittest/tsconfig.json +++ b/src/bundles/unittest/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/unity_academy/package.json b/src/bundles/unity_academy/package.json index c2684afc97..1369cb0a6c 100644 --- a/src/bundles/unity_academy/package.json +++ b/src/bundles/unity_academy/package.json @@ -1,5 +1,5 @@ { - "name": "unity_academy", + "name": "@sourceacademy/bundle-unity_academy", "version": "1.0.0", "private": true, "dependencies": { @@ -8,6 +8,7 @@ "react": "^18.3.1" }, "devDependencies": { - "@types/react": "^18.3.2" + "@types/react": "^18.3.2", + "@sourceacademy/module-buildtools": "workspace:^" } -} +} \ No newline at end of file diff --git a/src/bundles/unity_academy/tsconfig.json b/src/bundles/unity_academy/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/unity_academy/tsconfig.json +++ b/src/bundles/unity_academy/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/bundles/wasm/package.json b/src/bundles/wasm/package.json index eda3d3d43f..ee8dd52c42 100644 --- a/src/bundles/wasm/package.json +++ b/src/bundles/wasm/package.json @@ -1,5 +1,8 @@ { - "name": "wasm", + "name": "@sourceacademy/bundle-wasm", "version": "1.0.0", - "private": true -} + "private": true, + "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^" + } +} \ No newline at end of file diff --git a/src/bundles/wasm/tsconfig.json b/src/bundles/wasm/tsconfig.json index adc10992dc..8bd691a428 100644 --- a/src/bundles/wasm/tsconfig.json +++ b/src/bundles/wasm/tsconfig.json @@ -1,7 +1,4 @@ { - "compilerOptions": { - "noEmit": true - }, "extends": [ "../tsconfig.json" ], @@ -10,7 +7,7 @@ ], "references": [ { - "path": "../commons/tsconfig.json" + "path": "../../commons/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/commons/tsconfig.json b/src/commons/tsconfig.json deleted file mode 100644 index 58ff875272..0000000000 --- a/src/commons/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "composite": true - }, - "extends": ["../tsconfig.json"], - "include": ["*/**/*.ts"] -} \ No newline at end of file diff --git a/src/tsconfig.json b/src/tsconfig.json index 48a4ed7a1e..7cc7589c87 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -28,7 +28,7 @@ "noImplicitAny": false, "verbatimModuleSyntax": true, "paths": { - "js-slang/context": ["./typings/js-slang/context.d.ts"] + "js-slang/context": ["./commons/js-slang/context.d.ts"] }, "experimentalDecorators": true, "emitDecoratorMetadata": true @@ -36,5 +36,5 @@ /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ "include": ["."], /* Specifies an array of filenames or patterns that should be skipped when resolving include. */ - "exclude": ["jest.config.js"] + "exclude": ["jest.config.js", "**/__tests__"] } From 8ad43313a3ab14bde3a86f29a7b18bb0063f0354 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 12 May 2025 16:15:25 -0400 Subject: [PATCH 003/112] Refactor all bundles and tabs --- src/bundles/ar/package.json | 13 +- src/bundles/ar/tsconfig.json | 17 +- src/bundles/arcade_2d/package.json | 13 +- src/bundles/arcade_2d/tsconfig.json | 16 +- src/bundles/binary_tree/package.json | 13 +- src/bundles/binary_tree/tsconfig.json | 16 +- src/bundles/communication/package.json | 13 +- src/bundles/communication/tsconfig.json | 16 +- src/bundles/copy_gc/package.json | 13 +- src/bundles/copy_gc/tsconfig.json | 16 +- src/bundles/csg/package.json | 13 +- src/bundles/csg/src/functions.ts | 2 +- src/bundles/csg/src/jscad/renderer.ts | 2 +- src/bundles/csg/src/utilities.ts | 4 +- src/bundles/csg/tsconfig.json | 16 +- src/bundles/curve/manifest.json | 3 +- src/bundles/curve/package.json | 14 +- src/bundles/curve/src/curves_webgl.ts | 2 +- src/bundles/curve/src/types.ts | 3 +- src/bundles/curve/tsconfig.json | 11 +- src/bundles/game/package.json | 13 +- src/bundles/game/tsconfig.json | 16 +- src/bundles/mark_sweep/package.json | 13 +- src/bundles/mark_sweep/tsconfig.json | 16 +- src/bundles/nbody/package.json | 13 +- src/bundles/nbody/tsconfig.json | 16 +- src/bundles/painter/package.json | 13 +- src/bundles/painter/src/painter.ts | 2 +- src/bundles/painter/tsconfig.json | 16 +- src/bundles/physics_2d/package.json | 13 +- src/bundles/physics_2d/tsconfig.json | 16 +- src/bundles/pix_n_flix/package.json | 13 +- src/bundles/pix_n_flix/tsconfig.json | 16 +- src/bundles/plotly/package.json | 18 +- src/bundles/plotly/src/functions.ts | 2 +- src/bundles/plotly/src/plotly.ts | 2 +- src/bundles/plotly/src/sound_functions.ts | 2 +- src/bundles/plotly/tsconfig.json | 16 +- src/bundles/remote_execution/package.json | 13 +- src/bundles/remote_execution/tsconfig.json | 16 +- src/bundles/repeat/package.json | 13 +- src/bundles/repeat/tsconfig.json | 16 +- src/bundles/repl/package.json | 13 +- src/bundles/repl/tsconfig.json | 16 +- src/bundles/robot_simulation/package.json | 13 +- .../src/controllers/program/Program.ts | 2 +- .../src/controllers/utils/mergeConfig.ts | 2 +- .../src/engine/Render/debug/DebugArrow.ts | 2 +- .../robot_simulation/src/helper_functions.ts | 2 +- src/bundles/robot_simulation/tsconfig.json | 16 +- src/bundles/rune/package.json | 14 +- src/bundles/rune/src/rune.ts | 3 +- src/bundles/rune/src/runes_ops.ts | 2 +- src/bundles/rune/tsconfig.json | 16 +- src/bundles/rune_in_words/package.json | 13 +- src/bundles/rune_in_words/tsconfig.json | 16 +- src/bundles/scrabble/package.json | 13 +- src/bundles/scrabble/tsconfig.json | 16 +- src/bundles/sound/package.json | 13 +- src/bundles/sound/tsconfig.json | 16 +- src/bundles/sound_matrix/package.json | 13 +- src/bundles/sound_matrix/tsconfig.json | 16 +- src/bundles/stereo_sound/package.json | 13 +- src/bundles/stereo_sound/tsconfig.json | 16 +- src/bundles/tsconfig.json | 5 +- src/bundles/unittest/package.json | 13 +- src/bundles/unittest/tsconfig.json | 16 +- src/bundles/unity_academy/package.json | 13 +- src/bundles/unity_academy/tsconfig.json | 17 +- src/bundles/wasm/package.json | 13 +- src/bundles/wasm/tsconfig.json | 16 +- src/tabs/ArcadeTwod/index.tsx | 2 +- src/tabs/ArcadeTwod/package.json | 4 +- src/tabs/ArcadeTwod/tsconfig.json | 5 +- src/tabs/AugmentedReality/package.json | 7 +- .../{ => src}/AugmentedContent.tsx | 4 +- .../{ => src}/AugmentedLayer.tsx | 2 +- .../AugmentedReality/{ => src}/Overlay.tsx | 0 .../{ => src}/StartButton.tsx | 2 +- src/tabs/AugmentedReality/{ => src}/index.tsx | 2 +- src/tabs/AugmentedReality/tsconfig.json | 7 +- src/tabs/CopyGc/package.json | 2 +- src/tabs/CopyGc/{ => src}/index.tsx | 2 +- src/tabs/CopyGc/{ => src}/style.tsx | 0 src/tabs/CopyGc/tsconfig.json | 7 +- src/tabs/Csg/package.json | 6 +- src/tabs/Csg/{ => src}/canvas_holder.tsx | 10 +- src/tabs/Csg/{ => src}/hover_control_hint.tsx | 4 +- src/tabs/Csg/{ => src}/index.tsx | 6 +- src/tabs/Csg/tsconfig.json | 7 +- src/tabs/Curve/package.json | 6 +- src/tabs/Curve/{ => src}/__tests__/Curve.tsx | 0 .../__tests__/__snapshots__/Curve.tsx.snap | 0 .../{ => src}/animation_canvas_3d_curve.tsx | 12 +- src/tabs/Curve/{ => src}/canvas_3d_curve.tsx | 10 +- src/tabs/Curve/{ => src}/index.tsx | 12 +- src/tabs/Curve/tsconfig.json | 7 +- src/tabs/Game/package.json | 2 +- src/tabs/Game/{ => src}/constants.ts | 0 src/tabs/Game/{ => src}/index.tsx | 0 src/tabs/Game/tsconfig.json | 7 +- src/tabs/MarkSweep/package.json | 2 +- src/tabs/MarkSweep/{ => src}/index.tsx | 0 src/tabs/MarkSweep/{ => src}/style.tsx | 0 src/tabs/MarkSweep/tsconfig.json | 7 +- src/tabs/Nbody/package.json | 3 +- src/tabs/Nbody/tsconfig.json | 5 +- src/tabs/Painter/index.tsx | 6 +- src/tabs/Painter/package.json | 4 +- src/tabs/Painter/tsconfig.json | 5 +- src/tabs/Physics2D/package.json | 4 +- .../Physics2D/{ => src}/DebugDrawCanvas.tsx | 6 +- src/tabs/Physics2D/{ => src}/index.tsx | 2 +- src/tabs/Physics2D/tsconfig.json | 7 +- src/tabs/Pixnflix/index.tsx | 6 +- src/tabs/Pixnflix/package.json | 3 +- src/tabs/Pixnflix/tsconfig.json | 5 +- src/tabs/Plotly/index.tsx | 6 +- src/tabs/Plotly/package.json | 4 +- src/tabs/Plotly/tsconfig.json | 5 +- src/tabs/Repeat/package.json | 2 +- src/tabs/Repeat/tsconfig.json | 5 +- src/tabs/Repl/index.tsx | 8 +- src/tabs/Repl/package.json | 7 +- src/tabs/Repl/tsconfig.json | 5 +- src/tabs/RobotSimulation/package.json | 4 +- .../{ => src}/components/Main.tsx | 62 ++-- .../{ => src}/components/Modal.tsx | 124 +++---- .../{ => src}/components/Simulation/index.tsx | 240 ++++++------ .../components/TabPanels/ColorSensorPanel.tsx | 0 .../components/TabPanels/ConsolePanel.tsx | 0 .../components/TabPanels/MotorPidPanel.tsx | 0 .../TabPanels/UltrasonicSensorPanel.tsx | 0 .../components/TabPanels/WheelPidPanel.tsx | 0 .../TabPanels/tabComponents/LastUpdated.tsx | 0 .../TabPanels/tabComponents/Wrapper.tsx | 0 .../{ => src}/components/TabUi.tsx | 40 +- .../{ => src}/hooks/fetchFromSimulation.ts | 0 src/tabs/RobotSimulation/{ => src}/index.tsx | 80 ++-- src/tabs/RobotSimulation/tsconfig.json | 7 +- src/tabs/Rune/package.json | 4 +- src/tabs/Rune/{ => src}/__tests__/Rune.tsx | 0 .../__tests__/__snapshots__/Rune.tsx.snap | 0 src/tabs/Rune/{ => src}/hollusion_canvas.tsx | 4 +- src/tabs/Rune/{ => src}/index.tsx | 12 +- src/tabs/Rune/tsconfig.json | 7 +- src/tabs/Sound/index.tsx | 7 +- src/tabs/Sound/package.json | 4 +- src/tabs/Sound/tsconfig.json | 5 +- src/tabs/SoundMatrix/package.json | 2 +- src/tabs/SoundMatrix/tsconfig.json | 7 +- src/tabs/StereoSound/index.tsx | 7 +- src/tabs/StereoSound/package.json | 4 +- src/tabs/StereoSound/tsconfig.json | 5 +- src/tabs/Unittest/index.tsx | 5 +- src/tabs/Unittest/package.json | 4 +- src/tabs/Unittest/tsconfig.json | 5 +- src/tabs/UnityAcademy/index.tsx | 6 +- src/tabs/UnityAcademy/package.json | 5 +- src/tabs/UnityAcademy/tsconfig.json | 5 +- src/tabs/common/AnimationCanvas.tsx | 347 ------------------ src/tabs/common/AutoLoopSwitch.tsx | 25 -- src/tabs/common/ButtonComponent.tsx | 31 -- src/tabs/common/ModalDiv.tsx | 66 ---- src/tabs/common/MultItemDisplay.tsx | 142 ------- src/tabs/common/PlayButton.tsx | 27 -- src/tabs/common/WebglCanvas.tsx | 30 -- src/tabs/common/css_constants.ts | 23 -- src/tabs/common/package.json | 11 - src/tabs/common/testUtils.ts | 11 - src/tabs/common/tsconfig.json | 5 - src/tabs/tsconfig.json | 17 +- 172 files changed, 986 insertions(+), 1475 deletions(-) rename src/tabs/AugmentedReality/{ => src}/AugmentedContent.tsx (97%) rename src/tabs/AugmentedReality/{ => src}/AugmentedLayer.tsx (89%) rename src/tabs/AugmentedReality/{ => src}/Overlay.tsx (100%) rename src/tabs/AugmentedReality/{ => src}/StartButton.tsx (93%) rename src/tabs/AugmentedReality/{ => src}/index.tsx (96%) rename src/tabs/CopyGc/{ => src}/index.tsx (99%) rename src/tabs/CopyGc/{ => src}/style.tsx (100%) rename src/tabs/Csg/{ => src}/canvas_holder.tsx (92%) rename src/tabs/Csg/{ => src}/hover_control_hint.tsx (88%) rename src/tabs/Csg/{ => src}/index.tsx (82%) rename src/tabs/Curve/{ => src}/__tests__/Curve.tsx (100%) rename src/tabs/Curve/{ => src}/__tests__/__snapshots__/Curve.tsx.snap (100%) rename src/tabs/Curve/{ => src}/animation_canvas_3d_curve.tsx (95%) rename src/tabs/Curve/{ => src}/canvas_3d_curve.tsx (92%) rename src/tabs/Curve/{ => src}/index.tsx (73%) rename src/tabs/Game/{ => src}/constants.ts (100%) rename src/tabs/Game/{ => src}/index.tsx (100%) rename src/tabs/MarkSweep/{ => src}/index.tsx (100%) rename src/tabs/MarkSweep/{ => src}/style.tsx (100%) rename src/tabs/Physics2D/{ => src}/DebugDrawCanvas.tsx (97%) rename src/tabs/Physics2D/{ => src}/index.tsx (93%) rename src/tabs/RobotSimulation/{ => src}/components/Main.tsx (96%) rename src/tabs/RobotSimulation/{ => src}/components/Modal.tsx (95%) rename src/tabs/RobotSimulation/{ => src}/components/Simulation/index.tsx (88%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/ColorSensorPanel.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/ConsolePanel.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/MotorPidPanel.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/UltrasonicSensorPanel.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/WheelPidPanel.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/tabComponents/LastUpdated.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabPanels/tabComponents/Wrapper.tsx (100%) rename src/tabs/RobotSimulation/{ => src}/components/TabUi.tsx (94%) rename src/tabs/RobotSimulation/{ => src}/hooks/fetchFromSimulation.ts (100%) rename src/tabs/RobotSimulation/{ => src}/index.tsx (90%) rename src/tabs/Rune/{ => src}/__tests__/Rune.tsx (100%) rename src/tabs/Rune/{ => src}/__tests__/__snapshots__/Rune.tsx.snap (100%) rename src/tabs/Rune/{ => src}/hollusion_canvas.tsx (84%) rename src/tabs/Rune/{ => src}/index.tsx (77%) delete mode 100644 src/tabs/common/AnimationCanvas.tsx delete mode 100644 src/tabs/common/AutoLoopSwitch.tsx delete mode 100644 src/tabs/common/ButtonComponent.tsx delete mode 100644 src/tabs/common/ModalDiv.tsx delete mode 100644 src/tabs/common/MultItemDisplay.tsx delete mode 100644 src/tabs/common/PlayButton.tsx delete mode 100644 src/tabs/common/WebglCanvas.tsx delete mode 100644 src/tabs/common/css_constants.ts delete mode 100644 src/tabs/common/package.json delete mode 100644 src/tabs/common/testUtils.ts delete mode 100644 src/tabs/common/tsconfig.json diff --git a/src/bundles/ar/package.json b/src/bundles/ar/package.json index fae06622cb..c9cf1a97af 100644 --- a/src/bundles/ar/package.json +++ b/src/bundles/ar/package.json @@ -1,11 +1,22 @@ { "name": "@sourceacademy/bundle-ar", "version": "1.0.0", + "type": "module", "private": true, + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, "dependencies": { "saar": "^1.0.4" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/ar/tsconfig.json b/src/bundles/ar/tsconfig.json index 8bd691a428..8359d4d9b4 100644 --- a/src/bundles/ar/tsconfig.json +++ b/src/bundles/ar/tsconfig.json @@ -1,13 +1,10 @@ { - "extends": [ - "../tsconfig.json" - ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./dist" + }, + "extends": "../tsconfig.json", + "include": [ + "./src" ] } \ No newline at end of file diff --git a/src/bundles/arcade_2d/package.json b/src/bundles/arcade_2d/package.json index a6ff3bb632..6483135a62 100644 --- a/src/bundles/arcade_2d/package.json +++ b/src/bundles/arcade_2d/package.json @@ -6,6 +6,17 @@ "phaser": "^3.54.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/arcade_2d/tsconfig.json b/src/bundles/arcade_2d/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/arcade_2d/tsconfig.json +++ b/src/bundles/arcade_2d/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/binary_tree/package.json b/src/bundles/binary_tree/package.json index 6bbe15ebd3..9b85570248 100644 --- a/src/bundles/binary_tree/package.json +++ b/src/bundles/binary_tree/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/binary_tree/tsconfig.json b/src/bundles/binary_tree/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/binary_tree/tsconfig.json +++ b/src/bundles/binary_tree/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/communication/package.json b/src/bundles/communication/package.json index 86299b6693..79384b7970 100644 --- a/src/bundles/communication/package.json +++ b/src/bundles/communication/package.json @@ -7,6 +7,17 @@ "uniqid": "^5.4.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/communication/tsconfig.json b/src/bundles/communication/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/communication/tsconfig.json +++ b/src/bundles/communication/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/copy_gc/package.json b/src/bundles/copy_gc/package.json index 6a7b6128a1..550e61b072 100644 --- a/src/bundles/copy_gc/package.json +++ b/src/bundles/copy_gc/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/copy_gc/tsconfig.json b/src/bundles/copy_gc/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/copy_gc/tsconfig.json +++ b/src/bundles/copy_gc/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/csg/package.json b/src/bundles/csg/package.json index cd4a7271ee..3950b90237 100644 --- a/src/bundles/csg/package.json +++ b/src/bundles/csg/package.json @@ -7,7 +7,18 @@ "@jscad/regl-renderer": "^2.6.1", "@jscad/stl-serializer": "2.1.11" }, + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/csg/src/functions.ts b/src/bundles/csg/src/functions.ts index 25ed861d7d..cdfe03032d 100644 --- a/src/bundles/csg/src/functions.ts +++ b/src/bundles/csg/src/functions.ts @@ -12,6 +12,7 @@ import { } from '@jscad/modeling/src/operations/booleans'; import { extrudeLinear } from '@jscad/modeling/src/operations/extrusions'; import { serialize } from '@jscad/stl-serializer'; +import { degreesToRadians, hexToColor } from '@sourceacademy/modules-lib/utilities'; import { head, list, @@ -20,7 +21,6 @@ import { is_list } from 'js-slang/dist/stdlib/list'; import save from 'save-file'; -import { degreesToRadians, hexToColor } from '../../common/utilities'; import { Core } from './core'; import type { Solid } from './jscad/types'; import { diff --git a/src/bundles/csg/src/jscad/renderer.ts b/src/bundles/csg/src/jscad/renderer.ts index d82a6505f8..cbd6b0b40e 100644 --- a/src/bundles/csg/src/jscad/renderer.ts +++ b/src/bundles/csg/src/jscad/renderer.ts @@ -7,7 +7,7 @@ import { entitiesFromSolids, prepareRender } from '@jscad/regl-renderer'; -import { ACE_GUTTER_BACKGROUND_COLOR, ACE_GUTTER_TEXT_COLOR, BP_TEXT_COLOR } from '../../../tabs/common/css_constants'; +import { ACE_GUTTER_BACKGROUND_COLOR, ACE_GUTTER_TEXT_COLOR, BP_TEXT_COLOR } from '@sourceacademy/modules-lib/tabs/css_constants'; import { DEFAULT_COLOR, GRID_PADDING, diff --git a/src/bundles/csg/src/utilities.ts b/src/bundles/csg/src/utilities.ts index 2772420285..6cf71448ec 100644 --- a/src/bundles/csg/src/utilities.ts +++ b/src/bundles/csg/src/utilities.ts @@ -9,8 +9,8 @@ import { scale as _scale, translate as _translate } from '@jscad/modeling/src/operations/transforms'; -import { hexToColor } from '../../common/utilities'; -import type { ReplResult } from '../../typings/type_helpers'; +import type { ReplResult } from '@sourceacademy/modules-lib/types'; +import { hexToColor } from '@sourceacademy/modules-lib/utilities'; import { Core } from './core'; import type { AlphaColor, Color, Solid } from './jscad/types'; diff --git a/src/bundles/csg/tsconfig.json b/src/bundles/csg/tsconfig.json index 8bd691a428..046a8704e3 100644 --- a/src/bundles/csg/tsconfig.json +++ b/src/bundles/csg/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" - ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } + "compilerOptions": { + "outDir": "./dist" + }, + "extends": "../tsconfig.json", + "include": [ + "./src" ] } \ No newline at end of file diff --git a/src/bundles/curve/manifest.json b/src/bundles/curve/manifest.json index 2f37dc5b09..e195095ce0 100644 --- a/src/bundles/curve/manifest.json +++ b/src/bundles/curve/manifest.json @@ -1,3 +1,4 @@ { - "name": "test" + "name": "curve", + "tabs": ["Curve"] } \ No newline at end of file diff --git a/src/bundles/curve/package.json b/src/bundles/curve/package.json index 1a9ee2ba62..e936904de1 100644 --- a/src/bundles/curve/package.json +++ b/src/bundles/curve/package.json @@ -3,9 +3,21 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "gl-matrix": "^3.3.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/curve/src/curves_webgl.ts b/src/bundles/curve/src/curves_webgl.ts index d581db9356..d813843fb1 100644 --- a/src/bundles/curve/src/curves_webgl.ts +++ b/src/bundles/curve/src/curves_webgl.ts @@ -1,7 +1,7 @@ +import type { ReplResult } from '@sourceacademy/modules-lib/types'; import { mat4, vec3 } from 'gl-matrix'; import { stringify } from 'js-slang/dist/utils/stringify'; -import type { ReplResult } from '../../typings/type_helpers'; import type { CurveSpace, DrawMode, ScaleMode } from './types'; /** @hidden */ diff --git a/src/bundles/curve/src/types.ts b/src/bundles/curve/src/types.ts index a1a55d5d19..265d28a4dd 100644 --- a/src/bundles/curve/src/types.ts +++ b/src/bundles/curve/src/types.ts @@ -1,5 +1,4 @@ -import { glAnimation, type AnimFrame } from '../../typings/anim_types'; -import type { ReplResult } from '../../typings/type_helpers'; +import { glAnimation, type AnimFrame, type ReplResult } from '@sourceacademy/modules-lib/types'; import type { Curve, CurveDrawn } from './curves_webgl'; export type CurveModuleState = { diff --git a/src/bundles/curve/tsconfig.json b/src/bundles/curve/tsconfig.json index c0e5b1b93b..1abc1ef424 100644 --- a/src/bundles/curve/tsconfig.json +++ b/src/bundles/curve/tsconfig.json @@ -1,8 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/game/package.json b/src/bundles/game/package.json index 44b147c64c..4c58b1bb71 100644 --- a/src/bundles/game/package.json +++ b/src/bundles/game/package.json @@ -7,6 +7,17 @@ "phaser": "^3.54.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/game/tsconfig.json b/src/bundles/game/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/game/tsconfig.json +++ b/src/bundles/game/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/mark_sweep/package.json b/src/bundles/mark_sweep/package.json index 4c944440a5..e2a2677532 100644 --- a/src/bundles/mark_sweep/package.json +++ b/src/bundles/mark_sweep/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/mark_sweep/tsconfig.json b/src/bundles/mark_sweep/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/mark_sweep/tsconfig.json +++ b/src/bundles/mark_sweep/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/nbody/package.json b/src/bundles/nbody/package.json index 51c14acf45..29918a36a6 100644 --- a/src/bundles/nbody/package.json +++ b/src/bundles/nbody/package.json @@ -6,6 +6,17 @@ "nbody": "^0.2.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/nbody/tsconfig.json b/src/bundles/nbody/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/nbody/tsconfig.json +++ b/src/bundles/nbody/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/painter/package.json b/src/bundles/painter/package.json index 424f602a0e..1bbeae9da0 100644 --- a/src/bundles/painter/package.json +++ b/src/bundles/painter/package.json @@ -6,7 +6,18 @@ "plotly.js-dist": "^2.17.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/plotly.js": "^2.35.4", - "@sourceacademy/module-buildtools": "workspace:^" + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/painter/src/painter.ts b/src/bundles/painter/src/painter.ts index 740e4518c3..9f92282736 100644 --- a/src/bundles/painter/src/painter.ts +++ b/src/bundles/painter/src/painter.ts @@ -1,5 +1,5 @@ +import type { ReplResult } from '@sourceacademy/modules-lib/types'; import type { Data, Layout } from 'plotly.js-dist'; -import type { ReplResult } from '../../typings/type_helpers'; export class LinePlot implements ReplResult { plotlyDrawFn: any; diff --git a/src/bundles/painter/tsconfig.json b/src/bundles/painter/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/painter/tsconfig.json +++ b/src/bundles/painter/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/physics_2d/package.json b/src/bundles/physics_2d/package.json index e2bc91c745..16f1d16a95 100644 --- a/src/bundles/physics_2d/package.json +++ b/src/bundles/physics_2d/package.json @@ -6,6 +6,17 @@ "@box2d/core": "^0.10.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/physics_2d/tsconfig.json b/src/bundles/physics_2d/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/physics_2d/tsconfig.json +++ b/src/bundles/physics_2d/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/pix_n_flix/package.json b/src/bundles/pix_n_flix/package.json index 0166daafe9..9a9bea1bfb 100644 --- a/src/bundles/pix_n_flix/package.json +++ b/src/bundles/pix_n_flix/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/pix_n_flix/tsconfig.json b/src/bundles/pix_n_flix/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/pix_n_flix/tsconfig.json +++ b/src/bundles/pix_n_flix/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/plotly/package.json b/src/bundles/plotly/package.json index 422b7b5120..4e45aa28c4 100644 --- a/src/bundles/plotly/package.json +++ b/src/bundles/plotly/package.json @@ -3,10 +3,22 @@ "version": "1.0.0", "private": true, "dependencies": { - "js-slang": "^1.0.81", - "sound": "workspace:^" + "@sourceacademy/bundle-sound": "workspace:^", + "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "@types/plotly.js": "^2.35.4", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/plotly/src/functions.ts b/src/bundles/plotly/src/functions.ts index afcceb5e0f..a254a20945 100644 --- a/src/bundles/plotly/src/functions.ts +++ b/src/bundles/plotly/src/functions.ts @@ -3,9 +3,9 @@ * @module plotly */ +import type { Sound } from '@sourceacademy/bundle-sound/types'; import context from 'js-slang/context'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; -import type { Sound } from '../sound/types'; import { generatePlot } from './curve_functions'; import { type Curve, diff --git a/src/bundles/plotly/src/plotly.ts b/src/bundles/plotly/src/plotly.ts index fe80da643c..f31ee531a0 100644 --- a/src/bundles/plotly/src/plotly.ts +++ b/src/bundles/plotly/src/plotly.ts @@ -1,6 +1,6 @@ +import type { ReplResult } from '@sourceacademy/modules-lib/types'; import type { Pair } from 'js-slang/dist/stdlib/list'; import type { Data, Layout } from 'plotly.js-dist'; -import type { ReplResult } from '../../typings/type_helpers'; /** * Represents plots with a draw method attached diff --git a/src/bundles/plotly/src/sound_functions.ts b/src/bundles/plotly/src/sound_functions.ts index 9d46078f65..385cc201ce 100644 --- a/src/bundles/plotly/src/sound_functions.ts +++ b/src/bundles/plotly/src/sound_functions.ts @@ -1,9 +1,9 @@ +import type { Sound, Wave } from '@sourceacademy/bundle-sound/types'; import { head, tail, is_pair } from 'js-slang/dist/stdlib/list'; -import type { Sound, Wave } from '../sound/types'; export function is_sound(x: any): x is Sound { return ( is_pair(x) diff --git a/src/bundles/plotly/tsconfig.json b/src/bundles/plotly/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/plotly/tsconfig.json +++ b/src/bundles/plotly/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/remote_execution/package.json b/src/bundles/remote_execution/package.json index 1792f0f664..4d906227ee 100644 --- a/src/bundles/remote_execution/package.json +++ b/src/bundles/remote_execution/package.json @@ -6,6 +6,17 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/remote_execution/tsconfig.json b/src/bundles/remote_execution/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/remote_execution/tsconfig.json +++ b/src/bundles/remote_execution/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/repeat/package.json b/src/bundles/repeat/package.json index c2e4afd842..6a21f98c35 100644 --- a/src/bundles/repeat/package.json +++ b/src/bundles/repeat/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/repeat/tsconfig.json b/src/bundles/repeat/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/repeat/tsconfig.json +++ b/src/bundles/repeat/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/repl/package.json b/src/bundles/repl/package.json index 9567127d32..2f134582c8 100644 --- a/src/bundles/repl/package.json +++ b/src/bundles/repl/package.json @@ -6,6 +6,17 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/repl/tsconfig.json b/src/bundles/repl/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/repl/tsconfig.json +++ b/src/bundles/repl/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/robot_simulation/package.json b/src/bundles/robot_simulation/package.json index 4dbe2e5ff3..1a23a85679 100644 --- a/src/bundles/robot_simulation/package.json +++ b/src/bundles/robot_simulation/package.json @@ -6,7 +6,18 @@ "three": "^0.175.0" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/three": "^0.175.0", - "@sourceacademy/module-buildtools": "workspace:^" + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/robot_simulation/src/controllers/program/Program.ts b/src/bundles/robot_simulation/src/controllers/program/Program.ts index c3d9f3d964..4734c8f9cd 100644 --- a/src/bundles/robot_simulation/src/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/src/controllers/program/Program.ts @@ -1,6 +1,6 @@ +import type { DeepPartial } from '@sourceacademy/modules-lib/types'; import type { IOptions } from 'js-slang'; import context from 'js-slang/context'; -import type { DeepPartial } from '../../../../common/deepPartial'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; import type { Controller } from '../../engine/Core/Controller'; import type { PhysicsTimingInfo } from '../../engine/Physics'; diff --git a/src/bundles/robot_simulation/src/controllers/utils/mergeConfig.ts b/src/bundles/robot_simulation/src/controllers/utils/mergeConfig.ts index 530b751f4e..268a7fef8d 100644 --- a/src/bundles/robot_simulation/src/controllers/utils/mergeConfig.ts +++ b/src/bundles/robot_simulation/src/controllers/utils/mergeConfig.ts @@ -1,5 +1,5 @@ +import type { DeepPartial } from '@sourceacademy/modules-lib/types'; import * as _ from 'lodash'; -import type { DeepPartial } from '../../../../common/deepPartial'; export const mergeConfig = (defaultConfig:T, userConfig?: DeepPartial) :T => { if (userConfig === undefined) { diff --git a/src/bundles/robot_simulation/src/engine/Render/debug/DebugArrow.ts b/src/bundles/robot_simulation/src/engine/Render/debug/DebugArrow.ts index eb07291fcf..2cc1b76c2b 100644 --- a/src/bundles/robot_simulation/src/engine/Render/debug/DebugArrow.ts +++ b/src/bundles/robot_simulation/src/engine/Render/debug/DebugArrow.ts @@ -1,6 +1,6 @@ +import type { DeepPartial } from '@sourceacademy/modules-lib/types'; import * as THREE from 'three'; -import type { DeepPartial } from '../../../../../common/deepPartial'; import { mergeConfig, } from '../../../controllers/utils/mergeConfig'; diff --git a/src/bundles/robot_simulation/src/helper_functions.ts b/src/bundles/robot_simulation/src/helper_functions.ts index 474d5cdd80..f5b6193176 100644 --- a/src/bundles/robot_simulation/src/helper_functions.ts +++ b/src/bundles/robot_simulation/src/helper_functions.ts @@ -1,5 +1,5 @@ +import { interrupt } from '@sourceacademy/modules-lib/specialErrors'; import context from 'js-slang/context'; -import { interrupt } from '../../common/specialErrors'; import { sceneConfig } from './config'; import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; import { Paper, type PaperConfig } from './controllers/environment/Paper'; diff --git a/src/bundles/robot_simulation/tsconfig.json b/src/bundles/robot_simulation/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/robot_simulation/tsconfig.json +++ b/src/bundles/robot_simulation/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/rune/package.json b/src/bundles/rune/package.json index 72b91b0fcb..bac1c8afd0 100644 --- a/src/bundles/rune/package.json +++ b/src/bundles/rune/package.json @@ -3,9 +3,21 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "gl-matrix": "^3.3.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/rune/src/rune.ts b/src/bundles/rune/src/rune.ts index 2a2f052ebe..76afcb6be4 100644 --- a/src/bundles/rune/src/rune.ts +++ b/src/bundles/rune/src/rune.ts @@ -1,6 +1,5 @@ +import { type AnimFrame, type ReplResult, glAnimation } from '@sourceacademy/modules-lib/types'; import { mat4 } from 'gl-matrix'; -import { type AnimFrame, glAnimation } from '../../typings/anim_types'; -import type { ReplResult } from '../../typings/type_helpers'; import { classDeclaration } from '../../typings/type_map'; import { getWebGlFromCanvas, initShaderProgram } from './runes_webgl'; diff --git a/src/bundles/rune/src/runes_ops.ts b/src/bundles/rune/src/runes_ops.ts index 65c8747580..4e5c935629 100644 --- a/src/bundles/rune/src/runes_ops.ts +++ b/src/bundles/rune/src/runes_ops.ts @@ -1,7 +1,7 @@ /** * This file contains the bundle's private functions for runes. */ -import { hexToColor as hexToColorUtil } from '../../common/utilities'; +import { hexToColor as hexToColorUtil } from '@sourceacademy/modules-lib/utilities'; import { Rune } from './rune'; // ============================================================================= diff --git a/src/bundles/rune/tsconfig.json b/src/bundles/rune/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/rune/tsconfig.json +++ b/src/bundles/rune/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/rune_in_words/package.json b/src/bundles/rune_in_words/package.json index c517f50b4e..1729cfe00c 100644 --- a/src/bundles/rune_in_words/package.json +++ b/src/bundles/rune_in_words/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/rune_in_words/tsconfig.json b/src/bundles/rune_in_words/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/rune_in_words/tsconfig.json +++ b/src/bundles/rune_in_words/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/scrabble/package.json b/src/bundles/scrabble/package.json index 908f788f1c..c2e57e2321 100644 --- a/src/bundles/scrabble/package.json +++ b/src/bundles/scrabble/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/scrabble/tsconfig.json b/src/bundles/scrabble/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/scrabble/tsconfig.json +++ b/src/bundles/scrabble/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/sound/package.json b/src/bundles/sound/package.json index fa27677c73..2f97adeffb 100644 --- a/src/bundles/sound/package.json +++ b/src/bundles/sound/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/sound/tsconfig.json b/src/bundles/sound/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/sound/tsconfig.json +++ b/src/bundles/sound/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/sound_matrix/package.json b/src/bundles/sound_matrix/package.json index 5f1ab7936e..3c8b680e81 100644 --- a/src/bundles/sound_matrix/package.json +++ b/src/bundles/sound_matrix/package.json @@ -6,6 +6,17 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/sound_matrix/tsconfig.json b/src/bundles/sound_matrix/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/sound_matrix/tsconfig.json +++ b/src/bundles/sound_matrix/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/stereo_sound/package.json b/src/bundles/stereo_sound/package.json index 70f0adbc05..f176e93322 100644 --- a/src/bundles/stereo_sound/package.json +++ b/src/bundles/stereo_sound/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/stereo_sound/tsconfig.json b/src/bundles/stereo_sound/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/stereo_sound/tsconfig.json +++ b/src/bundles/stereo_sound/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/tsconfig.json b/src/bundles/tsconfig.json index 48e69311c2..61e8238399 100644 --- a/src/bundles/tsconfig.json +++ b/src/bundles/tsconfig.json @@ -1,7 +1,10 @@ +// bundles tsconfig { "compilerOptions": { + "emitDeclarationOnly": true, + "declaration": true, "paths": { - "js-slang/context": ["../../buildtools/src/types/js-slang/context.d.ts"] + "js-slang/context": ["../modules-lib/src/types/js-slang/context.d.ts"] } }, "extends": ["../tsconfig.json"] diff --git a/src/bundles/unittest/package.json b/src/bundles/unittest/package.json index 0efda0f4fa..f8b522095a 100644 --- a/src/bundles/unittest/package.json +++ b/src/bundles/unittest/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/unittest/tsconfig.json b/src/bundles/unittest/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/unittest/tsconfig.json +++ b/src/bundles/unittest/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/bundles/unity_academy/package.json b/src/bundles/unity_academy/package.json index 1369cb0a6c..6863a249c8 100644 --- a/src/bundles/unity_academy/package.json +++ b/src/bundles/unity_academy/package.json @@ -8,7 +8,18 @@ "react": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.2", - "@sourceacademy/module-buildtools": "workspace:^" + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/unity_academy/tsconfig.json b/src/bundles/unity_academy/tsconfig.json index 8bd691a428..4a068b604e 100644 --- a/src/bundles/unity_academy/tsconfig.json +++ b/src/bundles/unity_academy/tsconfig.json @@ -1,13 +1,10 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist", + "jsx": "react-jsx" + } } \ No newline at end of file diff --git a/src/bundles/wasm/package.json b/src/bundles/wasm/package.json index ee8dd52c42..0056f9c779 100644 --- a/src/bundles/wasm/package.json +++ b/src/bundles/wasm/package.json @@ -3,6 +3,17 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^" + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + "type": "module", + "exports": { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + }, + "scripts": { + "tsc": "tsc --project ./tsconfig.json", + "build": "buildtools build-bundle ." } } \ No newline at end of file diff --git a/src/bundles/wasm/tsconfig.json b/src/bundles/wasm/tsconfig.json index 8bd691a428..1abc1ef424 100644 --- a/src/bundles/wasm/tsconfig.json +++ b/src/bundles/wasm/tsconfig.json @@ -1,13 +1,9 @@ { - "extends": [ - "../tsconfig.json" + "extends": "../tsconfig.json", + "include": [ + "./src" ], - "includes": [ - "./src/*" - ], - "references": [ - { - "path": "../../commons/tsconfig.json" - } - ] + "compilerOptions": { + "outDir": "./dist" + } } \ No newline at end of file diff --git a/src/tabs/ArcadeTwod/index.tsx b/src/tabs/ArcadeTwod/index.tsx index e1fb56a21b..f8b570cedb 100644 --- a/src/tabs/ArcadeTwod/index.tsx +++ b/src/tabs/ArcadeTwod/index.tsx @@ -1,8 +1,8 @@ import { Button, ButtonGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import Phaser from 'phaser'; import React from 'react'; -import type { DebuggerContext } from '../../typings/type_helpers'; /** * Game display tab for user-created games made with the Arcade2D module. diff --git a/src/tabs/ArcadeTwod/package.json b/src/tabs/ArcadeTwod/package.json index 4228106946..a06567ab34 100644 --- a/src/tabs/ArcadeTwod/package.json +++ b/src/tabs/ArcadeTwod/package.json @@ -1,8 +1,10 @@ { - "name": "ArcadeTwod", + "name": "@sourceacademy/tab-ArcadeTwod", "version": "1.0.0", "private": true, "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/ArcadeTwod/tsconfig.json b/src/tabs/ArcadeTwod/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/ArcadeTwod/tsconfig.json +++ b/src/tabs/ArcadeTwod/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/AugmentedReality/package.json b/src/tabs/AugmentedReality/package.json index 5cafaf29bb..63baff080a 100644 --- a/src/tabs/AugmentedReality/package.json +++ b/src/tabs/AugmentedReality/package.json @@ -1,11 +1,14 @@ { - "name": "AugmentedReality", + "name": "@sourceacademy/tab-AugmentedReality", "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "@sourceacademy/modules-lib": "workspace:^", + "react": "^18.3.1", + "saar": "^1.0.4" }, "devDependencies": { + "@sourceacademy/bundle-ar": "workspace:^", "@types/react": "^18.3.1" } } diff --git a/src/tabs/AugmentedReality/AugmentedContent.tsx b/src/tabs/AugmentedReality/src/AugmentedContent.tsx similarity index 97% rename from src/tabs/AugmentedReality/AugmentedContent.tsx rename to src/tabs/AugmentedReality/src/AugmentedContent.tsx index 8893a21d5c..8976b8862c 100644 --- a/src/tabs/AugmentedReality/AugmentedContent.tsx +++ b/src/tabs/AugmentedReality/src/AugmentedContent.tsx @@ -7,8 +7,8 @@ import { getModuleState, type ARState, setFrontObject, -} from '../../bundles/ar/AR'; -import type { OverlayHelper } from '../../bundles/ar/OverlayHelper'; +} from '@sourceacademy/bundle-ar/AR'; +import type { OverlayHelper } from '@sourceacademy/bundle-ar/OverlayHelper'; /** * Content to be shown on screen. diff --git a/src/tabs/AugmentedReality/AugmentedLayer.tsx b/src/tabs/AugmentedReality/src/AugmentedLayer.tsx similarity index 89% rename from src/tabs/AugmentedReality/AugmentedLayer.tsx rename to src/tabs/AugmentedReality/src/AugmentedLayer.tsx index 72fe2625e1..5b301f3c45 100644 --- a/src/tabs/AugmentedReality/AugmentedLayer.tsx +++ b/src/tabs/AugmentedReality/src/AugmentedLayer.tsx @@ -1,6 +1,6 @@ import { PlayAreaContext } from 'saar/libraries/calibration_library/PlayAreaContext'; import { ControlsContext } from 'saar/libraries/controls_library/ControlsContext'; -import type { ARState } from '../../bundles/ar/AR'; +import type { ARState } from '@sourceacademy/bundle-ar/AR'; import { AugmentedContent } from './AugmentedContent'; /** diff --git a/src/tabs/AugmentedReality/Overlay.tsx b/src/tabs/AugmentedReality/src/Overlay.tsx similarity index 100% rename from src/tabs/AugmentedReality/Overlay.tsx rename to src/tabs/AugmentedReality/src/Overlay.tsx diff --git a/src/tabs/AugmentedReality/StartButton.tsx b/src/tabs/AugmentedReality/src/StartButton.tsx similarity index 93% rename from src/tabs/AugmentedReality/StartButton.tsx rename to src/tabs/AugmentedReality/src/StartButton.tsx index a902f31a3c..20a7afe8d6 100644 --- a/src/tabs/AugmentedReality/StartButton.tsx +++ b/src/tabs/AugmentedReality/src/StartButton.tsx @@ -1,7 +1,7 @@ +import type { ARState } from '@sourceacademy/bundle-ar/AR'; import { useEffect } from 'react'; import { ARButton } from 'saar/libraries/misc'; import { useScreenState } from 'saar/libraries/screen_state_library/ScreenStateContext'; -import type { ARState } from '../../bundles/ar/AR'; import { AugmentedLayer } from './AugmentedLayer'; import { Overlay } from './Overlay'; diff --git a/src/tabs/AugmentedReality/index.tsx b/src/tabs/AugmentedReality/src/index.tsx similarity index 96% rename from src/tabs/AugmentedReality/index.tsx rename to src/tabs/AugmentedReality/src/index.tsx index e699e4f110..e93fef839b 100644 --- a/src/tabs/AugmentedReality/index.tsx +++ b/src/tabs/AugmentedReality/src/index.tsx @@ -1,6 +1,6 @@ +import { getModuleState } from '@sourceacademy/bundle-ar/AR'; import React from 'react'; import { ScreenStateContext } from 'saar/libraries/screen_state_library/ScreenStateContext'; -import { getModuleState } from '../../bundles/ar/AR'; import { StartButton } from './StartButton'; /** diff --git a/src/tabs/AugmentedReality/tsconfig.json b/src/tabs/AugmentedReality/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/AugmentedReality/tsconfig.json +++ b/src/tabs/AugmentedReality/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/CopyGc/package.json b/src/tabs/CopyGc/package.json index 9637b9490d..6940891c5a 100644 --- a/src/tabs/CopyGc/package.json +++ b/src/tabs/CopyGc/package.json @@ -1,5 +1,5 @@ { - "name": "CopyGc", + "name": "@sourceacademy/tab-CopyGc", "version": "1.0.0", "private": true, "dependencies": { diff --git a/src/tabs/CopyGc/index.tsx b/src/tabs/CopyGc/src/index.tsx similarity index 99% rename from src/tabs/CopyGc/index.tsx rename to src/tabs/CopyGc/src/index.tsx index 2120cac658..39e13d0e81 100644 --- a/src/tabs/CopyGc/index.tsx +++ b/src/tabs/CopyGc/src/index.tsx @@ -1,6 +1,6 @@ import { Slider, Icon } from '@blueprintjs/core'; +import { COMMAND } from '@sourceacademy/bundle-copy_gc/types'; import React from 'react'; -import { COMMAND } from '../../bundles/copy_gc/types'; import { ThemeColor } from './style'; type Props = { diff --git a/src/tabs/CopyGc/style.tsx b/src/tabs/CopyGc/src/style.tsx similarity index 100% rename from src/tabs/CopyGc/style.tsx rename to src/tabs/CopyGc/src/style.tsx diff --git a/src/tabs/CopyGc/tsconfig.json b/src/tabs/CopyGc/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/CopyGc/tsconfig.json +++ b/src/tabs/CopyGc/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Csg/package.json b/src/tabs/Csg/package.json index e12180d51c..f36f49d53e 100644 --- a/src/tabs/Csg/package.json +++ b/src/tabs/Csg/package.json @@ -1,8 +1,12 @@ { - "name": "Csg", + "name": "@sourceacademy/tab-Csg", "version": "1.0.0", "private": true, "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", + "@sourceacademy/bundle-csg": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Csg/canvas_holder.tsx b/src/tabs/Csg/src/canvas_holder.tsx similarity index 92% rename from src/tabs/Csg/canvas_holder.tsx rename to src/tabs/Csg/src/canvas_holder.tsx index 54a5fa779f..473b12e92e 100644 --- a/src/tabs/Csg/canvas_holder.tsx +++ b/src/tabs/Csg/src/canvas_holder.tsx @@ -1,13 +1,13 @@ /* [Imports] */ import { Spinner, SpinnerSize } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import { Core } from '@sourceacademy/bundle-csg/core'; +import StatefulRenderer from '@sourceacademy/bundle-csg/stateful_renderer'; +import type { RenderGroup } from '@sourceacademy/bundle-csg/utilities'; +import { BP_CARD_BORDER_RADIUS, BP_TAB_BUTTON_MARGIN, BP_TAB_PANEL_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; import React from 'react'; -import { Core } from '../../bundles/csg/core'; -import StatefulRenderer from '../../bundles/csg/stateful_renderer'; -import type { RenderGroup } from '../../bundles/csg/utilities'; -import { BP_CARD_BORDER_RADIUS, BP_TAB_BUTTON_MARGIN, BP_TAB_PANEL_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; +import type { CanvasHolderProps, CanvasHolderState } from '../types'; import HoverControlHint from './hover_control_hint'; -import type { CanvasHolderProps, CanvasHolderState } from './types'; /* [Main] */ export default class CanvasHolder extends React.Component< diff --git a/src/tabs/Csg/hover_control_hint.tsx b/src/tabs/Csg/src/hover_control_hint.tsx similarity index 88% rename from src/tabs/Csg/hover_control_hint.tsx rename to src/tabs/Csg/src/hover_control_hint.tsx index 5fdf3c04b7..bd5fb3d7d2 100644 --- a/src/tabs/Csg/hover_control_hint.tsx +++ b/src/tabs/Csg/src/hover_control_hint.tsx @@ -1,8 +1,8 @@ /* [Imports] */ import { Icon, Tooltip } from '@blueprintjs/core'; +import { BP_ICON_COLOR, SA_TAB_BUTTON_WIDTH, SA_TAB_ICON_SIZE } from '@sourceacademy/modules-lib/tabs/css_constants'; import React from 'react'; -import { BP_ICON_COLOR, SA_TAB_BUTTON_WIDTH, SA_TAB_ICON_SIZE } from '../common/css_constants'; -import type { HintProps } from './types'; +import type { HintProps } from '../types'; /* [Main] */ export default class HoverControlHint extends React.Component { diff --git a/src/tabs/Csg/index.tsx b/src/tabs/Csg/src/index.tsx similarity index 82% rename from src/tabs/Csg/index.tsx rename to src/tabs/Csg/src/index.tsx index fc29ca39f4..f196dc3226 100644 --- a/src/tabs/Csg/index.tsx +++ b/src/tabs/Csg/src/index.tsx @@ -1,9 +1,9 @@ /* [Imports] */ import { IconNames } from '@blueprintjs/icons'; +import { Core } from '@sourceacademy/bundle-csg/Core'; +import type { CsgModuleState } from '@sourceacademy/bundle-csg/utilities'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import type { ReactElement } from 'react'; -import { Core } from '../../bundles/csg/core'; -import type { CsgModuleState } from '../../bundles/csg/utilities'; -import type { DebuggerContext } from '../../typings/type_helpers'; import CanvasHolder from './canvas_holder'; /* [Exports] */ diff --git a/src/tabs/Csg/tsconfig.json b/src/tabs/Csg/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/Csg/tsconfig.json +++ b/src/tabs/Csg/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Curve/package.json b/src/tabs/Curve/package.json index 3f4b7bd176..6a71810e7c 100644 --- a/src/tabs/Curve/package.json +++ b/src/tabs/Curve/package.json @@ -1,8 +1,12 @@ { - "name": "Curve", + "name": "@sourceacademy/tab-Curve", "version": "1.0.0", "private": true, "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", + "@sourceacademy/bundle-curve": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Curve/__tests__/Curve.tsx b/src/tabs/Curve/src/__tests__/Curve.tsx similarity index 100% rename from src/tabs/Curve/__tests__/Curve.tsx rename to src/tabs/Curve/src/__tests__/Curve.tsx diff --git a/src/tabs/Curve/__tests__/__snapshots__/Curve.tsx.snap b/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap similarity index 100% rename from src/tabs/Curve/__tests__/__snapshots__/Curve.tsx.snap rename to src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap diff --git a/src/tabs/Curve/animation_canvas_3d_curve.tsx b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx similarity index 95% rename from src/tabs/Curve/animation_canvas_3d_curve.tsx rename to src/tabs/Curve/src/animation_canvas_3d_curve.tsx index 62abd2e752..e6e12b47d5 100644 --- a/src/tabs/Curve/animation_canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx @@ -1,12 +1,12 @@ import { Icon, Slider, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import React from 'react'; -import type { AnimatedCurve } from '../../bundles/curve/types'; -import AutoLoopSwitch from '../common/AutoLoopSwitch'; -import ButtonComponent from '../common/ButtonComponent'; -import PlayButton from '../common/PlayButton'; -import WebGLCanvas from '../common/WebglCanvas'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; +import type { AnimatedCurve } from '@sourceacademy/bundle-curve/types'; +import AutoLoopSwitch from '@sourceacademy/modules-lib/tabs/AutoLoopSwitch'; +import ButtonComponent from '@sourceacademy/modules-lib/tabs/ButtonComponent'; +import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; type Props = { animation: AnimatedCurve; diff --git a/src/tabs/Curve/canvas_3d_curve.tsx b/src/tabs/Curve/src/canvas_3d_curve.tsx similarity index 92% rename from src/tabs/Curve/canvas_3d_curve.tsx rename to src/tabs/Curve/src/canvas_3d_curve.tsx index 318ace72c1..552134cee5 100644 --- a/src/tabs/Curve/canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/canvas_3d_curve.tsx @@ -1,10 +1,10 @@ import { Slider } from '@blueprintjs/core'; import React from 'react'; -import type { CurveDrawn } from '../../bundles/curve/curves_webgl'; -import { degreesToRadians } from '../../common/utilities'; -import PlayButton from '../common/PlayButton'; -import WebGLCanvas from '../common/WebglCanvas'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants'; +import type { CurveDrawn } from '@sourceacademy/bundle-curve/curves_webgl'; +import { degreesToRadians } from '@sourceacademy/modules-lib/utilities'; +import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; type State = { /** diff --git a/src/tabs/Curve/index.tsx b/src/tabs/Curve/src/index.tsx similarity index 73% rename from src/tabs/Curve/index.tsx rename to src/tabs/Curve/src/index.tsx index d37045dff7..2f2299263e 100644 --- a/src/tabs/Curve/index.tsx +++ b/src/tabs/Curve/src/index.tsx @@ -1,9 +1,9 @@ -import type { CurveModuleState } from '../../bundles/curve/types'; -import { glAnimation } from '../../typings/anim_types'; -import { getModuleState, type DebuggerContext, type ModuleTab } from '../../typings/type_helpers'; -import AnimationCanvas from '../common/AnimationCanvas'; -import MultiItemDisplay from '../common/MultItemDisplay'; -import WebGLCanvas from '../common/WebglCanvas'; +import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; +import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; +import AnimationCanvas from '@sourceacdemy/modules-lib/tabs/AnimationCanvas'; import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; import CurveCanvas3D from './canvas_3d_curve'; diff --git a/src/tabs/Curve/tsconfig.json b/src/tabs/Curve/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/Curve/tsconfig.json +++ b/src/tabs/Curve/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Game/package.json b/src/tabs/Game/package.json index a07d98ee13..7de319fa56 100644 --- a/src/tabs/Game/package.json +++ b/src/tabs/Game/package.json @@ -1,5 +1,5 @@ { - "name": "Game", + "name": "@sourceacademy/tab-Game", "version": "1.0.0", "private": true, "dependencies": { diff --git a/src/tabs/Game/constants.ts b/src/tabs/Game/src/constants.ts similarity index 100% rename from src/tabs/Game/constants.ts rename to src/tabs/Game/src/constants.ts diff --git a/src/tabs/Game/index.tsx b/src/tabs/Game/src/index.tsx similarity index 100% rename from src/tabs/Game/index.tsx rename to src/tabs/Game/src/index.tsx diff --git a/src/tabs/Game/tsconfig.json b/src/tabs/Game/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/Game/tsconfig.json +++ b/src/tabs/Game/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/MarkSweep/package.json b/src/tabs/MarkSweep/package.json index 3c05c3b275..57f4249335 100644 --- a/src/tabs/MarkSweep/package.json +++ b/src/tabs/MarkSweep/package.json @@ -1,5 +1,5 @@ { - "name": "MarkSweep", + "name": "@sourceacademy/tab-MarkSweep", "version": "1.0.0", "private": true, "dependencies": { diff --git a/src/tabs/MarkSweep/index.tsx b/src/tabs/MarkSweep/src/index.tsx similarity index 100% rename from src/tabs/MarkSweep/index.tsx rename to src/tabs/MarkSweep/src/index.tsx diff --git a/src/tabs/MarkSweep/style.tsx b/src/tabs/MarkSweep/src/style.tsx similarity index 100% rename from src/tabs/MarkSweep/style.tsx rename to src/tabs/MarkSweep/src/style.tsx diff --git a/src/tabs/MarkSweep/tsconfig.json b/src/tabs/MarkSweep/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/MarkSweep/tsconfig.json +++ b/src/tabs/MarkSweep/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Nbody/package.json b/src/tabs/Nbody/package.json index 128773ff56..3b8811deb5 100644 --- a/src/tabs/Nbody/package.json +++ b/src/tabs/Nbody/package.json @@ -1,8 +1,9 @@ { - "name": "Nbody", + "name": "@sourceacademy/tab-Nbody", "version": "1.0.0", "private": true, "dependencies": { + "nbody": "^0.2.0", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Nbody/tsconfig.json b/src/tabs/Nbody/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Nbody/tsconfig.json +++ b/src/tabs/Nbody/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/Painter/index.tsx b/src/tabs/Painter/index.tsx index 553624816b..2191401087 100644 --- a/src/tabs/Painter/index.tsx +++ b/src/tabs/Painter/index.tsx @@ -1,7 +1,7 @@ +import type { LinePlot } from '@sourceacademy/bundle-painter/painter'; +import Modal from '@sourceacademy/modules-lib/tabs/ModalDiv'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import React from 'react'; -import type { LinePlot } from '../../bundles/painter/painter'; -import type { DebuggerContext } from '../../typings/type_helpers'; -import Modal from '../common/ModalDiv'; type Props = { children?: never diff --git a/src/tabs/Painter/package.json b/src/tabs/Painter/package.json index 26b753d39e..8ebdf42b01 100644 --- a/src/tabs/Painter/package.json +++ b/src/tabs/Painter/package.json @@ -1,8 +1,10 @@ { - "name": "Painter", + "name": "@sourceacademy/tab-Painter", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-painter": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Painter/tsconfig.json b/src/tabs/Painter/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Painter/tsconfig.json +++ b/src/tabs/Painter/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/Physics2D/package.json b/src/tabs/Physics2D/package.json index ba9adda38c..cff1eb3103 100644 --- a/src/tabs/Physics2D/package.json +++ b/src/tabs/Physics2D/package.json @@ -1,8 +1,10 @@ { - "name": "Physics2D", + "name": "@sourceacademy/tab-Physics2D", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-physics_2d": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Physics2D/DebugDrawCanvas.tsx b/src/tabs/Physics2D/src/DebugDrawCanvas.tsx similarity index 97% rename from src/tabs/Physics2D/DebugDrawCanvas.tsx rename to src/tabs/Physics2D/src/DebugDrawCanvas.tsx index 1bcff79845..fdd4308cc8 100644 --- a/src/tabs/Physics2D/DebugDrawCanvas.tsx +++ b/src/tabs/Physics2D/src/DebugDrawCanvas.tsx @@ -2,10 +2,10 @@ import { Button, Icon, Slider, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { DrawShapes, type b2World } from '@box2d/core'; import { DebugDraw } from '@box2d/debug-draw'; -import React from 'react'; -import type { PhysicsWorld } from '../../bundles/physics_2d/PhysicsWorld'; -import WebGLCanvas from '../common/WebglCanvas'; +import type { PhysicsWorld } from '@sourceacademy/bundle-physics_2d/PhysicsWorld'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import React from 'react'; type DebugDrawCanvasProps = { world: PhysicsWorld; diff --git a/src/tabs/Physics2D/index.tsx b/src/tabs/Physics2D/src/index.tsx similarity index 93% rename from src/tabs/Physics2D/index.tsx rename to src/tabs/Physics2D/src/index.tsx index 841724779a..4e7b0c8657 100644 --- a/src/tabs/Physics2D/index.tsx +++ b/src/tabs/Physics2D/src/index.tsx @@ -1,4 +1,4 @@ -import type { DebuggerContext } from '../../typings/type_helpers'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import DebugDrawCanvas from './DebugDrawCanvas'; /** diff --git a/src/tabs/Physics2D/tsconfig.json b/src/tabs/Physics2D/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/Physics2D/tsconfig.json +++ b/src/tabs/Physics2D/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Pixnflix/index.tsx b/src/tabs/Pixnflix/index.tsx index a4a684beeb..4383b9bc04 100644 --- a/src/tabs/Pixnflix/index.tsx +++ b/src/tabs/Pixnflix/index.tsx @@ -1,6 +1,5 @@ import { Button, ButtonGroup, Divider, NumericInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React, { type ChangeEvent, type DragEvent } from 'react'; import { DEFAULT_FPS, DEFAULT_HEIGHT, @@ -12,13 +11,14 @@ import { MIN_FPS, MIN_HEIGHT, MIN_WIDTH -} from '../../bundles/pix_n_flix/constants'; +} from '@sourceacademy/bundle-pix_n_flix/constants'; import { type BundlePacket, type ErrorLogger, InputFeed, type TabsPacket -} from '../../bundles/pix_n_flix/types'; +} from '@sourceacademy/bundle-pix_n_flix/types'; +import React, { type ChangeEvent, type DragEvent } from 'react'; type Props = { children?: never; diff --git a/src/tabs/Pixnflix/package.json b/src/tabs/Pixnflix/package.json index b284a7555f..166bfe5a65 100644 --- a/src/tabs/Pixnflix/package.json +++ b/src/tabs/Pixnflix/package.json @@ -1,8 +1,9 @@ { - "name": "Pixnflix", + "name": "@sourceacademy/tab-Pixnflix", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-pix_n_flix": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Pixnflix/tsconfig.json b/src/tabs/Pixnflix/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Pixnflix/tsconfig.json +++ b/src/tabs/Pixnflix/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/Plotly/index.tsx b/src/tabs/Plotly/index.tsx index cee76d99e1..bd101c1a16 100644 --- a/src/tabs/Plotly/index.tsx +++ b/src/tabs/Plotly/index.tsx @@ -1,7 +1,7 @@ +import type { DrawnPlot } from '@sourceacademy/bundle-plotly/plotly'; +import Modal from '@sourceacademy/modules-lib/tabs/ModalDiv'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import React from 'react'; -import type { DrawnPlot } from '../../bundles/plotly/plotly'; -import type { DebuggerContext } from '../../typings/type_helpers'; -import Modal from '../common/ModalDiv'; type Props = { children?: never diff --git a/src/tabs/Plotly/package.json b/src/tabs/Plotly/package.json index d25c398603..905b488234 100644 --- a/src/tabs/Plotly/package.json +++ b/src/tabs/Plotly/package.json @@ -1,8 +1,10 @@ { - "name": "Plotly", + "name": "@sourceacademy/tab-Plotly", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-plotly": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Plotly/tsconfig.json b/src/tabs/Plotly/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Plotly/tsconfig.json +++ b/src/tabs/Plotly/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/Repeat/package.json b/src/tabs/Repeat/package.json index f8b8b979d2..8b4be1ca07 100644 --- a/src/tabs/Repeat/package.json +++ b/src/tabs/Repeat/package.json @@ -1,5 +1,5 @@ { - "name": "Repeat", + "name": "@sourceacademy/tab-Repeat", "version": "1.0.0", "private": true, "dependencies": { diff --git a/src/tabs/Repeat/tsconfig.json b/src/tabs/Repeat/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Repeat/tsconfig.json +++ b/src/tabs/Repeat/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/Repl/index.tsx b/src/tabs/Repl/index.tsx index 18f004a011..166b6fd732 100644 --- a/src/tabs/Repl/index.tsx +++ b/src/tabs/Repl/index.tsx @@ -6,13 +6,13 @@ import { Button } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; + +import { FONT_MESSAGE, MINIMUM_EDITOR_HEIGHT } from '@sourceacademy/bundle-repl/config'; +import type { ProgrammableRepl } from '@sourceacademy/bundle-repl/programmable_repl'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import React from 'react'; import AceEditor from 'react-ace'; -import { FONT_MESSAGE, MINIMUM_EDITOR_HEIGHT } from '../../bundles/repl/config'; -import type { ProgrammableRepl } from '../../bundles/repl/programmable_repl'; -import type { DebuggerContext } from '../../typings/type_helpers'; - import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/theme-twilight'; import 'ace-builds/src-noconflict/ext-language_tools'; diff --git a/src/tabs/Repl/package.json b/src/tabs/Repl/package.json index 6faf425db8..3fb21e8177 100644 --- a/src/tabs/Repl/package.json +++ b/src/tabs/Repl/package.json @@ -1,9 +1,12 @@ { - "name": "Repl", + "name": "@sourceacademy/tab-Repl", "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "@sourceacademy/modules-lib": "workspace:^", + "ace-builds": "^1.25.1", + "react": "^18.3.1", + "react-ace": "^10.1.0" }, "devDependencies": { "@types/react": "^18.3.1" diff --git a/src/tabs/Repl/tsconfig.json b/src/tabs/Repl/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Repl/tsconfig.json +++ b/src/tabs/Repl/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/RobotSimulation/package.json b/src/tabs/RobotSimulation/package.json index c861b31049..0c5c7df621 100644 --- a/src/tabs/RobotSimulation/package.json +++ b/src/tabs/RobotSimulation/package.json @@ -1,8 +1,10 @@ { - "name": "RobotSimulation", + "name": "@sourceacademy/tab-RobotSimulation", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-robot_simulation": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/src/components/Main.tsx similarity index 96% rename from src/tabs/RobotSimulation/components/Main.tsx rename to src/tabs/RobotSimulation/src/components/Main.tsx index b189817135..0b4eb624d0 100644 --- a/src/tabs/RobotSimulation/components/Main.tsx +++ b/src/tabs/RobotSimulation/src/components/Main.tsx @@ -1,31 +1,31 @@ -import React, { useState } from 'react'; -import type { DebuggerContext } from '../../../typings/type_helpers'; -import { Modal } from './Modal'; -import { SimulationCanvas } from './Simulation'; -import { TabUi } from './TabUi'; - -type MainProps = { - context: DebuggerContext; -}; - -export const Main: React.FC = ({ context }) => { - const [isCanvasShowing, setIsCanvasShowing] = useState(false); - - return ( -
- { - setIsCanvasShowing(true); - }} - /> - { - setIsCanvasShowing(false); - }} - > - - -
- ); -}; +import React, { useState } from 'react'; +import type { DebuggerContext } from '../../../typings/type_helpers'; +import { Modal } from './Modal'; +import { SimulationCanvas } from './Simulation'; +import { TabUi } from './TabUi'; + +type MainProps = { + context: DebuggerContext; +}; + +export const Main: React.FC = ({ context }) => { + const [isCanvasShowing, setIsCanvasShowing] = useState(false); + + return ( +
+ { + setIsCanvasShowing(true); + }} + /> + { + setIsCanvasShowing(false); + }} + > + + +
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/Modal.tsx b/src/tabs/RobotSimulation/src/components/Modal.tsx similarity index 95% rename from src/tabs/RobotSimulation/components/Modal.tsx rename to src/tabs/RobotSimulation/src/components/Modal.tsx index df5b747b87..e3d8453931 100644 --- a/src/tabs/RobotSimulation/components/Modal.tsx +++ b/src/tabs/RobotSimulation/src/components/Modal.tsx @@ -1,62 +1,62 @@ -import React, { type CSSProperties, type ReactNode } from 'react'; - -type ModalProps = { - isOpen: boolean; - onClose: () => void; - children: ReactNode; -}; - -export const containerStyle: CSSProperties = { - width: '100vw', - height: '100vh', - position: 'fixed', - bottom: 0, - top: 0, - left: 0, - right: 0, - zIndex: 20, - isolation: 'isolate', -}; - -export const closeButtonStyle: CSSProperties = { - position: 'fixed', - top: '10px', - right: '10px', - cursor: 'pointer', - fontSize: 24, - color: 'white', -}; - -export const greyedOutBackground: CSSProperties = { - background: 'black', - opacity: '70%', - width: '100%', - height: '100%', - position: 'absolute', - zIndex: -1, -}; - -export const childWrapperStyle: CSSProperties = { - display: 'flex', - width: '100%', - height: '100%', - justifyContent: 'center', - alignItems: 'center', -}; - -export const Modal: React.FC = ({ children, isOpen, onClose }) => { - return ( -
-
- - x - -
{children}
-
- ); -}; +import React, { type CSSProperties, type ReactNode } from 'react'; + +type ModalProps = { + isOpen: boolean; + onClose: () => void; + children: ReactNode; +}; + +export const containerStyle: CSSProperties = { + width: '100vw', + height: '100vh', + position: 'fixed', + bottom: 0, + top: 0, + left: 0, + right: 0, + zIndex: 20, + isolation: 'isolate', +}; + +export const closeButtonStyle: CSSProperties = { + position: 'fixed', + top: '10px', + right: '10px', + cursor: 'pointer', + fontSize: 24, + color: 'white', +}; + +export const greyedOutBackground: CSSProperties = { + background: 'black', + opacity: '70%', + width: '100%', + height: '100%', + position: 'absolute', + zIndex: -1, +}; + +export const childWrapperStyle: CSSProperties = { + display: 'flex', + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', +}; + +export const Modal: React.FC = ({ children, isOpen, onClose }) => { + return ( +
+
+ + x + +
{children}
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/src/components/Simulation/index.tsx similarity index 88% rename from src/tabs/RobotSimulation/components/Simulation/index.tsx rename to src/tabs/RobotSimulation/src/components/Simulation/index.tsx index 98314a5698..dc44c9e883 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/src/components/Simulation/index.tsx @@ -1,120 +1,120 @@ -import { Tab, Tabs } from '@blueprintjs/core'; -import { useRef, type CSSProperties, useEffect, useState } from 'react'; - -import type { DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; -import type { World } from '../../../../bundles/robot_simulation/engine'; -import type { WorldState } from '../../../../bundles/robot_simulation/engine/World'; -import type { DebuggerContext } from '../../../../typings/type_helpers'; - -import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; -import { ConsolePanel } from '../TabPanels/ConsolePanel'; -import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; -import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; -import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; - -const WrapperStyle: CSSProperties = { - display: 'flex', - flexDirection: 'column', - gap: '0.6rem', -}; - -const CanvasStyle: CSSProperties = { - width: 900, - height: 500, - borderRadius: 3, - overflow: 'hidden', - boxShadow: 'inset 0 0 0 1px rgba(255, 255, 255, 0.2)', -}; - -const bottomPanelStyle: CSSProperties = { - width: 900, - height: 200, - backgroundColor: '#1a2530', - borderRadius: 3, - overflow: 'hidden', - boxShadow: 'inset 0 0 0 1px rgba(255, 255, 255, 0.2)', -}; - -type SimulationCanvasProps = { - context: DebuggerContext; - isOpen: boolean; -}; - -export const SimulationCanvas: React.FC = ({ - context, - isOpen, -}) => { - const ref = useRef(null); - const sensorRef = useRef(null); - const [currentState, setCurrentState] = useState('unintialized'); - - // We know this is true because it is checked in RobotSimulation/index.tsx (toSpawn) - const world = context.context.moduleContexts.robot_simulation.state - .world as World; - - // This is not guaranteed to be true. - const ev3 = context.context.moduleContexts.robot_simulation.state.ev3 as - | DefaultEv3 - | undefined; - - const robotConsole = world.robotConsole; - - useEffect(() => { - const startThreeAndRapierEngines = async () => { - setCurrentState(world.state); - }; - - const attachRenderDom = () => { - if (ref.current) { - ref.current.replaceChildren(world.render.getElement()); - } - if (!ev3) { - return; - } - - if (sensorRef.current) { - sensorRef.current.replaceChildren( - ev3.get('colorSensor').renderer.getElement() - ); - } - }; - - if (currentState === 'unintialized') { - startThreeAndRapierEngines(); - } - - if (currentState === 'ready' || currentState === 'running') { - attachRenderDom(); - } - if (currentState === 'loading') { - setTimeout(() => { - setCurrentState('unintialized'); - }, 500); - } - }, [currentState]); - - useEffect(() => { - if (isOpen) { - world.start(); - } else { - world.pause(); - } - }, [isOpen]); - - return ( -
-
-
{currentState}
-
-
- - } /> - } /> - }/> - }/> - } /> - -
-
- ); -}; +import { Tab, Tabs } from '@blueprintjs/core'; + +import type { DefaultEv3 } from '@sourceacademy/bundle-robot_simulation/controllers'; +import type { World } from '@sourceacademy/bundle-robot_simulation/engine'; +import type { WorldState } from '@sourceacademy/bundle-robot_simulation/engine/World'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { useRef, type CSSProperties, useEffect, useState } from 'react'; + +import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; +import { ConsolePanel } from '../TabPanels/ConsolePanel'; +import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; +import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; +import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; + +const WrapperStyle: CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: '0.6rem', +}; + +const CanvasStyle: CSSProperties = { + width: 900, + height: 500, + borderRadius: 3, + overflow: 'hidden', + boxShadow: 'inset 0 0 0 1px rgba(255, 255, 255, 0.2)', +}; + +const bottomPanelStyle: CSSProperties = { + width: 900, + height: 200, + backgroundColor: '#1a2530', + borderRadius: 3, + overflow: 'hidden', + boxShadow: 'inset 0 0 0 1px rgba(255, 255, 255, 0.2)', +}; + +type SimulationCanvasProps = { + context: DebuggerContext; + isOpen: boolean; +}; + +export const SimulationCanvas: React.FC = ({ + context, + isOpen, +}) => { + const ref = useRef(null); + const sensorRef = useRef(null); + const [currentState, setCurrentState] = useState('unintialized'); + + // We know this is true because it is checked in RobotSimulation/index.tsx (toSpawn) + const world = context.context.moduleContexts.robot_simulation.state + .world as World; + + // This is not guaranteed to be true. + const ev3 = context.context.moduleContexts.robot_simulation.state.ev3 as + | DefaultEv3 + | undefined; + + const robotConsole = world.robotConsole; + + useEffect(() => { + const startThreeAndRapierEngines = async () => { + setCurrentState(world.state); + }; + + const attachRenderDom = () => { + if (ref.current) { + ref.current.replaceChildren(world.render.getElement()); + } + if (!ev3) { + return; + } + + if (sensorRef.current) { + sensorRef.current.replaceChildren( + ev3.get('colorSensor').renderer.getElement() + ); + } + }; + + if (currentState === 'unintialized') { + startThreeAndRapierEngines(); + } + + if (currentState === 'ready' || currentState === 'running') { + attachRenderDom(); + } + if (currentState === 'loading') { + setTimeout(() => { + setCurrentState('unintialized'); + }, 500); + } + }, [currentState]); + + useEffect(() => { + if (isOpen) { + world.start(); + } else { + world.pause(); + } + }, [isOpen]); + + return ( +
+
+
{currentState}
+
+
+ + } /> + } /> + }/> + }/> + } /> + +
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/ColorSensorPanel.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/ColorSensorPanel.tsx diff --git a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/ConsolePanel.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/ConsolePanel.tsx diff --git a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/MotorPidPanel.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/MotorPidPanel.tsx diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/UltrasonicSensorPanel.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/UltrasonicSensorPanel.tsx diff --git a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/WheelPidPanel.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/WheelPidPanel.tsx diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/tabComponents/LastUpdated.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/tabComponents/LastUpdated.tsx diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/Wrapper.tsx b/src/tabs/RobotSimulation/src/components/TabPanels/tabComponents/Wrapper.tsx similarity index 100% rename from src/tabs/RobotSimulation/components/TabPanels/tabComponents/Wrapper.tsx rename to src/tabs/RobotSimulation/src/components/TabPanels/tabComponents/Wrapper.tsx diff --git a/src/tabs/RobotSimulation/components/TabUi.tsx b/src/tabs/RobotSimulation/src/components/TabUi.tsx similarity index 94% rename from src/tabs/RobotSimulation/components/TabUi.tsx rename to src/tabs/RobotSimulation/src/components/TabUi.tsx index 00b9375c65..90018c32e6 100644 --- a/src/tabs/RobotSimulation/components/TabUi.tsx +++ b/src/tabs/RobotSimulation/src/components/TabUi.tsx @@ -1,20 +1,20 @@ -import React from 'react'; - -type TabUiProps = { - onOpenCanvas: () => void; -}; - -export const TabUi: React.FC = ({ onOpenCanvas }) => { - return ( -
-

Welcome to robot simulator.

- -
- ); -}; +import React from 'react'; + +type TabUiProps = { + onOpenCanvas: () => void; +}; + +export const TabUi: React.FC = ({ onOpenCanvas }) => { + return ( +
+

Welcome to robot simulator.

+ +
+ ); +}; diff --git a/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts b/src/tabs/RobotSimulation/src/hooks/fetchFromSimulation.ts similarity index 100% rename from src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts rename to src/tabs/RobotSimulation/src/hooks/fetchFromSimulation.ts diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/src/index.tsx similarity index 90% rename from src/tabs/RobotSimulation/index.tsx rename to src/tabs/RobotSimulation/src/index.tsx index 366fc79dbd..2c22093cdc 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/src/index.tsx @@ -1,40 +1,40 @@ -import type { DebuggerContext } from '../../typings/type_helpers'; -import { Main } from './components/Main'; - -/** - * Robot Simulation - * @author Joel Chan - */ - -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn(context: DebuggerContext) { - const worldState = - context.context.moduleContexts.robot_simulation.state?.world?.state; - return worldState !== undefined; - }, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: DebuggerContext) =>
, - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ - label: 'Sample Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ - iconName: 'build', -}; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { Main } from './components/Main'; + +/** + * Robot Simulation + * @author Joel Chan + */ + +export default { + /** + * This function will be called to determine if the component will be + * rendered. Currently spawns when the result in the REPL is "test". + * @param {DebuggerContext} context + * @returns {boolean} + */ + toSpawn(context: DebuggerContext) { + const worldState = + context.context.moduleContexts.robot_simulation.state?.world?.state; + return worldState !== undefined; + }, + + /** + * This function will be called to render the module tab in the side contents + * on Source Academy frontend. + * @param {DebuggerContext} context + */ + body: (context: DebuggerContext) =>
, + + /** + * The Tab's icon tooltip in the side contents on Source Academy frontend. + */ + label: 'Sample Tab', + + /** + * BlueprintJS IconName element's name, used to render the icon which will be + * displayed in the side contents panel. + * @see https://blueprintjs.com/docs/#icons + */ + iconName: 'build', +}; diff --git a/src/tabs/RobotSimulation/tsconfig.json b/src/tabs/RobotSimulation/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/RobotSimulation/tsconfig.json +++ b/src/tabs/RobotSimulation/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Rune/package.json b/src/tabs/Rune/package.json index 0df4521225..f910867c22 100644 --- a/src/tabs/Rune/package.json +++ b/src/tabs/Rune/package.json @@ -1,8 +1,10 @@ { - "name": "Rune", + "name": "@sourceacademy/tab-Rune", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-rune": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Rune/__tests__/Rune.tsx b/src/tabs/Rune/src/__tests__/Rune.tsx similarity index 100% rename from src/tabs/Rune/__tests__/Rune.tsx rename to src/tabs/Rune/src/__tests__/Rune.tsx diff --git a/src/tabs/Rune/__tests__/__snapshots__/Rune.tsx.snap b/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap similarity index 100% rename from src/tabs/Rune/__tests__/__snapshots__/Rune.tsx.snap rename to src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap diff --git a/src/tabs/Rune/hollusion_canvas.tsx b/src/tabs/Rune/src/hollusion_canvas.tsx similarity index 84% rename from src/tabs/Rune/hollusion_canvas.tsx rename to src/tabs/Rune/src/hollusion_canvas.tsx index 6daf0468ec..70a8487b33 100644 --- a/src/tabs/Rune/hollusion_canvas.tsx +++ b/src/tabs/Rune/src/hollusion_canvas.tsx @@ -1,6 +1,6 @@ +import type { HollusionRune } from '@sourceacademy/bundle-rune/functions'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; import React from 'react'; -import type { HollusionRune } from '../../bundles/rune/functions'; -import WebGLCanvas from '../common/WebglCanvas'; /** * Canvas used to display Hollusion runes diff --git a/src/tabs/Rune/index.tsx b/src/tabs/Rune/src/index.tsx similarity index 77% rename from src/tabs/Rune/index.tsx rename to src/tabs/Rune/src/index.tsx index c5585e92f5..06e1d003b7 100644 --- a/src/tabs/Rune/index.tsx +++ b/src/tabs/Rune/src/index.tsx @@ -1,9 +1,9 @@ -import { type RuneModuleState, isHollusionRune } from '../../bundles/rune/functions'; -import { glAnimation } from '../../typings/anim_types'; -import { getModuleState, type DebuggerContext, type ModuleTab } from '../../typings/type_helpers'; -import AnimationCanvas from '../common/AnimationCanvas'; -import MultiItemDisplay from '../common/MultItemDisplay'; -import WebGLCanvas from '../common/WebglCanvas'; +import { type RuneModuleState, isHollusionRune } from '@sourceacademy/bundle-rune/functions'; +import AnimationCanvas from '@sourceacademy/modules-lib/tabs/AnimationCanvas'; +import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; import HollusionCanvas from './hollusion_canvas'; export const RuneTab: ModuleTab = ({ context }) => { diff --git a/src/tabs/Rune/tsconfig.json b/src/tabs/Rune/tsconfig.json index caffc81dd2..97c73a120e 100644 --- a/src/tabs/Rune/tsconfig.json +++ b/src/tabs/Rune/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./src" + ] } \ No newline at end of file diff --git a/src/tabs/Sound/index.tsx b/src/tabs/Sound/index.tsx index 8142d68971..e4069ab226 100644 --- a/src/tabs/Sound/index.tsx +++ b/src/tabs/Sound/index.tsx @@ -1,6 +1,7 @@ -import type { SoundModuleState } from '../../bundles/sound/types'; -import { getModuleState, type DebuggerContext, type ModuleTab } from '../../typings/type_helpers'; -import MultiItemDisplay from '../common/MultItemDisplay'; +import type { SoundModuleState } from '@sourceacademy/bundle-sound/types'; +import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; +import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; /** * Tab for Source Academy Sounds Module diff --git a/src/tabs/Sound/package.json b/src/tabs/Sound/package.json index 58d6b7b834..1374514ca0 100644 --- a/src/tabs/Sound/package.json +++ b/src/tabs/Sound/package.json @@ -1,8 +1,10 @@ { - "name": "Sound", + "name": "@sourceacademy/tab-Sound", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-sound": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Sound/tsconfig.json b/src/tabs/Sound/tsconfig.json index caffc81dd2..8e4645929c 100644 --- a/src/tabs/Sound/tsconfig.json +++ b/src/tabs/Sound/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx" ] } \ No newline at end of file diff --git a/src/tabs/SoundMatrix/package.json b/src/tabs/SoundMatrix/package.json index 8305ca1ce9..e0aa2e2a0b 100644 --- a/src/tabs/SoundMatrix/package.json +++ b/src/tabs/SoundMatrix/package.json @@ -1,5 +1,5 @@ { - "name": "SoundMatrix", + "name": "@sourceacademy/tab-SoundMatrix", "version": "1.0.0", "private": true, "dependencies": { diff --git a/src/tabs/SoundMatrix/tsconfig.json b/src/tabs/SoundMatrix/tsconfig.json index caffc81dd2..854b48543b 100644 --- a/src/tabs/SoundMatrix/tsconfig.json +++ b/src/tabs/SoundMatrix/tsconfig.json @@ -1,5 +1,6 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": [ + "./index.tsx" + ] } \ No newline at end of file diff --git a/src/tabs/StereoSound/index.tsx b/src/tabs/StereoSound/index.tsx index cfb65395d2..85eb6687d7 100644 --- a/src/tabs/StereoSound/index.tsx +++ b/src/tabs/StereoSound/index.tsx @@ -1,6 +1,7 @@ -import type { StereoSoundModuleState } from '../../bundles/stereo_sound/types'; -import { getModuleState, type DebuggerContext, type ModuleTab } from '../../typings/type_helpers'; -import MultiItemDisplay from '../common/MultItemDisplay'; +import type { StereoSoundModuleState } from '@sourceacademy/bundle-stereo_sound/types'; +import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; +import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; /** * Tab for Source Academy Sounds Module diff --git a/src/tabs/StereoSound/package.json b/src/tabs/StereoSound/package.json index f18427476a..024dc004bd 100644 --- a/src/tabs/StereoSound/package.json +++ b/src/tabs/StereoSound/package.json @@ -1,8 +1,10 @@ { - "name": "StereoSound", + "name": "@sourceacademy/tab-StereoSound", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-stereo_sound": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/StereoSound/tsconfig.json b/src/tabs/StereoSound/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/StereoSound/tsconfig.json +++ b/src/tabs/StereoSound/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/Unittest/index.tsx b/src/tabs/Unittest/index.tsx index 2f2e36dad3..c83e886a9d 100644 --- a/src/tabs/Unittest/index.tsx +++ b/src/tabs/Unittest/index.tsx @@ -1,6 +1,7 @@ +import type { SuiteResult, TestContext } from '@sourceacademy/bundle-unittest/types'; +import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import React from 'react'; -import type { SuiteResult, TestContext } from '../../bundles/unittest/types'; -import { getModuleState, type DebuggerContext } from '../../typings/type_helpers'; /** * Tab for unit tests. diff --git a/src/tabs/Unittest/package.json b/src/tabs/Unittest/package.json index 1efa7855f0..dab36b85c2 100644 --- a/src/tabs/Unittest/package.json +++ b/src/tabs/Unittest/package.json @@ -1,8 +1,10 @@ { - "name": "Unittest", + "name": "@sourceacademy/tab-Unittest", "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/bundle-unittest": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/Unittest/tsconfig.json b/src/tabs/Unittest/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/Unittest/tsconfig.json +++ b/src/tabs/Unittest/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/UnityAcademy/index.tsx b/src/tabs/UnityAcademy/index.tsx index 865e50f8fa..355893e9c3 100644 --- a/src/tabs/UnityAcademy/index.tsx +++ b/src/tabs/UnityAcademy/index.tsx @@ -6,10 +6,10 @@ import { Button, NumericInput, Checkbox } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import { getInstance } from '@sourceacademy/bundle-unity_academy/UnityAcademy'; +import { UNITY_ACADEMY_BACKEND_URL } from '@sourceacademy/bundle-unity_academy/config'; +import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import React from 'react'; -import { getInstance } from '../../bundles/unity_academy/UnityAcademy'; -import { UNITY_ACADEMY_BACKEND_URL } from '../../bundles/unity_academy/config'; -import type { DebuggerContext } from '../../typings/type_helpers'; type Props = {}; diff --git a/src/tabs/UnityAcademy/package.json b/src/tabs/UnityAcademy/package.json index d4441d0b5d..e0c7e78c0c 100644 --- a/src/tabs/UnityAcademy/package.json +++ b/src/tabs/UnityAcademy/package.json @@ -1,8 +1,11 @@ { - "name": "UnityAcademy", + "name": "@sourceacademy/tab-UnityAcademy", "version": "1.0.0", "private": true, "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", + "@sourceacademy/bundle-unity_academy": "workspace:^", "react": "^18.3.1" }, "devDependencies": { diff --git a/src/tabs/UnityAcademy/tsconfig.json b/src/tabs/UnityAcademy/tsconfig.json index caffc81dd2..e4dd09d989 100644 --- a/src/tabs/UnityAcademy/tsconfig.json +++ b/src/tabs/UnityAcademy/tsconfig.json @@ -1,5 +1,4 @@ { - "references": { - "modules": "../tsconfig.json" - } + "extends": "../tsconfig.json", + "include": ["./index.tsx"] } \ No newline at end of file diff --git a/src/tabs/common/AnimationCanvas.tsx b/src/tabs/common/AnimationCanvas.tsx deleted file mode 100644 index 8ff2dbe7ae..0000000000 --- a/src/tabs/common/AnimationCanvas.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import { Icon, Slider, Tooltip } from '@blueprintjs/core'; -import { IconNames } from '@blueprintjs/icons'; -import React from 'react'; -import { type glAnimation } from '../../typings/anim_types'; -import AutoLoopSwitch from './AutoLoopSwitch'; -import ButtonComponent from './ButtonComponent'; -import PlayButton from './PlayButton'; -import WebGLCanvas from './WebglCanvas'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from './css_constants'; - -type AnimCanvasProps = { - animation: glAnimation; -}; - -type AnimCanvasState = { - /** Timestamp of the animation */ - animTimestamp: number; - - /** Boolean value indicating if the animation is playing */ - isPlaying: boolean; - - /** Previous value of `isPlaying` */ - wasPlaying: boolean; - - /** Whether auto loop is enabled */ - isAutoLooping: boolean; - - errored?: any; -}; - -/** - * Canvas to display glAnimations. - * - * Uses WebGLCanvas internally. - */ -export default class AnimationCanvas extends React.Component< - AnimCanvasProps, - AnimCanvasState -> { - private canvas: HTMLCanvasElement | null; - - /** - * The duration of one frame in milliseconds - */ - private readonly frameDuration: number; - - /** - * The duration of the entire animation - */ - private readonly animationDuration: number; - - /** - * Last timestamp since the previous `requestAnimationFrame` call - */ - private callbackTimestamp: number | null; - - private reqframeId: number | null; - - constructor(props: AnimCanvasProps | Readonly) { - super(props); - - this.state = { - animTimestamp: 0, - isPlaying: false, - wasPlaying: false, - isAutoLooping: true - }; - - this.canvas = null; - this.frameDuration = 1000 / props.animation.fps; - this.animationDuration = Math.round(props.animation.duration * 1000); - this.callbackTimestamp = null; - this.reqframeId = null; - } - - public componentDidMount() { - this.drawFrame(); - } - - /** - * Call this to actually draw a frame onto the canvas - */ - private drawFrame = () => { - if (this.canvas) { - try { - const frame = this.props.animation.getFrame( - this.state.animTimestamp / 1000 - ); - frame.draw(this.canvas); - } catch (error) { - if (this.reqframeId !== null) { - cancelAnimationFrame(this.reqframeId); - } - - this.setState({ - isPlaying: false, - errored: error - }); - } - } - }; - - private reqFrame = () => { - this.reqframeId = requestAnimationFrame(this.animationCallback); - }; - - private startAnimation = () => this.setState( - { - isPlaying: true - }, - this.reqFrame - ); - - private stopAnimation = () => this.setState( - { - isPlaying: false - }, - () => { - this.callbackTimestamp = null; - } - ); - - /** - * Callback to use with `requestAnimationFrame` - */ - private animationCallback = (timeInMs: number) => { - if (!this.canvas || !this.state.isPlaying) return; - - if (!this.callbackTimestamp) { - this.callbackTimestamp = timeInMs; - this.drawFrame(); - this.reqFrame(); - return; - } - - const currentFrame = timeInMs - this.callbackTimestamp; - - if (currentFrame < this.frameDuration) { - // Not time to draw a new frame yet - this.reqFrame(); - return; - } - - this.callbackTimestamp = timeInMs; - if (this.state.animTimestamp >= this.animationDuration) { - // Animation has ended - if (this.state.isAutoLooping) { - // If auto loop is active, restart the animation - this.setState( - { - animTimestamp: 0 - }, - this.reqFrame - ); - } else { - // Otherwise, stop the animation - this.stopAnimation(); - } - } else { - // Animation hasn't ended, so just draw the next frame - this.setState( - (prev) => ({ - animTimestamp: prev.animTimestamp + currentFrame - }), - () => { - this.drawFrame(); - this.reqFrame(); - } - ); - } - }; - - /** - * Play button click handler - */ - private onPlayButtonClick = () => { - if (this.state.isPlaying) this.stopAnimation(); - else this.startAnimation(); - }; - - /** - * Reset button click handler - */ - private onResetButtonClick = () => { - this.setState( - { animTimestamp: 0 }, - () => { - if (this.state.isPlaying) { - // Force stop - this.onPlayButtonClick(); - } - - this.drawFrame(); - } - ); - }; - - /** - * Slider value change handler - * @param newValue New value of the slider - */ - private onSliderChange = (newValue: number) => { - this.callbackTimestamp = null; - this.setState( - (prev) => ({ - wasPlaying: prev.isPlaying, - isPlaying: false, - animTimestamp: newValue - }), - this.drawFrame - ); - }; - - /** - * Handler triggered when the slider is clicked off - */ - private onSliderRelease = () => { - this.setState( - (prev) => ({ - isPlaying: prev.wasPlaying - }), - () => { - if (!this.state.isPlaying) { - this.callbackTimestamp = null; - } else { - this.reqFrame(); - } - } - ); - }; - - /** - * Auto loop switch onChange callback - */ - private onSwitchChange = () => { - this.setState((prev) => ({ - isAutoLooping: !prev.isAutoLooping - })); - }; - - public render() { - return
-
-
- - - - - - - - -
-
-
- {this.state.errored - ? ( -
-
- -
-

An error occurred while running your animation!

-

Here's the details:

-
-
- - {this.state.errored.toString()} - -
) - : ( - { - this.canvas = r; - }} - /> - )} -
-
; - } -} diff --git a/src/tabs/common/AutoLoopSwitch.tsx b/src/tabs/common/AutoLoopSwitch.tsx deleted file mode 100644 index 13cec03797..0000000000 --- a/src/tabs/common/AutoLoopSwitch.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* [Imports] */ -import { Switch, type SwitchProps } from '@blueprintjs/core'; -import React from 'react'; - -export type AutoLoopSwitchProps = Omit & { - isAutoLooping: boolean, -}; - -/* [Main] */ -export default class AutoLoopSwitch extends React.Component { - render() { - return ; - } -} diff --git a/src/tabs/common/ButtonComponent.tsx b/src/tabs/common/ButtonComponent.tsx deleted file mode 100644 index 4eb3d68ab2..0000000000 --- a/src/tabs/common/ButtonComponent.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { AnchorButton, Button, Intent } from '@blueprintjs/core'; -import type { MouseEventHandler, ReactNode } from 'react'; - -const defaultOptions = { - className: '', - fullWidth: false, - iconOnRight: false, - intent: Intent.NONE, - minimal: true -}; - -type Props = { - onClick?: MouseEventHandler, - disabled?: boolean, - children?: ReactNode, -}; - -const ButtonComponent = (props: Props) => { - const buttonProps = { - ...defaultOptions, - ...props - }; - return props.disabled - ? ( - - ) - : ( - -

-
- Call  - {/* When the text box is focused, it shows a little up and down bar, which needs a little bit - * more space to be shown - */} -
- { - // Disallow non numeric inputs - if (newValue && !/^[0-9]+$/u.test(newValue)) return; - - // Disallow numbers that have too many digits - if (newValue.length > elementsDigitCount) return; - setStepEditorValue(newValue); - }} - onConfirm={(value) => { - if (value) { - const newStep = parseInt(value); - const clampedStep = clamp(newStep, 1, elements.length); - setCurrentStep(clampedStep - 1); - setStepEditorFocused(false); - setStepEditorValue(clampedStep.toString()); - return; - } - - // If the input element is blank - // then reset it - resetStepEditor(); - - // Indicate that the editor is no longer focused - setStepEditorFocused(false); - }} - onCancel={() => { - resetStepEditor(); - setStepEditorFocused(false); - }} - onEdit={() => setStepEditorFocused(true)} - /> -
- {stepEditorFocused && <> }/{elements.length} -
-

- - -
- {elements[currentStep]} -
- - ); -}; - -export default MultiItemDisplay; diff --git a/src/tabs/common/PlayButton.tsx b/src/tabs/common/PlayButton.tsx deleted file mode 100644 index c21f3bd0e0..0000000000 --- a/src/tabs/common/PlayButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* [Imports] */ -import { Icon, type ButtonProps, Tooltip } from '@blueprintjs/core'; -import { IconNames } from '@blueprintjs/icons'; -import React from 'react'; -import ButtonComponent from './ButtonComponent'; - -/* [Exports] */ -export type PlayButtonProps = ButtonProps & { - isPlaying: boolean, - // onClickCallback: () => void, -}; - -/* [Main] */ -export default class PlayButton extends React.Component { - render() { - return - - - - ; - } -} diff --git a/src/tabs/common/WebglCanvas.tsx b/src/tabs/common/WebglCanvas.tsx deleted file mode 100644 index 072c74caf5..0000000000 --- a/src/tabs/common/WebglCanvas.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { CANVAS_MAX_WIDTH } from './css_constants'; - -const defaultStyle = { - width: '100%', - maxWidth: CANVAS_MAX_WIDTH, - aspectRatio: '1' -} as React.CSSProperties; - -/** - * Canvas component used by the curve and rune modules. Standardizes the - * appearances of canvases for both modules. - */ -const WebGLCanvas = React.forwardRef( - (props: any, ref) => { - const style - = props.style !== undefined - ? { - ...defaultStyle, - ...props.style - } - : defaultStyle; - - return ( - - ); - } -); - -export default WebGLCanvas; diff --git a/src/tabs/common/css_constants.ts b/src/tabs/common/css_constants.ts deleted file mode 100644 index d245a0f5e7..0000000000 --- a/src/tabs/common/css_constants.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* [Imports] */ -import { IconSize } from '@blueprintjs/core'; - -/* [Exports] */ -// Values extracted from the styling of the Source Academy frontend -export const SA_TAB_BUTTON_WIDTH: string = '40px'; -export const SA_TAB_ICON_SIZE: number = IconSize.LARGE; - -// Values extracted from BlueprintJS V4 -export const BP_TAB_BUTTON_MARGIN: string = '20px'; -export const BP_TAB_PANEL_MARGIN: string = '20px'; -export const BP_CARD_BORDER_RADIUS: string = '2px'; -export const BP_TEXT_MARGIN: string = '10px'; - -export const BP_TEXT_COLOR: string = '#FFFFFF'; -export const BP_ICON_COLOR: string = '#A7B6C2'; - -// Values extracted from the Ace editor -export const ACE_GUTTER_TEXT_COLOR: string = '#8091A0'; -export const ACE_GUTTER_BACKGROUND_COLOR: string = '#34495E'; - -// Commonly reused values -export const CANVAS_MAX_WIDTH: string = 'max(70vh, 30vw)'; diff --git a/src/tabs/common/package.json b/src/tabs/common/package.json deleted file mode 100644 index 84f2b1dbd3..0000000000 --- a/src/tabs/common/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "common", - "version": "1.0.0", - "private": true, - "dependencies": { - "react": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.3.1" - } -} diff --git a/src/tabs/common/testUtils.ts b/src/tabs/common/testUtils.ts deleted file mode 100644 index 6e6540f1a5..0000000000 --- a/src/tabs/common/testUtils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { DebuggerContext } from '../../typings/type_helpers'; - -export const mockDebuggerContext = (state: T, moduleName: string) => ({ - context: { - moduleContexts: { - [moduleName]: { - state - } - } - } -}) as DebuggerContext; diff --git a/src/tabs/common/tsconfig.json b/src/tabs/common/tsconfig.json deleted file mode 100644 index caffc81dd2..0000000000 --- a/src/tabs/common/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "references": { - "modules": "../tsconfig.json" - } -} \ No newline at end of file diff --git a/src/tabs/tsconfig.json b/src/tabs/tsconfig.json index 467aee112a..f628782936 100644 --- a/src/tabs/tsconfig.json +++ b/src/tabs/tsconfig.json @@ -1,17 +1,6 @@ { + "extends": "../tsconfig.json", "compilerOptions": { - "strict": false, - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "noEmit": true, - "paths": { - "@src/*": ["./src/*"] - }, - "target": "ESNext", - "skipLibCheck": true, - "composite": true - }, - "include": ["./src", "jest.setup.ts"] + "jsx": "react-jsx" + } } From e6cfbfad4a9ee41552604cf60534276d5a0d8f46 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 12 May 2025 17:23:06 -0400 Subject: [PATCH 004/112] Add moduleslib --- src/modules-lib/.gitignore | 1 + src/modules-lib/package.json | 41 +++ src/modules-lib/src/__tests__/hextocolor.ts | 17 + src/modules-lib/src/specialErrors.ts | 8 + src/modules-lib/src/tabs/AnimationCanvas.tsx | 347 ++++++++++++++++++ src/modules-lib/src/tabs/AutoLoopSwitch.tsx | 25 ++ src/modules-lib/src/tabs/ButtonComponent.tsx | 31 ++ src/modules-lib/src/tabs/ModalDiv.tsx | 66 ++++ src/modules-lib/src/tabs/MultItemDisplay.tsx | 142 +++++++ src/modules-lib/src/tabs/PlayButton.tsx | 27 ++ src/modules-lib/src/tabs/WebglCanvas.tsx | 30 ++ src/modules-lib/src/tabs/css_constants.ts | 23 ++ src/modules-lib/src/tabs/testUtils.ts | 11 + src/modules-lib/src/tabs/utils.ts | 5 + src/modules-lib/src/type_map.ts | 69 ++++ src/modules-lib/src/types/index.ts | 44 +++ .../src/types/js-slang/context.d.ts | 4 + src/modules-lib/src/utilities.ts | 16 + src/modules-lib/tsconfig.json | 10 + 19 files changed, 917 insertions(+) create mode 100644 src/modules-lib/.gitignore create mode 100644 src/modules-lib/package.json create mode 100644 src/modules-lib/src/__tests__/hextocolor.ts create mode 100644 src/modules-lib/src/specialErrors.ts create mode 100644 src/modules-lib/src/tabs/AnimationCanvas.tsx create mode 100644 src/modules-lib/src/tabs/AutoLoopSwitch.tsx create mode 100644 src/modules-lib/src/tabs/ButtonComponent.tsx create mode 100644 src/modules-lib/src/tabs/ModalDiv.tsx create mode 100644 src/modules-lib/src/tabs/MultItemDisplay.tsx create mode 100644 src/modules-lib/src/tabs/PlayButton.tsx create mode 100644 src/modules-lib/src/tabs/WebglCanvas.tsx create mode 100644 src/modules-lib/src/tabs/css_constants.ts create mode 100644 src/modules-lib/src/tabs/testUtils.ts create mode 100644 src/modules-lib/src/tabs/utils.ts create mode 100644 src/modules-lib/src/type_map.ts create mode 100644 src/modules-lib/src/types/index.ts create mode 100644 src/modules-lib/src/types/js-slang/context.d.ts create mode 100644 src/modules-lib/src/utilities.ts create mode 100644 src/modules-lib/tsconfig.json diff --git a/src/modules-lib/.gitignore b/src/modules-lib/.gitignore new file mode 100644 index 0000000000..53c37a1660 --- /dev/null +++ b/src/modules-lib/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/src/modules-lib/package.json b/src/modules-lib/package.json new file mode 100644 index 0000000000..03151c246c --- /dev/null +++ b/src/modules-lib/package.json @@ -0,0 +1,41 @@ +{ + "name": "@sourceacademy/modules-lib", + "description": "Common library used by Source Acaddemy bundles and tabs", + "private": true, + "devDependencies": { + "@types/react": "^18.3.2", + "@types/react-dom": "^18.3.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "typescript": "^5.8.2" + }, + "exports": { + "./types": "./dist/types/index.js", + "./tabs/*": "./dist/tabs/*.js", + "./utilities": "./dist/utilities.js", + "./utilities.js": "./dist/utilities.js", + "./specialErrors": "./dist/specialErrors.js", + "./specialErrors.js": "./dist/specialErrors.js", + "./type_map": "./dist/type_map.js" + }, + "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", + "js-slang": "^1.0.81", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "scripts": { + "build": "tsc --project ./tsconfig.json" + }, + "jest": { + "displayName": "Modules Library", + "extensionsToTreatAsEsm": [ + ".ts" + ], + "preset": "ts-jest/presets/default-esm", + "testMatch": [ + "/src/**/__tests__/**/*.ts" + ] + } +} diff --git a/src/modules-lib/src/__tests__/hextocolor.ts b/src/modules-lib/src/__tests__/hextocolor.ts new file mode 100644 index 0000000000..835095350a --- /dev/null +++ b/src/modules-lib/src/__tests__/hextocolor.ts @@ -0,0 +1,17 @@ +import { hexToColor } from '../utilities'; + +describe('Test hexToColor', () => { + test.each([ + ['#FFFFFF', [1, 1, 1]], + ['ffffff', [1, 1, 1]], + ['0088ff', [0, 0.53, 1]], + ['#000000', [0, 0, 0]], + ['#GGGGGG', [0, 0, 0]], + ['888888', [0.53, 0.53, 0.53]] + ])('Testing %s', (c, expected) => { + const result = hexToColor(c); + for (let i = 0; i < expected.length; i++) { + expect(result[i]).toBeCloseTo(expected[i]); + } + }); +}); diff --git a/src/modules-lib/src/specialErrors.ts b/src/modules-lib/src/specialErrors.ts new file mode 100644 index 0000000000..54aa1fbbfa --- /dev/null +++ b/src/modules-lib/src/specialErrors.ts @@ -0,0 +1,8 @@ +/** + * This function is used to interrupt the frontend execution. + * When called, the frontend will notify that the program has ended successfully + * and display a message that the program is stopped by a module. + */ +export function interrupt() { + throw 'source_academy_interrupt'; +} diff --git a/src/modules-lib/src/tabs/AnimationCanvas.tsx b/src/modules-lib/src/tabs/AnimationCanvas.tsx new file mode 100644 index 0000000000..78f9fda639 --- /dev/null +++ b/src/modules-lib/src/tabs/AnimationCanvas.tsx @@ -0,0 +1,347 @@ +import { Icon, Slider, Tooltip } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; +import React from 'react'; +import type { glAnimation } from '../types'; +import AutoLoopSwitch from './AutoLoopSwitch'; +import ButtonComponent from './ButtonComponent'; +import PlayButton from './PlayButton'; +import WebGLCanvas from './WebglCanvas'; +import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from './css_constants'; + +type AnimCanvasProps = { + animation: glAnimation; +}; + +type AnimCanvasState = { + /** Timestamp of the animation */ + animTimestamp: number; + + /** Boolean value indicating if the animation is playing */ + isPlaying: boolean; + + /** Previous value of `isPlaying` */ + wasPlaying: boolean; + + /** Whether auto loop is enabled */ + isAutoLooping: boolean; + + errored?: any; +}; + +/** + * Canvas to display glAnimations. + * + * Uses WebGLCanvas internally. + */ +export default class AnimationCanvas extends React.Component< + AnimCanvasProps, + AnimCanvasState +> { + private canvas: HTMLCanvasElement | null; + + /** + * The duration of one frame in milliseconds + */ + private readonly frameDuration: number; + + /** + * The duration of the entire animation + */ + private readonly animationDuration: number; + + /** + * Last timestamp since the previous `requestAnimationFrame` call + */ + private callbackTimestamp: number | null; + + private reqframeId: number | null; + + constructor(props: AnimCanvasProps | Readonly) { + super(props); + + this.state = { + animTimestamp: 0, + isPlaying: false, + wasPlaying: false, + isAutoLooping: true + }; + + this.canvas = null; + this.frameDuration = 1000 / props.animation.fps; + this.animationDuration = Math.round(props.animation.duration * 1000); + this.callbackTimestamp = null; + this.reqframeId = null; + } + + public componentDidMount() { + this.drawFrame(); + } + + /** + * Call this to actually draw a frame onto the canvas + */ + private drawFrame = () => { + if (this.canvas) { + try { + const frame = this.props.animation.getFrame( + this.state.animTimestamp / 1000 + ); + frame.draw(this.canvas); + } catch (error) { + if (this.reqframeId !== null) { + cancelAnimationFrame(this.reqframeId); + } + + this.setState({ + isPlaying: false, + errored: error + }); + } + } + }; + + private reqFrame = () => { + this.reqframeId = requestAnimationFrame(this.animationCallback); + }; + + private startAnimation = () => this.setState( + { + isPlaying: true + }, + this.reqFrame + ); + + private stopAnimation = () => this.setState( + { + isPlaying: false + }, + () => { + this.callbackTimestamp = null; + } + ); + + /** + * Callback to use with `requestAnimationFrame` + */ + private animationCallback = (timeInMs: number) => { + if (!this.canvas || !this.state.isPlaying) return; + + if (!this.callbackTimestamp) { + this.callbackTimestamp = timeInMs; + this.drawFrame(); + this.reqFrame(); + return; + } + + const currentFrame = timeInMs - this.callbackTimestamp; + + if (currentFrame < this.frameDuration) { + // Not time to draw a new frame yet + this.reqFrame(); + return; + } + + this.callbackTimestamp = timeInMs; + if (this.state.animTimestamp >= this.animationDuration) { + // Animation has ended + if (this.state.isAutoLooping) { + // If auto loop is active, restart the animation + this.setState( + { + animTimestamp: 0 + }, + this.reqFrame + ); + } else { + // Otherwise, stop the animation + this.stopAnimation(); + } + } else { + // Animation hasn't ended, so just draw the next frame + this.setState( + (prev) => ({ + animTimestamp: prev.animTimestamp + currentFrame + }), + () => { + this.drawFrame(); + this.reqFrame(); + } + ); + } + }; + + /** + * Play button click handler + */ + private onPlayButtonClick = () => { + if (this.state.isPlaying) this.stopAnimation(); + else this.startAnimation(); + }; + + /** + * Reset button click handler + */ + private onResetButtonClick = () => { + this.setState( + { animTimestamp: 0 }, + () => { + if (this.state.isPlaying) { + // Force stop + this.onPlayButtonClick(); + } + + this.drawFrame(); + } + ); + }; + + /** + * Slider value change handler + * @param newValue New value of the slider + */ + private onSliderChange = (newValue: number) => { + this.callbackTimestamp = null; + this.setState( + (prev) => ({ + wasPlaying: prev.isPlaying, + isPlaying: false, + animTimestamp: newValue + }), + this.drawFrame + ); + }; + + /** + * Handler triggered when the slider is clicked off + */ + private onSliderRelease = () => { + this.setState( + (prev) => ({ + isPlaying: prev.wasPlaying + }), + () => { + if (!this.state.isPlaying) { + this.callbackTimestamp = null; + } else { + this.reqFrame(); + } + } + ); + }; + + /** + * Auto loop switch onChange callback + */ + private onSwitchChange = () => { + this.setState((prev) => ({ + isAutoLooping: !prev.isAutoLooping + })); + }; + + public render() { + return
+
+
+ + + + + + + + +
+
+
+ {this.state.errored + ? ( +
+
+ +
+

An error occurred while running your animation!

+

Here's the details:

+
+
+ + {this.state.errored.toString()} + +
) + : ( + { + this.canvas = r; + }} + /> + )} +
+
; + } +} diff --git a/src/modules-lib/src/tabs/AutoLoopSwitch.tsx b/src/modules-lib/src/tabs/AutoLoopSwitch.tsx new file mode 100644 index 0000000000..13cec03797 --- /dev/null +++ b/src/modules-lib/src/tabs/AutoLoopSwitch.tsx @@ -0,0 +1,25 @@ +/* [Imports] */ +import { Switch, type SwitchProps } from '@blueprintjs/core'; +import React from 'react'; + +export type AutoLoopSwitchProps = Omit & { + isAutoLooping: boolean, +}; + +/* [Main] */ +export default class AutoLoopSwitch extends React.Component { + render() { + return ; + } +} diff --git a/src/modules-lib/src/tabs/ButtonComponent.tsx b/src/modules-lib/src/tabs/ButtonComponent.tsx new file mode 100644 index 0000000000..4eb3d68ab2 --- /dev/null +++ b/src/modules-lib/src/tabs/ButtonComponent.tsx @@ -0,0 +1,31 @@ +import { AnchorButton, Button, Intent } from '@blueprintjs/core'; +import type { MouseEventHandler, ReactNode } from 'react'; + +const defaultOptions = { + className: '', + fullWidth: false, + iconOnRight: false, + intent: Intent.NONE, + minimal: true +}; + +type Props = { + onClick?: MouseEventHandler, + disabled?: boolean, + children?: ReactNode, +}; + +const ButtonComponent = (props: Props) => { + const buttonProps = { + ...defaultOptions, + ...props + }; + return props.disabled + ? ( + + ) + : ( + +

+
+ Call  + {/* When the text box is focused, it shows a little up and down bar, which needs a little bit + * more space to be shown + */} +
+ { + // Disallow non numeric inputs + if (newValue && !/^[0-9]+$/u.test(newValue)) return; + + // Disallow numbers that have too many digits + if (newValue.length > elementsDigitCount) return; + setStepEditorValue(newValue); + }} + onConfirm={(value) => { + if (value) { + const newStep = parseInt(value); + const clampedStep = clamp(newStep, 1, elements.length); + setCurrentStep(clampedStep - 1); + setStepEditorFocused(false); + setStepEditorValue(clampedStep.toString()); + return; + } + + // If the input element is blank + // then reset it + resetStepEditor(); + + // Indicate that the editor is no longer focused + setStepEditorFocused(false); + }} + onCancel={() => { + resetStepEditor(); + setStepEditorFocused(false); + }} + onEdit={() => setStepEditorFocused(true)} + /> +
+ {stepEditorFocused && <> }/{elements.length} +
+

+ + +
+ {elements[currentStep]} +
+ + ); +}; + +export default MultiItemDisplay; diff --git a/src/modules-lib/src/tabs/PlayButton.tsx b/src/modules-lib/src/tabs/PlayButton.tsx new file mode 100644 index 0000000000..c21f3bd0e0 --- /dev/null +++ b/src/modules-lib/src/tabs/PlayButton.tsx @@ -0,0 +1,27 @@ +/* [Imports] */ +import { Icon, type ButtonProps, Tooltip } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; +import React from 'react'; +import ButtonComponent from './ButtonComponent'; + +/* [Exports] */ +export type PlayButtonProps = ButtonProps & { + isPlaying: boolean, + // onClickCallback: () => void, +}; + +/* [Main] */ +export default class PlayButton extends React.Component { + render() { + return + + + + ; + } +} diff --git a/src/modules-lib/src/tabs/WebglCanvas.tsx b/src/modules-lib/src/tabs/WebglCanvas.tsx new file mode 100644 index 0000000000..072c74caf5 --- /dev/null +++ b/src/modules-lib/src/tabs/WebglCanvas.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { CANVAS_MAX_WIDTH } from './css_constants'; + +const defaultStyle = { + width: '100%', + maxWidth: CANVAS_MAX_WIDTH, + aspectRatio: '1' +} as React.CSSProperties; + +/** + * Canvas component used by the curve and rune modules. Standardizes the + * appearances of canvases for both modules. + */ +const WebGLCanvas = React.forwardRef( + (props: any, ref) => { + const style + = props.style !== undefined + ? { + ...defaultStyle, + ...props.style + } + : defaultStyle; + + return ( + + ); + } +); + +export default WebGLCanvas; diff --git a/src/modules-lib/src/tabs/css_constants.ts b/src/modules-lib/src/tabs/css_constants.ts new file mode 100644 index 0000000000..d245a0f5e7 --- /dev/null +++ b/src/modules-lib/src/tabs/css_constants.ts @@ -0,0 +1,23 @@ +/* [Imports] */ +import { IconSize } from '@blueprintjs/core'; + +/* [Exports] */ +// Values extracted from the styling of the Source Academy frontend +export const SA_TAB_BUTTON_WIDTH: string = '40px'; +export const SA_TAB_ICON_SIZE: number = IconSize.LARGE; + +// Values extracted from BlueprintJS V4 +export const BP_TAB_BUTTON_MARGIN: string = '20px'; +export const BP_TAB_PANEL_MARGIN: string = '20px'; +export const BP_CARD_BORDER_RADIUS: string = '2px'; +export const BP_TEXT_MARGIN: string = '10px'; + +export const BP_TEXT_COLOR: string = '#FFFFFF'; +export const BP_ICON_COLOR: string = '#A7B6C2'; + +// Values extracted from the Ace editor +export const ACE_GUTTER_TEXT_COLOR: string = '#8091A0'; +export const ACE_GUTTER_BACKGROUND_COLOR: string = '#34495E'; + +// Commonly reused values +export const CANVAS_MAX_WIDTH: string = 'max(70vh, 30vw)'; diff --git a/src/modules-lib/src/tabs/testUtils.ts b/src/modules-lib/src/tabs/testUtils.ts new file mode 100644 index 0000000000..f662679a41 --- /dev/null +++ b/src/modules-lib/src/tabs/testUtils.ts @@ -0,0 +1,11 @@ +import type { DebuggerContext } from '../types'; + +export const mockDebuggerContext = (state: T, moduleName: string) => ({ + context: { + moduleContexts: { + [moduleName]: { + state + } + } + } +}) as DebuggerContext; diff --git a/src/modules-lib/src/tabs/utils.ts b/src/modules-lib/src/tabs/utils.ts new file mode 100644 index 0000000000..7e46833827 --- /dev/null +++ b/src/modules-lib/src/tabs/utils.ts @@ -0,0 +1,5 @@ +import type { DebuggerContext } from '../types'; + +export function getModuleState({ context }: DebuggerContext, name: string): T { + return context.moduleContexts[name].state; +} diff --git a/src/modules-lib/src/type_map.ts b/src/modules-lib/src/type_map.ts new file mode 100644 index 0000000000..014138baa9 --- /dev/null +++ b/src/modules-lib/src/type_map.ts @@ -0,0 +1,69 @@ +export const type_map : Record = {}; + +const registerType = (name: string, declaration: string) => { + if (name == 'prelude') { + type_map['prelude'] = type_map['prelude'] != undefined ? type_map['prelude'] + '\n' + declaration : declaration; + } else { + type_map[name] = declaration; + } +}; + +export const classDeclaration = (name: string) => { + return (_target: any) => { + registerType('prelude', `class ${name} {}`); + }; +}; + +export const typeDeclaration = (type: string, declaration = null) => { + return (_target: any) => { + const typeAlias = `type ${_target.name} = ${type}`; + let variableDeclaration = `const ${_target.name} = ${declaration === null ? type : declaration}`; + + switch (type) { + case 'number': + variableDeclaration = `const ${_target.name} = 0`; + break; + case 'string': + variableDeclaration = `const ${_target.name} = ''`; + break; + case 'boolean': + variableDeclaration = `const ${_target.name} = false`; + break; + case 'void': + variableDeclaration = ''; + break; + } + + registerType('prelude', `${typeAlias};\n${variableDeclaration};`); + }; +}; + +export const functionDeclaration = (paramTypes: string, returnType: string) => { + return (_target: any, propertyKey: string, _descriptor: PropertyDescriptor) => { + let returnValue = ''; + switch (returnType) { + case 'number': + returnValue = 'return 0'; + break; + case 'string': + returnValue = "return ''"; + break; + case 'boolean': + returnValue = 'return false'; + break; + case 'void': + returnValue = ''; + break; + default: + returnValue = `return ${returnType}`; + break; + } + registerType(propertyKey, `function ${propertyKey} (${paramTypes}) : ${returnType} { ${returnValue} }`); + }; +}; + +export const variableDeclaration = (type: string) => { + return (_target: any, propertyKey: string) => { + registerType(propertyKey, `const ${propertyKey}: ${type} = ${type}`); + }; +}; diff --git a/src/modules-lib/src/types/index.ts b/src/modules-lib/src/types/index.ts new file mode 100644 index 0000000000..9da4fb9ad3 --- /dev/null +++ b/src/modules-lib/src/types/index.ts @@ -0,0 +1,44 @@ +import type { Context } from 'js-slang'; +import type { FC } from 'react'; + +/** + * Represents an animation drawn using WebGL + * @field duration Duration of the animation in secondss + * @field fps Framerate in frames per second + */ +export abstract class glAnimation { + constructor(public readonly duration: number, public readonly fps: number) { } + + public abstract getFrame(timestamp: number): AnimFrame; + + public static isAnimation = (obj: any): obj is glAnimation => obj.fps !== undefined; +} +export interface AnimFrame { + draw: (canvas: HTMLCanvasElement) => void; +} +export type DeepPartial = T extends object ? { + [P in keyof T]?: DeepPartial; +} : T; + +/** + * DebuggerContext type used by frontend to assist typing information + */ +export type DebuggerContext = { + result: any; + lastDebuggerResult: any; + code: string; + context: Context; + workspaceLocation?: any; +}; + +export type ModuleContexts = Context['moduleContexts']; + +/** + * Interface to represent objects that require a string representation in the + * REPL + */ +export interface ReplResult { + toReplString: () => string; +} + +export type ModuleTab = FC<{ context: DebuggerContext }>; diff --git a/src/modules-lib/src/types/js-slang/context.d.ts b/src/modules-lib/src/types/js-slang/context.d.ts new file mode 100644 index 0000000000..841849bbc1 --- /dev/null +++ b/src/modules-lib/src/types/js-slang/context.d.ts @@ -0,0 +1,4 @@ +import type { Context } from 'js-slang'; + +const ctx: Context; +export default ctx; diff --git a/src/modules-lib/src/utilities.ts b/src/modules-lib/src/utilities.ts new file mode 100644 index 0000000000..f25780ffdd --- /dev/null +++ b/src/modules-lib/src/utilities.ts @@ -0,0 +1,16 @@ +/* [Exports] */ +export function degreesToRadians(degrees: number): number { + return (degrees / 360) * (2 * Math.PI); +} + +export function hexToColor(hex: string): [number, number, number] { + const regex = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/igu; + const groups = regex.exec(hex); + + if (groups == undefined) return [0, 0, 0]; + return [ + parseInt(groups[1], 16) / 0xff, + parseInt(groups[2], 16) / 0xff, + parseInt(groups[3], 16) / 0xff + ]; +} diff --git a/src/modules-lib/tsconfig.json b/src/modules-lib/tsconfig.json new file mode 100644 index 0000000000..296d071d78 --- /dev/null +++ b/src/modules-lib/tsconfig.json @@ -0,0 +1,10 @@ +// modules-lib +{ + "compilerOptions": { + "declaration": true, + "noEmit": false, + "outDir": "dist" + }, + "extends": ["../tsconfig.json", "../tabs/tsconfig.json"], + "include": ["./src"] +} \ No newline at end of file From 159790b69179b0f6555e9d8e2b924ebf175c1fc1 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 12 May 2025 17:30:34 -0400 Subject: [PATCH 005/112] Add lint plugin --- src/lintplugin/package.json | 29 +++++++++++ src/lintplugin/src/index.ts | 11 ++++ .../src/rules/__tests__/typeimports.test.ts | 45 +++++++++++++++++ src/lintplugin/src/rules/typeimports.ts | 50 +++++++++++++++++++ src/lintplugin/tsconfig.json | 11 ++++ 5 files changed, 146 insertions(+) create mode 100644 src/lintplugin/package.json create mode 100644 src/lintplugin/src/index.ts create mode 100644 src/lintplugin/src/rules/__tests__/typeimports.test.ts create mode 100644 src/lintplugin/src/rules/typeimports.ts create mode 100644 src/lintplugin/tsconfig.json diff --git a/src/lintplugin/package.json b/src/lintplugin/package.json new file mode 100644 index 0000000000..f2c1a14d7a --- /dev/null +++ b/src/lintplugin/package.json @@ -0,0 +1,29 @@ +{ + "name": "@sourceacademy/lint-plugin", + "version": "1.0.0", + "exports": { + ".": "./dist/index.js" + }, + "dependencies": { + "eslint": "^9.21.0" + }, + "devDependencies": { + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "typescript": "^5.8.2" + }, + "scripts": { + "build": "tsc --project ./tsconfig.json", + "test": "jest" + }, + "jest": { + "displayName": "Lint Plugin", + "extensionsToTreatAsEsm": [ + ".ts" + ], + "preset": "ts-jest/presets/default-esm", + "testMatch": [ + "/src/**/__tests__/**/*.test.ts" + ] + } +} diff --git a/src/lintplugin/src/index.ts b/src/lintplugin/src/index.ts new file mode 100644 index 0000000000..a93f28d850 --- /dev/null +++ b/src/lintplugin/src/index.ts @@ -0,0 +1,11 @@ +import type { ESLint } from 'eslint'; +import collateTypeImports from './rules/typeimports'; + +const plugin: ESLint.Plugin = { + name: 'Source Academy Lint Plugin', + rules: { + 'collate-type-imports': collateTypeImports + } +}; + +export default plugin; diff --git a/src/lintplugin/src/rules/__tests__/typeimports.test.ts b/src/lintplugin/src/rules/__tests__/typeimports.test.ts new file mode 100644 index 0000000000..a17ef96cf7 --- /dev/null +++ b/src/lintplugin/src/rules/__tests__/typeimports.test.ts @@ -0,0 +1,45 @@ +import { RuleTester } from 'eslint'; +import collateTypeImports from '../typeimports'; + +describe('Test collateTypeImports', () => { + const tester = new RuleTester({ + 'languageOptions': { + // eslint-disable-next-line @typescript-eslint/no-require-imports + parser: require('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } + }, + }); + + tester.run( + 'collate-type-imports', + collateTypeImports, + { + valid: [ + 'import type { a, b } from "wherever"', + '', + 'import { type a, b } from "wherever"', + 'import { a, b } from "wherever"', + 'import a, { type b } from "wherever"', + 'import type { a as b } from "wherever"', + 'import { type a as b, c } from "wherever"', + 'import "wherever"', + ], + invalid: [{ + code: 'import { type a, type b } from "wherever"', + errors: 1, + output: 'import type { a, b } from \'wherever\'' + }, { + code: 'import { type a } from "wherever"', + errors: 1, + output: 'import type { a } from \'wherever\'' + }, { + code: 'import { type a as b } from "wherever"', + errors: 1, + output: "import type { a as b } from 'wherever'" + }] + } + ); +}); diff --git a/src/lintplugin/src/rules/typeimports.ts b/src/lintplugin/src/rules/typeimports.ts new file mode 100644 index 0000000000..774e578084 --- /dev/null +++ b/src/lintplugin/src/rules/typeimports.ts @@ -0,0 +1,50 @@ +import type { Rule } from 'eslint'; +import type es from 'estree'; + +function isImportSpecifier(spec: es.ImportDeclaration['specifiers'][number]): spec is es.ImportSpecifier { + return spec.type === 'ImportSpecifier'; +} + +function specToString(spec: es.ImportSpecifier) { + if (spec.imported.type === 'Identifier') { + if (spec.imported.name === spec.local.name) { + return spec.imported.name; + } + return `${spec.imported.name} as ${spec.local.name}`; + } + return ''; +} + +const collateTypeImports = { + meta: { + type: 'suggestion', + fixable: 'code' + }, + create: context => ({ + ImportDeclaration(node) { + // @ts-expect-error import kind is unknown property + if (node.importKind === 'type' || node.specifiers.length === 0) return; + + // @ts-expect-error import kind is unknown property + if (node.specifiers.some(spec => !isImportSpecifier(spec) || spec.importKind !== 'type')) return; + + context.report({ + node, + message: 'Use a single type specifier', + fix(fixer) { + const regularSpecs = (node.specifiers as es.ImportSpecifier[]).map(specToString); + + return [ + fixer.remove(node), + fixer.insertTextAfter( + node, + `import type { ${regularSpecs.join(', ')} } from '${node.source.value}'` + ) + ]; + } + }); + } + }) +} satisfies Rule.RuleModule; + +export default collateTypeImports; diff --git a/src/lintplugin/tsconfig.json b/src/lintplugin/tsconfig.json new file mode 100644 index 0000000000..c8101e9f35 --- /dev/null +++ b/src/lintplugin/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "declaration": true, + "isolatedModules": true, + "module": "ESNext", + "moduleResolution": "bundler", + "outDir": "dist" + }, + "include": ["./src"] +} \ No newline at end of file From b59f345e74496290b9d972248360d0d649282bdf Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 13 May 2025 15:31:21 -0400 Subject: [PATCH 006/112] modules --- src/buildtools/src/build/modules/bundle.ts | 32 ++++++++++ src/buildtools/src/build/modules/commons.ts | 63 ++++++++++++++++++ src/buildtools/src/build/modules/index.ts | 21 ++++++ .../src/build/modules/manifest.schema.json | 28 ++++++++ src/buildtools/src/build/modules/manifest.ts | 64 +++++++++++++++++++ src/buildtools/src/build/modules/tab.ts | 52 +++++++++++++++ 6 files changed, 260 insertions(+) create mode 100644 src/buildtools/src/build/modules/bundle.ts create mode 100644 src/buildtools/src/build/modules/commons.ts create mode 100644 src/buildtools/src/build/modules/index.ts create mode 100644 src/buildtools/src/build/modules/manifest.schema.json create mode 100644 src/buildtools/src/build/modules/manifest.ts create mode 100644 src/buildtools/src/build/modules/tab.ts diff --git a/src/buildtools/src/build/modules/bundle.ts b/src/buildtools/src/build/modules/bundle.ts new file mode 100644 index 0000000000..c74392c462 --- /dev/null +++ b/src/buildtools/src/build/modules/bundle.ts @@ -0,0 +1,32 @@ +import fs from 'fs/promises'; +import { build as esbuild } from 'esbuild'; +import { commonEsbuildOptions, outputBundleOrTab } from './commons'; +import type { BundleManifest } from './manifest'; + +export async function getBundleEntryPoint(bundleDir: string) { + let bundlePath = `${bundleDir}/src/index.ts` + + try { + await fs.access(bundlePath, fs.constants.R_OK) + return bundlePath + } catch (error) { + bundlePath = `${bundleDir}/index.ts` + await fs.access(bundlePath, fs.constants.R_OK) + return bundlePath + } +} + +/** + * Build a bundle at the given directory + */ +export async function buildBundle(bundleDir: string, manifest: BundleManifest, outDir: string) { + const entryPoint = await getBundleEntryPoint(bundleDir) + const { outputFiles: [result] } = await esbuild({ + ...commonEsbuildOptions, + entryPoints: [entryPoint], + tsconfig: `${bundleDir}/tsconfig.json`, + outfile: `/bundle/${manifest.name}`, + }); + + await outputBundleOrTab(result, outDir); +} diff --git a/src/buildtools/src/build/modules/commons.ts b/src/buildtools/src/build/modules/commons.ts new file mode 100644 index 0000000000..313089be54 --- /dev/null +++ b/src/buildtools/src/build/modules/commons.ts @@ -0,0 +1,63 @@ +import fs from 'fs/promises'; +import pathlib from 'path'; +import { parse } from 'acorn'; +import { generate } from 'astring'; +import type { BuildOptions as ESBuildOptions, OutputFile } from 'esbuild'; +import type es from 'estree'; + +export const commonEsbuildOptions: ESBuildOptions = { + bundle: true, + format: 'iife', + define: { + process: JSON.stringify({ + env: { + NODE_ENV: 'production' + } + }) + }, + external: ['js-slang*'], + globalName: 'module', + platform: 'browser', + target: 'es6', + write: false +}; + +export async function outputBundleOrTab({ path, text }: OutputFile, outDir: string) { + const [, type, name] = path.split(pathlib.posix.sep) + const parsed = parse(text, { ecmaVersion: 6 }) as es.Program; + + // Account for 'use strict'; directives + let declStatement: es.VariableDeclaration; + if (parsed.body[0].type === 'VariableDeclaration') { + declStatement = parsed.body[0]; + } else { + declStatement = parsed.body[1] as es.VariableDeclaration; + } + + const { init: callExpression } = declStatement.declarations[0]; + if (callExpression.type !== 'CallExpression') { + throw new Error(`Expected a CallExpression, got ${callExpression.type}`); + } + + const moduleCode = callExpression.callee; + + if (moduleCode.type !== 'FunctionExpression' && moduleCode.type !== 'ArrowFunctionExpression') { + throw new Error(`Expected a function, got ${moduleCode.type}`); + } + + const output: es.ExportDefaultDeclaration = { + type: 'ExportDefaultDeclaration', + declaration: { + ...moduleCode, + params: [{ + type: 'Identifier', + name: 'require' + }] + } + }; + + await fs.mkdir(`${outDir}/${type}`, { recursive: true }) + const file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); + const writeStream = file.createWriteStream(); + generate(output, { output: writeStream }); +} diff --git a/src/buildtools/src/build/modules/index.ts b/src/buildtools/src/build/modules/index.ts new file mode 100644 index 0000000000..7842c554c0 --- /dev/null +++ b/src/buildtools/src/build/modules/index.ts @@ -0,0 +1,21 @@ +import fs from 'fs/promises'; +import pathlib from 'path'; +import { buildBundle } from './bundle'; +import { getBundleManifests } from './manifest'; + +/** + * Search the given directory for valid bundles, then build and write + * them to the given output directory, along with the modules manifest + */ +export async function buildBundles(directory: string, outDir: string) { + const manifests = await getBundleManifests(directory) + await Promise.all(Object.values(manifests).map(async manifest => { + const fullPath = pathlib.join(directory, manifest.name) + await buildBundle(fullPath, manifest, outDir); + })) + + await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(manifests)); +} + +export { buildBundle }; +export { buildTab } from './tab'; \ No newline at end of file diff --git a/src/buildtools/src/build/modules/manifest.schema.json b/src/buildtools/src/build/modules/manifest.schema.json new file mode 100644 index 0000000000..b8279c37a3 --- /dev/null +++ b/src/buildtools/src/build/modules/manifest.schema.json @@ -0,0 +1,28 @@ +{ + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of your bundle", + "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$" + }, + "tabs": { + "description": "Tabs that will be loaded with this bundle", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "type": "string", + "description": "Version of your bundle" + }, + "requires": { + "enum": [1, 2, 3, 4] + }, + "additionalProperties": false + }, + "required": ["name"] + } +} \ No newline at end of file diff --git a/src/buildtools/src/build/modules/manifest.ts b/src/buildtools/src/build/modules/manifest.ts new file mode 100644 index 0000000000..f800e35f4e --- /dev/null +++ b/src/buildtools/src/build/modules/manifest.ts @@ -0,0 +1,64 @@ +import fs from 'fs/promises'; +import pathlib from 'path'; +import { validate } from 'jsonschema'; +import manifestSchema from './manifest.schema.json' with { type: 'json' }; + +export interface BundleManifest { + name: string + version?: string + tabs?: string[] +} + +export type ModulesManifest = { + [name: string]: BundleManifest +} + +/** + * Checks that the given bundle manifest was correctly specified + */ +export async function getBundleManifest(manifestFile: string, tabCheck?: boolean): Promise { + const data = JSON.parse(await fs.readFile(manifestFile, 'utf-8')) as BundleManifest; + const { valid, errors } = validate(data, manifestSchema); + + if (!valid) throw errors; + + // Make sure that all the tabs specified exist + if (tabCheck && data.tabs) { + await Promise.all(data.tabs.map(async tabName => { + try { + await fs.access(`./src/tabs/${tabName}`, fs.constants.R_OK); + } catch { + throw new Error(`Failed to find tab with name '${tabName}'!`); + } + })); + } + + return data; +} + +export function getEmptyManifest(bundleName: string): BundleManifest { + return { name: bundleName }; +} + +export async function getBundleManifests(bundlesDir: string): Promise { + const subdirs = await fs.readdir(bundlesDir) + const manifests = await Promise.all(subdirs.map(async fileName => { + const fullPath = pathlib.join(bundlesDir, fileName); + const stats = await fs.stat(fullPath); + if (stats.isDirectory()) { + const manifest = await getBundleManifest(`${fullPath}/manifest.json`); + return manifest; + } + + return undefined + })) + + return manifests.reduce((res, manifest) => { + if (manifest === undefined) return res; + + return { + ...res, + [manifest.name]: manifest + }; + }, {} as Record); +} \ No newline at end of file diff --git a/src/buildtools/src/build/modules/tab.ts b/src/buildtools/src/build/modules/tab.ts new file mode 100644 index 0000000000..77e545ab8f --- /dev/null +++ b/src/buildtools/src/build/modules/tab.ts @@ -0,0 +1,52 @@ +import fs from 'fs/promises'; +import pathlib from 'path' +import { build as esbuild, type Plugin as ESBuildPlugin } from 'esbuild'; +import { commonEsbuildOptions, outputBundleOrTab } from './commons'; + +const tabContextPlugin: ESBuildPlugin = { + name: 'Tab Context', + setup(build) { + build.onResolve({ filter: /^js-slang\/context/u }, () => ({ + errors: [{ + text: 'If you see this message, it means that your tab code is importing js-slang/context directly or indirectly. Do not do this' + }] + })); + } +}; + +export async function getTabEntryPoint(tabDir: string) { + let tabPath = `${tabDir}/src/index.tsx` + try { + await fs.access(tabPath, fs.constants.R_OK) + return tabPath + } catch { + tabPath = `${tabPath}/index.tsx` + await fs.access(tabPath, fs.constants.R_OK) + return tabPath + } +} + +/** + * Build a tab at the given directory + */ +export async function buildTab(tabDir: string, outDir: string) { + let tabPath = await getTabEntryPoint(tabDir) + const tabName = pathlib.basename(tabDir) + + const { outputFiles: [result]} = await esbuild({ + ...commonEsbuildOptions, + entryPoints: [tabPath], + external: [ + ...commonEsbuildOptions.external, + 'react', + 'react-ace', + 'react-dom', + 'react/jsx-runtime', + '@blueprintjs/*' + ], + outfile:`/tabs/${tabName}`, + tsconfig: `${tabDir}/tsconfig.json`, + plugins: [tabContextPlugin] + }); + await outputBundleOrTab(result, outDir); +} From 5275cc0255f5b9b02b3e5f9aef18bf7864688c6e Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 13 May 2025 17:30:35 -0400 Subject: [PATCH 007/112] Added working commands --- .../src/commands/__tests__/testing.ts | 28 ++++++++ src/buildtools/src/commands/build.ts | 24 +++++++ src/buildtools/src/commands/index.ts | 3 + src/buildtools/src/commands/main.ts | 9 +++ src/buildtools/src/commands/template.ts | 41 +++++++++++ src/buildtools/src/commands/testing.ts | 18 +++++ src/buildtools/src/commands/utils.ts | 71 +++++++++++++++++++ 7 files changed, 194 insertions(+) create mode 100644 src/buildtools/src/commands/__tests__/testing.ts create mode 100644 src/buildtools/src/commands/build.ts create mode 100644 src/buildtools/src/commands/index.ts create mode 100644 src/buildtools/src/commands/main.ts create mode 100644 src/buildtools/src/commands/template.ts create mode 100644 src/buildtools/src/commands/testing.ts create mode 100644 src/buildtools/src/commands/utils.ts diff --git a/src/buildtools/src/commands/__tests__/testing.ts b/src/buildtools/src/commands/__tests__/testing.ts new file mode 100644 index 0000000000..86fb1c5f71 --- /dev/null +++ b/src/buildtools/src/commands/__tests__/testing.ts @@ -0,0 +1,28 @@ +import type { MockedFunction } from 'jest-mock'; +import getTestCommand from '../testing'; +import * as runner from '../../testing/runner'; + +jest.spyOn(runner, 'runJest') + .mockImplementation(jest.fn()); + +const runCommand = (...args: string[]) => getTestCommand() + .parseAsync(args, { from: 'user' }); +const mockRunJest = runner.runJest as MockedFunction; + +test('Check that the test command properly passes options to jest', async () => { + await runCommand('-u', '-w', '--srcDir', 'gg', './src/folder'); + + const [call] = mockRunJest.mock.calls; + expect(call[0]) + .toEqual(['-u', '-w', './src/folder']); + expect(call[1]) + .toEqual('gg'); +}); + +test('Check that the test command handles windows paths as posix paths', async () => { + await runCommand('.\\src\\folder'); + + const [call] = mockRunJest.mock.calls; + expect(call[0]) + .toEqual(['./src/folder']); +}); diff --git a/src/buildtools/src/commands/build.ts b/src/buildtools/src/commands/build.ts new file mode 100644 index 0000000000..b63a0088a5 --- /dev/null +++ b/src/buildtools/src/commands/build.ts @@ -0,0 +1,24 @@ +import pathlib from 'path' +import { Command } from "@commander-js/extra-typings"; +import { buildBundle } from "../build/modules/bundle"; +import { getBundleManifest } from "../build/modules/manifest"; +import { buildTab } from '../build/modules/tab'; +import { getGitRoot } from '../utils'; + +const outDir = pathlib.join(await getGitRoot(), 'build') +export const getBuildBundleCommand = () => new Command('bundle') + .argument('', 'Directory in which the bundle\'s source files are located') + .action(async bundleDir => { + const manifest = await getBundleManifest(`${bundleDir}/manifest.json`) + await buildBundle(bundleDir, manifest, outDir) + }) + +export const getBuildTabCommand = () => new Command('tab') + .argument('', 'Directory in which the tab\'s source files are located') + .action(async tabDir => { + await buildTab(tabDir, outDir) + }) + +export const getBuildCommand = () => new Command('build') + .addCommand(getBuildBundleCommand()) + .addCommand(getBuildTabCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/index.ts b/src/buildtools/src/commands/index.ts new file mode 100644 index 0000000000..c0b3b96360 --- /dev/null +++ b/src/buildtools/src/commands/index.ts @@ -0,0 +1,3 @@ +import { getMainCommand } from "./main"; + +await getMainCommand().parseAsync() \ No newline at end of file diff --git a/src/buildtools/src/commands/main.ts b/src/buildtools/src/commands/main.ts new file mode 100644 index 0000000000..32c7c00746 --- /dev/null +++ b/src/buildtools/src/commands/main.ts @@ -0,0 +1,9 @@ +import { Command } from "@commander-js/extra-typings"; +import { getBuildCommand } from "./build"; +import getTemplateCommand from "./template"; +import getTestCommand from "./testing"; + +export const getMainCommand = () => new Command() + .addCommand(getBuildCommand()) + .addCommand(getTemplateCommand()) + .addCommand(getTestCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/template.ts b/src/buildtools/src/commands/template.ts new file mode 100644 index 0000000000..48205378c7 --- /dev/null +++ b/src/buildtools/src/commands/template.ts @@ -0,0 +1,41 @@ +import type { Interface } from 'readline/promises'; +import { Command } from '@commander-js/extra-typings'; + +import { addNew as addNewModule } from '../templates/bundle'; +import { error as _error, askQuestion, getRl, info, warn } from '../templates/print'; +import { addNew as addNewTab } from '../templates/tab'; +import { getGitRoot } from '../utils'; +import path from 'path'; + +async function askMode(rl: Interface) { + while (true) { + const mode = await askQuestion('What would you like to create? (module/tab)', rl); + if (mode !== 'module' && mode !== 'tab') { + warn("Please answer with only 'module' or 'tab'."); + } else { + return mode; + } + } +} + +export default function getTemplateCommand() { + return new Command('template') + .description('Interactively create a new module or tab') + .action(async () => { + const gitRoot = await getGitRoot() + const bundlesDir = path.join(gitRoot, 'src', 'bundles') + const tabsDir = path.join(gitRoot, 'src', 'tabs') + + const rl = getRl(); + try { + const mode = await askMode(rl); + if (mode === 'module') await addNewModule(bundlesDir, rl); + else if (mode === 'tab') await addNewTab(bundlesDir, tabsDir, rl); + } catch (error) { + _error(`ERROR: ${error.message}`); + info('Terminating module app...'); + } finally { + rl.close(); + } + }); +} diff --git a/src/buildtools/src/commands/testing.ts b/src/buildtools/src/commands/testing.ts new file mode 100644 index 0000000000..a39e257dec --- /dev/null +++ b/src/buildtools/src/commands/testing.ts @@ -0,0 +1,18 @@ +import pathlib from 'path'; +import { Command } from '@commander-js/extra-typings'; + +import { runJest } from '../testing/runner'; + +const getTestCommand = () => new Command('test') + .description('Run jest') + .allowUnknownOption() + .argument('[patterns...]') + .action((patterns, args: Record) => { + const jestArgs = [ + ...Object.entries(args).flat(), + ...patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)) + ] + return runJest(jestArgs); + }); + +export default getTestCommand; diff --git a/src/buildtools/src/commands/utils.ts b/src/buildtools/src/commands/utils.ts new file mode 100644 index 0000000000..b460d63170 --- /dev/null +++ b/src/buildtools/src/commands/utils.ts @@ -0,0 +1,71 @@ +import { Option } from '@commander-js/extra-typings'; + +class OptionNew< + UsageT extends string = '', + PresetT = undefined, + DefaultT = undefined, + CoerceT = undefined, + Mandatory extends boolean = false, + ChoicesT = undefined +> + extends Option { + default(value: T, description?: string): Option { + return super.default(value, description); + } +} + +export const srcDirOption = new OptionNew('--srcDir ', 'Location of the source files') + .default('src'); + +export const outDirOption = new OptionNew('--outDir ', 'Location of output directory') + .default('build'); + +export const manifestOption = new OptionNew('--manifest ', 'Location of manifest') + .default('modules.json'); + +export const lintOption = new OptionNew('--lint', 'Run ESLint'); + +export const lintFixOption = new OptionNew('--fix', 'Fix automatically fixable linting errors') + .implies({ lint: true }); + +export const bundlesOption = new OptionNew('-b, --bundles ', 'Manually specify which bundles') + .default(null); + +export const tabsOption = new OptionNew('-t, --tabs ', 'Manually specify which tabs') + .default(null); + +export function promiseAll[]>(...args: T): Promise<{ [K in keyof T]: Awaited }> { + return Promise.all(args); +} + +export interface TimedResult { + result: T + elapsed: number +} + +// export function wrapWithTimer Promise>(func: T) { +// return async (...args: Parameters): Promise>> => { +// const startTime = performance.now(); +// const result = await func(...args); +// return { +// result, +// elapsed: performance.now() - startTime +// }; +// }; +// } + +type ValuesOfRecord = T extends Record ? U : never; + +export type EntriesOfRecord> = ValuesOfRecord<{ + [K in keyof T]: [K, T[K]] +}>; + +export function objectEntries>(obj: T) { + return Object.entries(obj) as EntriesOfRecord[]; +} + +export interface BuildInputs { + bundles?: string[] | null; + tabs?: string[] | null; + modulesSpecified?: boolean; +} From aa889e64bcc6460c1df132645b4493d0fca931f2 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 13 May 2025 17:30:58 -0400 Subject: [PATCH 008/112] Added templates --- .../src/templates/__tests__/names.test.ts | 22 +++ .../src/templates/__tests__/template.test.ts | 156 ++++++++++++++++++ src/buildtools/src/templates/bundle.ts | 57 +++++++ src/buildtools/src/templates/print.ts | 27 +++ src/buildtools/src/templates/tab.ts | 81 +++++++++ .../templates/templates/bundle/src/index.ts | 25 +++ .../templates/templates/bundle/tsconfig.json | 4 + .../src/templates/templates/tab/index.tsx | 71 ++++++++ .../src/templates/templates/tab/tsconfig.json | 4 + src/buildtools/src/templates/utilities.ts | 20 +++ 10 files changed, 467 insertions(+) create mode 100644 src/buildtools/src/templates/__tests__/names.test.ts create mode 100644 src/buildtools/src/templates/__tests__/template.test.ts create mode 100644 src/buildtools/src/templates/bundle.ts create mode 100644 src/buildtools/src/templates/print.ts create mode 100644 src/buildtools/src/templates/tab.ts create mode 100644 src/buildtools/src/templates/templates/bundle/src/index.ts create mode 100644 src/buildtools/src/templates/templates/bundle/tsconfig.json create mode 100644 src/buildtools/src/templates/templates/tab/index.tsx create mode 100644 src/buildtools/src/templates/templates/tab/tsconfig.json create mode 100644 src/buildtools/src/templates/utilities.ts diff --git a/src/buildtools/src/templates/__tests__/names.test.ts b/src/buildtools/src/templates/__tests__/names.test.ts new file mode 100644 index 0000000000..633cca5014 --- /dev/null +++ b/src/buildtools/src/templates/__tests__/names.test.ts @@ -0,0 +1,22 @@ +import { isPascalCase, isSnakeCase } from '../utilities'; + +function testFunction( + func: (value: string) => boolean, + tcs: [string, boolean][] +) { + describe(`Testing ${func.name}`, () => test.each(tcs)('%#: %s', (value, valid) => expect(func(value)) + .toEqual(valid))); +} + +testFunction(isPascalCase, [ + ['PascalCase', true], + ['snake_case', false], + ['Snake_Case', false] +]); + +testFunction(isSnakeCase, [ + ['snake_case', true], + ['arcade_2d', true], + ['PascalCase', false], + ['camelCase', false] +]); diff --git a/src/buildtools/src/templates/__tests__/template.test.ts b/src/buildtools/src/templates/__tests__/template.test.ts new file mode 100644 index 0000000000..9b2e5adec7 --- /dev/null +++ b/src/buildtools/src/templates/__tests__/template.test.ts @@ -0,0 +1,156 @@ +import fs from 'fs/promises'; +import type { MockedFunction } from 'jest-mock'; + +import getTemplateCommand from '..'; +import { askQuestion } from '../print'; + +jest.mock('../print', () => ({ + ...jest.requireActual('../print'), + askQuestion: jest.fn(), + error(x: string) { + // The command has a catch-all for errors, + // so we rethrow the error to observe the value + throw new Error(x); + }, + // Because the functions run in perpetual while loops + // We need to throw an error to observe what value warn + // was called with + warn(x: string) { + throw new Error(x); + } +})); + +const asMock = any>(func: T) => func as MockedFunction; + +const mockedAskQuestion = asMock(askQuestion); + +function runCommand(...args: string[]) { + return getTemplateCommand() + .parseAsync(args, { from: 'user' }); +} + +function expectCall any>( + func: T, + ...expected: Parameters[]) { + const mocked = asMock(func); + + expect(func) + .toHaveBeenCalledTimes(expected.length); + + mocked.mock.calls.forEach((actual, i) => { + expect(actual) + .toEqual(expected[i]); + }); +} + +async function expectCommandFailure(snapshot: string) { + await expect(runCommand()) + .rejects + // eslint-disable-next-line jest/no-interpolation-in-snapshots + .toMatchInlineSnapshot(`[Error: ERROR: ${snapshot}]`); + + expect(fs.writeFile).not.toHaveBeenCalled(); + expect(fs.copyFile).not.toHaveBeenCalled(); + expect(fs.mkdir).not.toHaveBeenCalled(); +} + +describe('Test adding new module', () => { + beforeEach(() => { + mockedAskQuestion.mockResolvedValueOnce('module'); + }); + + it('should require camel case for module names', async () => { + mockedAskQuestion.mockResolvedValueOnce('camelCase'); + await expectCommandFailure('Module names must be in snake case. (eg. binary_tree)'); + }); + + it('should check for existing modules', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + await expectCommandFailure('A module with the same name already exists.'); + }); + + test('successfully adding a new module', async () => { + mockedAskQuestion.mockResolvedValueOnce('new_module'); + await runCommand(); + + expectCall( + fs.mkdir, + ['src/bundles/new_module', { recursive: true }] + ); + + expectCall( + fs.copyFile, + [ + './scripts/src/templates/templates/__bundle__.ts', + 'src/bundles/new_module/index.ts' + ] + ); + + const oldManifest = await retrieveManifest('modules.json'); + const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; + expect(manifestPath) + .toEqual('modules.json'); + + expect(JSON.parse(newManifest as string)) + .toMatchObject({ + ...oldManifest, + new_module: { tabs: [] } + }); + }); +}); + +describe('Test adding new tab', () => { + beforeEach(() => { + mockedAskQuestion.mockResolvedValueOnce('tab'); + }); + + it('should require pascal case for tab names', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + mockedAskQuestion.mockResolvedValueOnce('unknown_tab'); + await expectCommandFailure('Tab names must be in pascal case. (eg. BinaryTree)'); + }); + + it('should check if the given tab already exists', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + mockedAskQuestion.mockResolvedValueOnce('tab0'); + await expectCommandFailure('A tab with the same name already exists.'); + }); + + it('should check if the given module exists', async () => { + mockedAskQuestion.mockResolvedValueOnce('unknown_module'); + await expectCommandFailure('Module unknown_module does not exist.'); + }); + + test('Successfully adding new tab', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + mockedAskQuestion.mockResolvedValueOnce('TabNew'); + + await runCommand(); + + expectCall( + fs.mkdir, + ['src/tabs/TabNew', { recursive: true }] + ); + + expectCall( + fs.copyFile, + [ + './scripts/src/templates/templates/__tab__.tsx', + 'src/tabs/TabNew/index.tsx' + ] + ); + + const oldManifest = await retrieveManifest('modules.json'); + const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; + expect(manifestPath) + .toEqual('modules.json'); + + expect(JSON.parse(newManifest as string)) + .toMatchObject({ + ...oldManifest, + test0: { + tabs: ['tab0', 'TabNew'] + } + }); + }); +}); diff --git a/src/buildtools/src/templates/bundle.ts b/src/buildtools/src/templates/bundle.ts new file mode 100644 index 0000000000..3b9d2b08de --- /dev/null +++ b/src/buildtools/src/templates/bundle.ts @@ -0,0 +1,57 @@ +import fs from 'fs/promises'; +import type { Interface } from 'readline/promises'; +import { askQuestion, success, warn } from './print'; +import { check, isSnakeCase } from './utilities'; +import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/modules/manifest'; + +async function askModuleName(manifest: ModulesManifest, rl: Interface) { + while (true) { + const name = await askQuestion('What is the name of your new module? (eg. binary_tree)', rl); + if (!isSnakeCase(name)) { + warn('Module names must be in snake case. (eg. binary_tree)'); + } else if (check(manifest, name)) { + warn('A module with the same name already exists.'); + } else { + return name; + } + } +} + +export async function addNew(bundlesDir: string, rl: Interface) { + const manifest = await getBundleManifests(bundlesDir) + const moduleName = await askModuleName(manifest, rl); + const bundleDestination = `${bundlesDir}/${moduleName}`; + await fs.cp('./src/templates/templates/bundle', bundleDestination) + + const packageJson = { + name: `@sourceacademy/bundle-${moduleName}`, + private: true, + version: "1.0.0", + devDependencies: { + "@sourceacademy/module-buildtools": "workspace:^", + "typescript": "^5.8.2" + }, + type: "module", + scripts: { + tsc: "tsc --project ./tsconfig.json", + build: 'buildtools build bundle .' + }, + exports: { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + } + } + + const bundleManifest: BundleManifest = { + name: moduleName, + tabs: [] + } + + await Promise.all([ + fs.writeFile(`${bundleDestination}/package.json`, JSON.stringify(packageJson, null, 2)), + fs.writeFile(`${bundleDestination}/manifest.json`, JSON.stringify(bundleManifest, null, 2)) + ]) + + success(`Bundle for module ${moduleName} created at ${bundleDestination}.`); +} diff --git a/src/buildtools/src/templates/print.ts b/src/buildtools/src/templates/print.ts new file mode 100644 index 0000000000..f358a2fc66 --- /dev/null +++ b/src/buildtools/src/templates/print.ts @@ -0,0 +1,27 @@ +import { type Interface, createInterface } from 'readline/promises'; +import chalk from 'chalk'; + +export const getRl = () => createInterface({ + input: process.stdin, + output: process.stdout +}); + +export function info(...args: any[]) { + return console.log(...args.map(string => chalk.grey(string))); +} + +export function error(...args) { + return console.log(...args.map(string => chalk.red(string))); +} + +export function warn(...args) { + return console.log(...args.map(string => chalk.yellow(string))); +} + +export function success(...args) { + return console.log(...args.map(string => chalk.green(string))); +} + +export function askQuestion(question: string, rl: Interface) { + return rl.question(chalk.blueBright(`${question}\n`)); +} diff --git a/src/buildtools/src/templates/tab.ts b/src/buildtools/src/templates/tab.ts new file mode 100644 index 0000000000..17f2592097 --- /dev/null +++ b/src/buildtools/src/templates/tab.ts @@ -0,0 +1,81 @@ +import fs from 'fs/promises'; +import type { Interface } from 'readline/promises'; +import { askQuestion, success, warn } from './print'; +import { check, isPascalCase } from './utilities'; +import { getBundleManifest, getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/modules/manifest'; + +async function askModuleName(manifest: ModulesManifest, rl: Interface) { + while (true) { + const name = await askQuestion('Add a new tab to which module?', rl); + if (!check(manifest, name)) { + warn(`Module ${name} does not exist.`); + } else { + return name; + } + } +} + +function checkTabExists(manifest: ModulesManifest, name: string) { + return Object.values(manifest).flatMap(x => x.tabs).includes(name) +} + +async function askTabName(manifest: ModulesManifest, rl: Interface) { + while (true) { + const name = await askQuestion('What is the name of your new tab? (eg. BinaryTree)', rl); + if (checkTabExists(manifest, name)) { + warn('A tab with the same name already exists.'); + } else if (!isPascalCase(name)) { + warn('Tab names must be in pascal case. (eg. BinaryTree)'); + } else { + return name; + } + } +} + +export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) { + const manifest = await getBundleManifests(bundlesDir) + const moduleName = await askModuleName(manifest, rl); + const tabName = await askTabName(manifest, rl); + + // Copy module tab template into correct destination and show success message + const tabDestination = `${tabsDir}/${tabName}`; + await fs.mkdir(tabDestination, { recursive: true }); + + const packageJson = { + name: `@sourceacademy/tab-${tabName}`, + private: true, + version: "1.0.0", + devDependencies: { + "@sourceacademy/module-buildtools": "workspace:^", + "@types/react": "^18.3.1", + "typescript": "^5.8.2" + }, + dependencies: { + react: "^18.3.1" + }, + scripts: { + "build": "buildtools build tab ." + } + } + + await fs.cp('buildtools/src/templates/templates/tabs', tabDestination) + await Promise.all([ + fs.writeFile(`${tabDestination}/package.json`, JSON.stringify(packageJson, null, 2)), + getBundleManifest(`${bundlesDir}/${moduleName}`) + .then(async bundleManifest => { + const newManifest: BundleManifest = { + ...bundleManifest, + tabs: [ + ...bundleManifest.tabs, + tabName + ] + } + + await fs.writeFile(`${bundlesDir}/${moduleName}/manifest.json`, JSON.stringify(newManifest, null, 2)) + }) + ]) + + success( + `Tab ${tabName} for module ${moduleName} created at ${tabDestination}.` + ); +} diff --git a/src/buildtools/src/templates/templates/bundle/src/index.ts b/src/buildtools/src/templates/templates/bundle/src/index.ts new file mode 100644 index 0000000000..297cca4d76 --- /dev/null +++ b/src/buildtools/src/templates/templates/bundle/src/index.ts @@ -0,0 +1,25 @@ +/** + * A single sentence summarising the module (this sentence is displayed larger). + * + * Sentences describing the module. More sentences about the module. + * + * @module module_name + * @author Author Name + * @author Author Name + */ + +/* + To access things like the context or module state you can just import the context + using the import below + */ +import context from 'js-slang/context'; + +/** + * Sample function. Increments a number by 1. + * + * @param x The number to be incremented. + * @returns The incremented value of the number. + */ +export function sample_function(x: number): number { + return ++x; +} // Then any functions or variables you want to expose to the user is exported from the bundle's index.ts file \ No newline at end of file diff --git a/src/buildtools/src/templates/templates/bundle/tsconfig.json b/src/buildtools/src/templates/templates/bundle/tsconfig.json new file mode 100644 index 0000000000..2536da7baf --- /dev/null +++ b/src/buildtools/src/templates/templates/bundle/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends" : "../tsconfig.json", + "include": ["./src"] +} \ No newline at end of file diff --git a/src/buildtools/src/templates/templates/tab/index.tsx b/src/buildtools/src/templates/templates/tab/index.tsx new file mode 100644 index 0000000000..34eb95b091 --- /dev/null +++ b/src/buildtools/src/templates/templates/tab/index.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +/** + * + * @author + * @author + */ + +/** + * React Component props for the Tab. + */ +type Props = { + children?: never; + className?: never; + context?: any; +}; + +/** + * React Component state for the Tab. + */ +type State = { + counter: number; +}; + +/** + * The main React Component of the Tab. + */ +class Repeat extends React.Component { + constructor(props) { + super(props); + this.state = { + counter: 0, + }; + } + + public render() { + const { counter } = this.state; + return ( +
This is spawned from the repeat package. Counter is {counter}
+ ); + } +} + +export default { + /** + * This function will be called to determine if the component will be + * rendered. Currently spawns when the result in the REPL is "test". + * @param {DebuggerContext} context + * @returns {boolean} + */ + toSpawn: (context: any) => context.result.value === 'test', + + /** + * This function will be called to render the module tab in the side contents + * on Source Academy frontend. + * @param {DebuggerContext} context + */ + body: (context: any) => , + + /** + * The Tab's icon tooltip in the side contents on Source Academy frontend. + */ + label: 'Sample Tab', + + /** + * BlueprintJS IconName element's name, used to render the icon which will be + * displayed in the side contents panel. + * @see https://blueprintjs.com/docs/#icons + */ + iconName: 'build', +}; \ No newline at end of file diff --git a/src/buildtools/src/templates/templates/tab/tsconfig.json b/src/buildtools/src/templates/templates/tab/tsconfig.json new file mode 100644 index 0000000000..8e4645929c --- /dev/null +++ b/src/buildtools/src/templates/templates/tab/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["./index.tsx" ] +} \ No newline at end of file diff --git a/src/buildtools/src/templates/utilities.ts b/src/buildtools/src/templates/utilities.ts new file mode 100644 index 0000000000..37d2124b67 --- /dev/null +++ b/src/buildtools/src/templates/utilities.ts @@ -0,0 +1,20 @@ +// Snake case regex has been changed from `/\b[a-z]+(?:_[a-z]+)*\b/u` to `/\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u` +// to be consistent with the naming of the `arcade_2d` and `physics_2d` modules. + +import type { ModulesManifest } from "../build/modules/manifest"; + +// This change should not affect other modules, since the set of possible names is only expanded. +const snakeCaseRegex = /\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u; +const pascalCaseRegex = /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/u; + +export function isSnakeCase(string: string) { + return snakeCaseRegex.test(string); +} + +export function isPascalCase(string: string) { + return pascalCaseRegex.test(string); +} + +export function check(manifest: ModulesManifest, name: string) { + return Object.keys(manifest).includes(name) +} \ No newline at end of file From 754a1de0884382bac3385898fba3e4625d9d2a8b Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 14 May 2025 10:10:28 -0400 Subject: [PATCH 009/112] Add more buildtools --- src/buildtools/src/build/modules/bundle.ts | 21 +++-- src/buildtools/src/build/modules/commons.ts | 4 +- src/buildtools/src/build/modules/index.ts | 6 +- .../src/build/modules/manifest.schema.json | 5 -- src/buildtools/src/build/modules/manifest.ts | 31 ++++--- src/buildtools/src/build/modules/tab.ts | 9 +- src/buildtools/src/prebuild/lint.ts | 39 +++++++++ src/buildtools/src/prebuild/typecheck.ts | 82 +++++++++++++++++++ 8 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 src/buildtools/src/prebuild/lint.ts create mode 100644 src/buildtools/src/prebuild/typecheck.ts diff --git a/src/buildtools/src/build/modules/bundle.ts b/src/buildtools/src/build/modules/bundle.ts index c74392c462..654226c3e5 100644 --- a/src/buildtools/src/build/modules/bundle.ts +++ b/src/buildtools/src/build/modules/bundle.ts @@ -1,7 +1,8 @@ import fs from 'fs/promises'; import { build as esbuild } from 'esbuild'; import { commonEsbuildOptions, outputBundleOrTab } from './commons'; -import type { BundleManifest } from './manifest'; +import pathlib from 'path'; +import { getBundleManifest } from './manifest'; export async function getBundleEntryPoint(bundleDir: string) { let bundlePath = `${bundleDir}/src/index.ts` @@ -19,14 +20,22 @@ export async function getBundleEntryPoint(bundleDir: string) { /** * Build a bundle at the given directory */ -export async function buildBundle(bundleDir: string, manifest: BundleManifest, outDir: string) { - const entryPoint = await getBundleEntryPoint(bundleDir) +export async function buildBundle(bundleDir: string, outDir: string) { + const fullyResolved = pathlib.resolve(bundleDir) + const manifest = await getBundleManifest(fullyResolved) + if (manifest === undefined) { + throw new Error(`Could not find a bundle at ${fullyResolved}`) + } + + const entryPoint = await getBundleEntryPoint(fullyResolved) + const bundleName = pathlib.basename(fullyResolved) + const { outputFiles: [result] } = await esbuild({ ...commonEsbuildOptions, entryPoints: [entryPoint], - tsconfig: `${bundleDir}/tsconfig.json`, - outfile: `/bundle/${manifest.name}`, + tsconfig: `${fullyResolved}/tsconfig.json`, + outfile: `/bundle/${bundleName}`, }); - await outputBundleOrTab(result, outDir); + await outputBundleOrTab(result, bundleName, 'bundle', outDir); } diff --git a/src/buildtools/src/build/modules/commons.ts b/src/buildtools/src/build/modules/commons.ts index 313089be54..b8c5443259 100644 --- a/src/buildtools/src/build/modules/commons.ts +++ b/src/buildtools/src/build/modules/commons.ts @@ -1,5 +1,4 @@ import fs from 'fs/promises'; -import pathlib from 'path'; import { parse } from 'acorn'; import { generate } from 'astring'; import type { BuildOptions as ESBuildOptions, OutputFile } from 'esbuild'; @@ -22,8 +21,7 @@ export const commonEsbuildOptions: ESBuildOptions = { write: false }; -export async function outputBundleOrTab({ path, text }: OutputFile, outDir: string) { - const [, type, name] = path.split(pathlib.posix.sep) +export async function outputBundleOrTab({ text }: OutputFile, name: string, type: 'bundle' | 'tab', outDir: string) { const parsed = parse(text, { ecmaVersion: 6 }) as es.Program; // Account for 'use strict'; directives diff --git a/src/buildtools/src/build/modules/index.ts b/src/buildtools/src/build/modules/index.ts index 7842c554c0..99a181c3c7 100644 --- a/src/buildtools/src/build/modules/index.ts +++ b/src/buildtools/src/build/modules/index.ts @@ -9,9 +9,9 @@ import { getBundleManifests } from './manifest'; */ export async function buildBundles(directory: string, outDir: string) { const manifests = await getBundleManifests(directory) - await Promise.all(Object.values(manifests).map(async manifest => { - const fullPath = pathlib.join(directory, manifest.name) - await buildBundle(fullPath, manifest, outDir); + await Promise.all(Object.keys(manifests).map(async name => { + const fullPath = pathlib.join(directory, name) + await buildBundle(fullPath, outDir); })) await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(manifests)); diff --git a/src/buildtools/src/build/modules/manifest.schema.json b/src/buildtools/src/build/modules/manifest.schema.json index b8279c37a3..c633dca965 100644 --- a/src/buildtools/src/build/modules/manifest.schema.json +++ b/src/buildtools/src/build/modules/manifest.schema.json @@ -2,11 +2,6 @@ "schema": { "type": "object", "properties": { - "name": { - "type": "string", - "description": "Name of your bundle", - "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$" - }, "tabs": { "description": "Tabs that will be loaded with this bundle", "type": "array", diff --git a/src/buildtools/src/build/modules/manifest.ts b/src/buildtools/src/build/modules/manifest.ts index f800e35f4e..69c6987aed 100644 --- a/src/buildtools/src/build/modules/manifest.ts +++ b/src/buildtools/src/build/modules/manifest.ts @@ -4,7 +4,6 @@ import { validate } from 'jsonschema'; import manifestSchema from './manifest.schema.json' with { type: 'json' }; export interface BundleManifest { - name: string version?: string tabs?: string[] } @@ -16,8 +15,19 @@ export type ModulesManifest = { /** * Checks that the given bundle manifest was correctly specified */ -export async function getBundleManifest(manifestFile: string, tabCheck?: boolean): Promise { - const data = JSON.parse(await fs.readFile(manifestFile, 'utf-8')) as BundleManifest; +export async function getBundleManifest(manifestFile: string, tabCheck?: boolean): Promise { + let rawManifest: string + + try { + rawManifest = await fs.readFile(manifestFile, 'utf-8') + } catch (error) { + if (error.message === 'ENOENT') { + return undefined + } + throw error + } + + const data = JSON.parse(rawManifest) as BundleManifest; const { valid, errors } = validate(data, manifestSchema); if (!valid) throw errors; @@ -36,10 +46,6 @@ export async function getBundleManifest(manifestFile: string, tabCheck?: boolean return data; } -export function getEmptyManifest(bundleName: string): BundleManifest { - return { name: bundleName }; -} - export async function getBundleManifests(bundlesDir: string): Promise { const subdirs = await fs.readdir(bundlesDir) const manifests = await Promise.all(subdirs.map(async fileName => { @@ -47,18 +53,21 @@ export async function getBundleManifests(bundlesDir: string): Promise { - if (manifest === undefined) return res; + return manifests.reduce((res, entry) => { + if (entry === undefined) return res; + + const [name, manifest] = entry return { ...res, - [manifest.name]: manifest + [name]: manifest }; }, {} as Record); } \ No newline at end of file diff --git a/src/buildtools/src/build/modules/tab.ts b/src/buildtools/src/build/modules/tab.ts index 77e545ab8f..3d88dab1e8 100644 --- a/src/buildtools/src/build/modules/tab.ts +++ b/src/buildtools/src/build/modules/tab.ts @@ -6,7 +6,7 @@ import { commonEsbuildOptions, outputBundleOrTab } from './commons'; const tabContextPlugin: ESBuildPlugin = { name: 'Tab Context', setup(build) { - build.onResolve({ filter: /^js-slang\/context/u }, () => ({ + build.onResolve({ filter: /^js-slang\/context/ }, () => ({ errors: [{ text: 'If you see this message, it means that your tab code is importing js-slang/context directly or indirectly. Do not do this' }] @@ -31,7 +31,8 @@ export async function getTabEntryPoint(tabDir: string) { */ export async function buildTab(tabDir: string, outDir: string) { let tabPath = await getTabEntryPoint(tabDir) - const tabName = pathlib.basename(tabDir) + const fullyResolved = pathlib.resolve(tabDir) + const tabName = pathlib.basename(fullyResolved) const { outputFiles: [result]} = await esbuild({ ...commonEsbuildOptions, @@ -45,8 +46,8 @@ export async function buildTab(tabDir: string, outDir: string) { '@blueprintjs/*' ], outfile:`/tabs/${tabName}`, - tsconfig: `${tabDir}/tsconfig.json`, + tsconfig: `${fullyResolved}/tsconfig.json`, plugins: [tabContextPlugin] }); - await outputBundleOrTab(result, outDir); + await outputBundleOrTab(result, tabName, 'tab', outDir); } diff --git a/src/buildtools/src/prebuild/lint.ts b/src/buildtools/src/prebuild/lint.ts new file mode 100644 index 0000000000..540857d341 --- /dev/null +++ b/src/buildtools/src/prebuild/lint.ts @@ -0,0 +1,39 @@ +import { ESLint } from 'eslint' +import { findSeverity, type Severity } from '../utils'; + +interface LintResults { + formatted: string + severity: Severity +} + +export async function runEslint(directory: string, fix: boolean): Promise { + const linter = new ESLint({ fix }); + + try { + const linterResults = await linter.lintFiles(directory); + if (fix) { + await ESLint.outputFixes(linterResults); + } + + const outputFormatter = await linter.loadFormatter('stylish'); + const formatted = await outputFormatter.format(linterResults); + const severity = findSeverity(linterResults, ({ warningCount, errorCount, fatalErrorCount }) => { + + if (fatalErrorCount > 0) return 'error' + if (!fix && errorCount > 0) return 'error' + + if (warningCount > 0) return 'warn'; + return 'success'; + }); + + return { + formatted, + severity + }; + } catch (error) { + return { + severity: 'error', + formatted: error.toString() + }; + } +} \ No newline at end of file diff --git a/src/buildtools/src/prebuild/typecheck.ts b/src/buildtools/src/prebuild/typecheck.ts new file mode 100644 index 0000000000..a1d3d2f675 --- /dev/null +++ b/src/buildtools/src/prebuild/typecheck.ts @@ -0,0 +1,82 @@ +import fs from 'fs/promises' +import pathlib from 'path' +import ts from 'typescript' + +type TsconfigResult = { + severity: 'error', + results?: ts.Diagnostic[] + error?: any +} | { + severity: 'success', + results: ts.CompilerOptions +}; + +type TscResult = { + severity: 'error' + results?: ts.Diagnostic[] + error?: any +} | { + severity: 'success', + results: ts.Diagnostic[] +}; + +async function getTsconfig(srcDir: string): Promise { + // Step 1: Read the text from tsconfig.json + const tsconfigLocation = pathlib.join(srcDir, 'tsconfig.json'); + + try { + const configText = await fs.readFile(tsconfigLocation, 'utf-8'); + + // Step 2: Parse the raw text into a json object + const { error: configJsonError, config: configJson } = ts.parseConfigFileTextToJson(tsconfigLocation, configText); + if (configJsonError) { + return { + severity: 'error', + results: [configJsonError] + }; + } + + // Step 3: Parse the json object into a config object for use by tsc + const { errors: parseErrors, options: tsconfig } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); + if (parseErrors.length > 0) { + return { + severity: 'error', + results: parseErrors + }; + } + + return { + severity: 'success', + results: tsconfig + }; + } catch (error) { + return { + severity: 'error', + error + }; + } +} + +export async function runTsc(srcDir: string): Promise { + const tsconfigRes = await getTsconfig(srcDir); + if (tsconfigRes.severity === 'error') { + return tsconfigRes; + } + + try { + const tsc = ts.createProgram([], tsconfigRes.results); + const results = tsc.emit(); + const diagnostics = ts.getPreEmitDiagnostics(tsc) + .concat(results.diagnostics); + + return { + severity: diagnostics.length > 0 ? 'error' : 'success', + results: diagnostics + }; + } catch (error) { + return { + severity: 'error', + error + }; + } +} From fad46267ed25cbf53e2e35d866d1244cb3fe1ea1 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 14 May 2025 11:02:13 -0400 Subject: [PATCH 010/112] Further work on buildtools --- src/buildtools/.gitignore | 1 + src/buildtools/jest.config.js | 16 ++++++++ src/buildtools/jest.setup.ts | 18 +++++++++ src/buildtools/package.json | 32 ++++++++++++++++ src/buildtools/src/commands/build.ts | 21 ++++++++--- src/buildtools/src/commands/main.ts | 5 ++- src/buildtools/src/commands/prebuild.ts | 49 +++++++++++++++++++++++++ src/buildtools/tsconfig.json | 15 ++++++++ 8 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 src/buildtools/.gitignore create mode 100644 src/buildtools/jest.config.js create mode 100644 src/buildtools/jest.setup.ts create mode 100644 src/buildtools/package.json create mode 100644 src/buildtools/src/commands/prebuild.ts create mode 100644 src/buildtools/tsconfig.json diff --git a/src/buildtools/.gitignore b/src/buildtools/.gitignore new file mode 100644 index 0000000000..3f59370baf --- /dev/null +++ b/src/buildtools/.gitignore @@ -0,0 +1 @@ +bin.js \ No newline at end of file diff --git a/src/buildtools/jest.config.js b/src/buildtools/jest.config.js new file mode 100644 index 0000000000..8e4647ed8f --- /dev/null +++ b/src/buildtools/jest.config.js @@ -0,0 +1,16 @@ +/** + * @type {import('jest').Config} + */ +const jestConfig = { + clearMocks: true, + displayName: 'Build Tools', + extensionsToTreatAsEsm: ['.ts'], + testEnvironment: 'node', + preset: 'ts-jest/presets/default-esm', + setupFilesAfterEnv: ['/jest.setup.ts'], + testMatch: [ + '/src/**/__tests__/**/*.test.ts', + ], +}; + +export default jestConfig; diff --git a/src/buildtools/jest.setup.ts b/src/buildtools/jest.setup.ts new file mode 100644 index 0000000000..412b8c6459 --- /dev/null +++ b/src/buildtools/jest.setup.ts @@ -0,0 +1,18 @@ +const chalkFunction = new Proxy((x: string) => x, { + get: () => chalkFunction +}); + +jest.mock('chalk', () => new Proxy({}, { + get: () => chalkFunction +})); + +jest.mock('fs/promises', () => ({ + copyFile: jest.fn(() => Promise.resolve()), + mkdir: jest.fn(() => Promise.resolve()), + open: jest.fn(), + writeFile: jest.fn(() => Promise.resolve()) +})); + +global.process.exit = jest.fn(code => { + throw new Error(`process.exit called with ${code}`); +}); diff --git a/src/buildtools/package.json b/src/buildtools/package.json new file mode 100644 index 0000000000..da9d3fb02b --- /dev/null +++ b/src/buildtools/package.json @@ -0,0 +1,32 @@ +{ + "name": "@sourceacademy/module-buildtools", + "description": "Tools for building Source Academy bundles and tabs", + "version": "1.0.0", + "devDependencies": { + "@commander-js/extra-typings": "^13.0.0", + "@types/estree": "^1.0.0", + "@types/jest": "^29.0.0", + "@types/node": "^20.12.12" + }, + "bin": { + "buildtools": "./bin.js" + }, + "type": "module", + "dependencies": { + "acorn": "^8.8.1", + "astring": "^1.8.6", + "chalk": "^5.0.1", + "commander": "^13.0.0", + "console-table-printer": "^2.11.1", + "esbuild": "^0.25.0", + "jest": "^29.7.0", + "jsonschema": "^1.5.0", + "ts-jest": "^29.1.2", + "typedoc": "^0.25.12", + "typescript": "^5.8.2" + }, + "scripts": { + "build": "esbuild ./src/commands/index.ts --bundle --format=esm --packages=external --target=node20 --outfile=./bin.js --tsconfig=./tsconfig.json", + "tsc": "tsc --project ./tsconfig.json" + } +} diff --git a/src/buildtools/src/commands/build.ts b/src/buildtools/src/commands/build.ts index b63a0088a5..09082bf137 100644 --- a/src/buildtools/src/commands/build.ts +++ b/src/buildtools/src/commands/build.ts @@ -1,16 +1,19 @@ +import fs from 'fs/promises' import pathlib from 'path' import { Command } from "@commander-js/extra-typings"; import { buildBundle } from "../build/modules/bundle"; -import { getBundleManifest } from "../build/modules/manifest"; +import { getBundleManifests } from "../build/modules/manifest"; import { buildTab } from '../build/modules/tab'; import { getGitRoot } from '../utils'; -const outDir = pathlib.join(await getGitRoot(), 'build') +const gitRoot = await getGitRoot() +const bundlesDir = pathlib.join(gitRoot, 'src', 'bundles') +const outDir = pathlib.join(gitRoot, 'build') + export const getBuildBundleCommand = () => new Command('bundle') .argument('', 'Directory in which the bundle\'s source files are located') .action(async bundleDir => { - const manifest = await getBundleManifest(`${bundleDir}/manifest.json`) - await buildBundle(bundleDir, manifest, outDir) + await buildBundle(bundleDir, outDir) }) export const getBuildTabCommand = () => new Command('tab') @@ -19,6 +22,14 @@ export const getBuildTabCommand = () => new Command('tab') await buildTab(tabDir, outDir) }) +export const getBuildManifestCommand = () => new Command('manifest') + .action(async () => { + const manifest = await getBundleManifests(bundlesDir) + await fs.mkdir(outDir, { recursive: true }) + await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(manifest, null, 2)) + }) + export const getBuildCommand = () => new Command('build') .addCommand(getBuildBundleCommand()) - .addCommand(getBuildTabCommand()) \ No newline at end of file + .addCommand(getBuildTabCommand()) + .addCommand(getBuildManifestCommand()) diff --git a/src/buildtools/src/commands/main.ts b/src/buildtools/src/commands/main.ts index 32c7c00746..f6fb94b0a2 100644 --- a/src/buildtools/src/commands/main.ts +++ b/src/buildtools/src/commands/main.ts @@ -2,8 +2,11 @@ import { Command } from "@commander-js/extra-typings"; import { getBuildCommand } from "./build"; import getTemplateCommand from "./template"; import getTestCommand from "./testing"; +import { getLintCommand, getTscCommand } from "./prebuild"; export const getMainCommand = () => new Command() .addCommand(getBuildCommand()) .addCommand(getTemplateCommand()) - .addCommand(getTestCommand()) \ No newline at end of file + .addCommand(getTestCommand()) + .addCommand(getTscCommand()) + .addCommand(getLintCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/prebuild.ts b/src/buildtools/src/commands/prebuild.ts new file mode 100644 index 0000000000..79c6773f5a --- /dev/null +++ b/src/buildtools/src/commands/prebuild.ts @@ -0,0 +1,49 @@ +import { Command } from "@commander-js/extra-typings"; +import { runEslint } from "../prebuild/lint"; +import type { AwaitedReturn } from "../utils"; +import { runTsc } from "../prebuild/typecheck"; +import pathlib from 'path'; +import chalk from "chalk"; +import ts from 'typescript' + +export const getLintCommand = () => new Command('lint') + .argument('') + .option('--fix') + .action(async (directory, { fix }) => { + const results = await runEslint(directory, fix) + console.log(eslintResultsLogger(results)) + }) + +export const getTscCommand = () => new Command('typecheck') + .argument('') + .action(async directory => { + const results = await runTsc(directory) + console.log(tscResultsLogger(results)) + }) + +function tscResultsLogger(tscResult: AwaitedReturn) { + if (tscResult.severity === 'error' && tscResult.error) { + return `${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}: ${tscResult.error}`)}`; + } + + const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { + getNewLine: () => '\n', + getCurrentDirectory: () => process.cwd(), + getCanonicalFileName: name => pathlib.basename(name) + }); + + if (tscResult.severity === 'error') { + return `${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}}`)}`; + } + return `${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')}`)}`; +} + +function eslintResultsLogger({ formatted, severity }: AwaitedReturn) { + let errStr: string; + + if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); + else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); + else errStr = chalk.greenBright('successfully'); + + return `${chalk.cyanBright(`Linting completed:`)}\n${formatted}`; +} \ No newline at end of file diff --git a/src/buildtools/tsconfig.json b/src/buildtools/tsconfig.json new file mode 100644 index 0000000000..7929b73931 --- /dev/null +++ b/src/buildtools/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "target": "ESNext", + "verbatimModuleSyntax": true + }, + "include": ["./src", "jest.setup.ts"], + "exclude": [ + "./src/templates/templates/**", + "./src/build/docs/__tests__/test_mocks" + ] +} From 0274c12f10443e79c41f36981fc20344f7c08a5d Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 14 May 2025 20:03:09 -0400 Subject: [PATCH 011/112] Working typedoc building --- .../src/build/docs/__mocks__/docsUtils.ts | 16 ++ .../src/build/docs/__tests__/building.test.ts | 52 +++++ .../src/build/docs/__tests__/docs.test.ts | 49 +++++ .../src/build/docs/__tests__/json.test.ts | 42 ++++ .../test_mocks/bundles/test0/index.ts | 8 + .../test_mocks/bundles/test1/index.ts | 4 + .../docs/__tests__/test_mocks/tsconfig.json | 45 +++++ src/buildtools/src/build/docs/docsUtils.ts | 51 +++++ src/buildtools/src/build/docs/docsreadme.md | 18 ++ src/buildtools/src/build/docs/drawdown.ts | 190 ++++++++++++++++++ src/buildtools/src/build/docs/index.ts | 33 +++ src/buildtools/src/build/docs/json.ts | 69 +++++++ 12 files changed, 577 insertions(+) create mode 100644 src/buildtools/src/build/docs/__mocks__/docsUtils.ts create mode 100644 src/buildtools/src/build/docs/__tests__/building.test.ts create mode 100644 src/buildtools/src/build/docs/__tests__/docs.test.ts create mode 100644 src/buildtools/src/build/docs/__tests__/json.test.ts create mode 100644 src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts create mode 100644 src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts create mode 100644 src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json create mode 100644 src/buildtools/src/build/docs/docsUtils.ts create mode 100644 src/buildtools/src/build/docs/docsreadme.md create mode 100644 src/buildtools/src/build/docs/drawdown.ts create mode 100644 src/buildtools/src/build/docs/index.ts create mode 100644 src/buildtools/src/build/docs/json.ts diff --git a/src/buildtools/src/build/docs/__mocks__/docsUtils.ts b/src/buildtools/src/build/docs/__mocks__/docsUtils.ts new file mode 100644 index 0000000000..8532cf4da5 --- /dev/null +++ b/src/buildtools/src/build/docs/__mocks__/docsUtils.ts @@ -0,0 +1,16 @@ +export const initTypedoc = jest.fn(() => { + const proj = { + getChildByName: () => ({ + children: [] + }), + path: '' + } as any; + + const app = { + convert: jest.fn() + .mockReturnValue(proj), + generateDocs: jest.fn(() => Promise.resolve()) + }; + + return Promise.resolve([proj, app]); +}); diff --git a/src/buildtools/src/build/docs/__tests__/building.test.ts b/src/buildtools/src/build/docs/__tests__/building.test.ts new file mode 100644 index 0000000000..786787b57f --- /dev/null +++ b/src/buildtools/src/build/docs/__tests__/building.test.ts @@ -0,0 +1,52 @@ +import fs from 'fs/promises'; +import type { MockedFunction } from 'jest-mock'; +import { initTypedoc } from '../docsUtils'; +import * as json from '../json'; + +jest.spyOn(json, 'buildJson'); + +const mockedWriteFile = fs.writeFile as MockedFunction; + +const test0Obj = { + test_function: { + kind: 'function', + params: [['_param0', 'string']], + description: '

This is just some test function

', + retType: 'number' + } +}; + +const test1Obj = { + test_variable: { + kind: 'variable', + type: 'number', + description: '

Test variable

' + } +}; + +const workingDir = __dirname + '/test_mocks'; + +function matchObj(raw: string, expected: T) { + expect(JSON.parse(raw)).toMatchObject(expected); +} + +describe('Check that buildJsons can handle building different numbers of bundles', () => { + test('Building the json documentation for a single bundle', async () => { + const [project,] = await initTypedoc(['test0'], workingDir, false, false); + await json.buildJsons({ bundles: ['test0'] }, workingDir, project); + + expect(json.buildJson).toHaveBeenCalledTimes(1); + const [[, test0str]] = mockedWriteFile.mock.calls; + matchObj(test0str as string, test0Obj); + }); + + test('Building the json documentation for multiple bundles', async () => { + const [project,] = await initTypedoc(['test0', 'test1'], workingDir, false, false); + await json.buildJsons({ bundles: ['test0', 'test1'] }, workingDir, project); + + expect(json.buildJson).toHaveBeenCalledTimes(2); + const [[, test0Str], [, test1Str]] = mockedWriteFile.mock.calls; + matchObj(test0Str as string, test0Obj); + matchObj(test1Str as string, test1Obj); + }); +}); diff --git a/src/buildtools/src/build/docs/__tests__/docs.test.ts b/src/buildtools/src/build/docs/__tests__/docs.test.ts new file mode 100644 index 0000000000..5877d52010 --- /dev/null +++ b/src/buildtools/src/build/docs/__tests__/docs.test.ts @@ -0,0 +1,49 @@ +import type { MockedFunction } from 'jest-mock'; +import { testBuildCommand } from '@src/build/__tests__/testingUtils'; +import { getBuildDocsCommand } from '..'; +import * as html from '../html'; +import * as json from '../json'; + +jest.mock('../docsUtils'); + +jest.spyOn(json, 'buildJsons'); +jest.spyOn(html, 'buildHtml'); + +const asMock = any>(func: T) => func as MockedFunction; +const mockBuildJson = asMock(json.buildJsons); + +const runCommand = (...args: string[]) => getBuildDocsCommand() + .parseAsync(args, { from: 'user' }); + +describe('test the docs command', () => { + testBuildCommand( + 'buildDocs', + getBuildDocsCommand, + [json.buildJsons, html.buildHtml] + ); + + it('should only build the documentation for specified modules', async () => { + await runCommand('-b', 'test0', 'test1'); + + expect(json.buildJsons) + .toHaveBeenCalledTimes(1); + + const buildJsonCall = mockBuildJson.mock.calls[0]; + expect(buildJsonCall[0]) + .toEqual({ + bundles: ['test0', 'test1'], + modulesSpecified: true + }); + + expect(html.buildHtml) + .toHaveBeenCalledTimes(1); + + expect(html.buildHtml) + .toReturnWith(Promise.resolve({ + elapsed: 0, + result: { + severity: 'warn' + } + })); + }); +}); diff --git a/src/buildtools/src/build/docs/__tests__/json.test.ts b/src/buildtools/src/build/docs/__tests__/json.test.ts new file mode 100644 index 0000000000..86581ec019 --- /dev/null +++ b/src/buildtools/src/build/docs/__tests__/json.test.ts @@ -0,0 +1,42 @@ +import fs from 'fs/promises'; +import type { MockedFunction } from 'jest-mock'; +import { testBuildCommand } from '@src/build/__tests__/testingUtils'; +import * as json from '../json'; + +jest.spyOn(json, 'buildJsons'); +jest.mock('../docsUtils'); + +const mockBuildJson = json.buildJsons as MockedFunction; +const runCommand = (...args: string[]) => json.getBuildJsonsCommand() + .parseAsync(args, { from: 'user' }); + +describe('test json command', () => { + testBuildCommand( + 'buildJsons', + json.getBuildJsonsCommand, + [json.buildJsons] + ); + + test('normal function', async () => { + await runCommand(); + + expect(fs.mkdir) + .toBeCalledWith('build/jsons', { recursive: true }); + + expect(json.buildJsons) + .toHaveBeenCalledTimes(1); + }); + + it('should only build the jsons for specified modules', async () => { + await runCommand('-b', 'test0', 'test1'); + + expect(json.buildJsons) + .toHaveBeenCalledTimes(1); + + const buildJsonCall = mockBuildJson.mock.calls[0]; + expect(buildJsonCall[0]) + .toMatchObject({ + bundles: ['test0', 'test1'] + }); + }); +}); diff --git a/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts b/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts new file mode 100644 index 0000000000..e7dee10395 --- /dev/null +++ b/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts @@ -0,0 +1,8 @@ +/** + * This is just some test function + * @param _param0 Test parameter + * @returns Zero + */ +export function test_function(_param0: string) { + return 0; +} diff --git a/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts b/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts new file mode 100644 index 0000000000..9f313a9cbd --- /dev/null +++ b/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts @@ -0,0 +1,4 @@ +/** + * Test variable + */ +export const test_variable: number = 0; diff --git a/src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json b/src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json new file mode 100644 index 0000000000..d9a93d7cd9 --- /dev/null +++ b/src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + /* Allow JavaScript files to be imported inside your project, instead of just .ts and .tsx files. */ + "allowJs": false, + /* When set to true, allowSyntheticDefaultImports allows you to write an import like "import React from "react";" */ + "allowSyntheticDefaultImports": true, + /* See https://www.typescriptlang.org/tsconfig#esModuleInterop */ + "esModuleInterop": true, + /* Controls how JSX constructs are emitted in JavaScript files. This only affects output of JS files that started in .tsx files. */ + "jsx": "react-jsx", + /* See https://www.typescriptlang.org/tsconfig#lib */ + "lib": ["es6", "dom", "es2016", "ESNext", "scripthost"], + /* Sets the module system for the program. See the Modules reference page for more information. */ + "module": "esnext", + /* Specify the module resolution strategy: 'node' (Node.js) or 'classic' (used in TypeScript before the release of 1.6). */ + "moduleResolution": "node", + /* Do not emit compiler output files like JavaScript source code, source-maps or declarations. */ + "noEmit": true, + /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ + "resolveJsonModule": true, + /* The longest common path of all non-declaration input files. */ + "rootDir": "./", + /* Enables the generation of sourcemap files. These files allow debuggers and other tools to display the original TypeScript source code when actually working with the emitted JavaScript files. */ + "sourceMap": false, + /* Skip running typescript on declaration files. This option is needed due to a known bug in react-ace */ + "skipLibCheck": true, + /* The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. */ + "strict": true, + "forceConsistentCasingInFileNames": true, + /* The target setting changes which JS features are downleveled and which are left intact. */ + "target": "es6", + /* In some cases where no type annotations are present, TypeScript will fall back to a type of any for a variable when it cannot infer the type. */ + /* *** TEMPORARILY ADDED UNTIL ALL MODULES HAVE BEEN REFACTORED!!!!!!!!!!! *** */ + "noImplicitAny": false, + "verbatimModuleSyntax": true, + "paths": { + "js-slang/context": ["./typings/js-slang/context.d.ts"] + }, + "ignoreDeprecations": "5.0" + }, + /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ + "include": ["."], + /* Specifies an array of filenames or patterns that should be skipped when resolving include. */ + "exclude": ["jest.config.js"] +} diff --git a/src/buildtools/src/build/docs/docsUtils.ts b/src/buildtools/src/build/docs/docsUtils.ts new file mode 100644 index 0000000000..e6f6b7871f --- /dev/null +++ b/src/buildtools/src/build/docs/docsUtils.ts @@ -0,0 +1,51 @@ +import * as td from 'typedoc'; +import type { ResolvedBundle } from '../modules/manifest'; +import { getGitRoot } from '../../utils'; +import pathlib from 'path'; + +export async function initTypedocForSingleBundle(bundle: ResolvedBundle) { + const app = await td.Application.bootstrap({ + name: bundle.name, + categorizeByGroup: true, + entryPoints: [bundle.entryPoint], + excludeInternal: true, + logLevel: 'Error', + tsconfig: `${bundle.directory}/tsconfig.json`, + skipErrorChecking: true, + }); + + const reflection = await app.convert() + if (!reflection) { + throw new Error(`Failed to generate reflection for ${bundle.name}, check that the bundle has no type errors!`) + } + + return reflection +} + +export async function initTypedoc(manifest: Record) { + const entryPoints = Object.values(manifest).map(({ entryPoint }) => entryPoint) + const gitRoot = await getGitRoot() + const readme = pathlib.resolve(gitRoot, 'src', 'buildtools', 'src', 'build', 'docs', 'docsreadme.md') + const tsconfigPath = pathlib.resolve(gitRoot, 'src', 'bundles', 'tsconfig.json') + + const app = await td.Application.bootstrap({ + alwaysCreateEntryPointModule: true, + categorizeByGroup: true, + entryPoints, + excludeInternal: true, + logLevel: 'Error', + name: 'Source Academy Modules', + readme, + skipErrorChecking: true, + tsconfig: tsconfigPath + }); + + + const project = await app.convert(); + if (!project) { + throw new Error('Failed to initialize typedoc - Make sure to check that the source files have no compilation errors!'); + } + return [project, app] as [td.ProjectReflection, td.Application]; +} + +export type TypedocInitResult = Awaited>; diff --git a/src/buildtools/src/build/docs/docsreadme.md b/src/buildtools/src/build/docs/docsreadme.md new file mode 100644 index 0000000000..0adf8f5beb --- /dev/null +++ b/src/buildtools/src/build/docs/docsreadme.md @@ -0,0 +1,18 @@ +# Overview + +The Source Academy allows programmers to import functions and constants from a module, using JavaScript's `import` directive. For example, the programmer may decide to import the function `thrice` from the module `repeat` by starting the program with +``` +import { thrice } from "repeat"; +``` + +When evaluating such a directive, the Source Academy looks for a module with the matching name, here `repeat`, in a preconfigured modules site. The Source Academy at https://sourceacademy.org uses the default modules site (located at https://source-academy.github.io/modules). + +After importing functions or constants from a module, they can be used as usual. +``` +thrice(display)(8); // displays 8 three times +``` +if `thrice` is declared in the module `repeat` as follows: +``` +const thrice = f => x => f(f(f(x))); +``` +[List of modules](modules.html) available at the default modules site. \ No newline at end of file diff --git a/src/buildtools/src/build/docs/drawdown.ts b/src/buildtools/src/build/docs/drawdown.ts new file mode 100644 index 0000000000..8fe5d5aaa8 --- /dev/null +++ b/src/buildtools/src/build/docs/drawdown.ts @@ -0,0 +1,190 @@ +/* eslint-disable*/ +/** + * Module to convert from markdown into HTML + * drawdown.js + * (c) Adam Leggett + */ + +export default (src: string): string => { + var rx_lt = //g; + var rx_space = /\t|\r|\uf8ff/g; + var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g; + var rx_hr = /^([*\-=_] *){3,}$/gm; + var rx_blockquote = /\n *> *([^]*?)(?=(\n|$){2})/g; + var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g; + var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g; + var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+)|`)(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g; + var rx_code = /\n((```|~~~).*\n?([^]*?)\n?\2|(( {4}.*?\n)+))/g; + var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g; + var rx_table = /\n(( *\|.*?\| *\n)+)/g; + var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/; + var rx_row = /.*\n/g; + var rx_cell = /\||(.*?[^\\])\|/g; + var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g; + var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g; + var rx_stash = /-\d+\uf8ff/g; + + function replace(rex, fn) { + src = src.replace(rex, fn); + } + + function element(tag, content) { + return '<' + tag + '>' + content + ''; + } + + function blockquote(src) { + return src.replace(rx_blockquote, function (all, content) { + return element( + 'blockquote', + blockquote(highlight(content.replace(/^ *> */gm, ''))) + ); + }); + } + + function list(src) { + return src.replace(rx_list, function (all, ind, ol, num, low, content) { + var entry = element( + 'li', + highlight( + content + .split( + RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g') + ) + .map(list) + .join('
  • ') + ) + ); + + return ( + '\n' + + (ol + ? '
      ' + : parseInt(ol, 36) - + 9 + + '" style="list-style-type:' + + (low ? 'low' : 'upp') + + 'er-alpha">') + + entry + + '
    ' + : element('ul', entry)) + ); + }); + } + + function highlight(src) { + return src.replace( + rx_highlight, + function (all, _, p1, emp, sub, sup, small, big, p2, content) { + return ( + _ + + element( + emp + ? p2 + ? 'strong' + : 'em' + : sub + ? p2 + ? 's' + : 'sub' + : sup + ? 'sup' + : small + ? 'small' + : big + ? 'big' + : 'code', + highlight(content) + ) + ); + } + ); + } + + function unesc(str) { + return str.replace(rx_escape, '$1'); + } + + var stash = []; + var si = 0; + + src = '\n' + src + '\n'; + + replace(rx_lt, '<'); + replace(rx_gt, '>'); + replace(rx_space, ' '); + + // blockquote + src = blockquote(src); + + // horizontal rule + replace(rx_hr, '
    '); + + // list + src = list(src); + replace(rx_listjoin, ''); + + // code + replace(rx_code, function (all, p1, p2, p3, p4) { + stash[--si] = element( + 'pre', + element('code', p3 || p4.replace(/^ {4}/gm, '')) + ); + return si + '\uf8ff'; + }); + + // link or image + replace(rx_link, function (all, p1, p2, p3, p4, p5, p6) { + stash[--si] = p4 + ? p2 + ? '' + p3 + '' + : '' + unesc(highlight(p3)) + '' + : p6; + return si + '\uf8ff'; + }); + + // table + replace(rx_table, function (all, table) { + var sep = table.match(rx_thead)[1]; + return ( + '\n' + + element( + 'table', + table.replace(rx_row, function (row, ri) { + return row == sep + ? '' + : element( + 'tr', + row.replace(rx_cell, function (all, cell, ci) { + return ci + ? element( + sep && !ri ? 'th' : 'td', + unesc(highlight(cell || '')) + ) + : ''; + }) + ); + }) + ) + ); + }); + + // heading + replace(rx_heading, function (all, _, p1, p2) { + return _ + element('h' + p1.length, unesc(highlight(p2))); + }); + + // paragraph + replace(rx_para, function (all, content) { + return element('p', unesc(highlight(content))); + }); + + // stash + replace(rx_stash, function (all) { + return stash[parseInt(all)]; + }); + + return src.trim(); +}; \ No newline at end of file diff --git a/src/buildtools/src/build/docs/index.ts b/src/buildtools/src/build/docs/index.ts new file mode 100644 index 0000000000..725af2a824 --- /dev/null +++ b/src/buildtools/src/build/docs/index.ts @@ -0,0 +1,33 @@ +import type { ProjectReflection } from 'typedoc' +import { resolveAllBundles } from '../modules/manifest' +import { initTypedoc } from './docsUtils' +import { buildJson } from './json' +import chalk from 'chalk' + +/** + * A more efficient version of documentation building to avoid + * having to instantiate typedoc multiple times + */ +export async function buildDocs(bundlesDir: string, outDir: string) { + const manifest = await resolveAllBundles(bundlesDir) + const [project, app] = await initTypedoc(manifest) + + const jsonPromises = Object.keys(manifest) + .map(async bundleName => { + const reflection = project.getChildByName(bundleName) + if (!reflection) { + console.warn(`${chalk.yellow('[warning]:')} Did not find documentation for ${bundleName}. Did you include a @module tag?`) + return; + } + + await buildJson(bundleName, reflection as ProjectReflection, outDir) + }) + + await Promise.all([ + ...jsonPromises, + app.generateDocs(project, `${outDir}/documentation`) + ]) +} + + +export { buildJson } \ No newline at end of file diff --git a/src/buildtools/src/build/docs/json.ts b/src/buildtools/src/build/docs/json.ts new file mode 100644 index 0000000000..e99505f383 --- /dev/null +++ b/src/buildtools/src/build/docs/json.ts @@ -0,0 +1,69 @@ +import fs from 'fs/promises'; +import * as td from 'typedoc'; +import drawdown from './drawdown'; + +const typeToName = (type?: td.SomeType) => type.stringify(td.TypeContext.none); + +const parsers = { + [td.ReflectionKind.Function](obj) { + // Functions should have only 1 signature + if (obj.signatures.length > 1) { + console.warn(`${obj.name} has more than 1 signature; only using the first one`) + } + + const [signature] = obj.signatures; + + let description: string; + if (signature.comment) { + description = drawdown(signature.comment.summary.map(({ text }) => text) + .join('')); + } else { + description = 'No description available'; + } + + const params = signature.parameters.map(({ type, name }) => [name, typeToName(type)] as [string, string]); + + return { + kind: 'function', + name: obj.name, + description, + params, + retType: typeToName(signature.type) + }; + }, + [td.ReflectionKind.Variable](obj) { + let description: string; + if (obj.comment) { + description = drawdown(obj.comment.summary.map(({ text }) => text) + .join('')); + } else { + description = 'No description available'; + } + + return { + kind: 'variable', + name: obj.name, + description, + type: typeToName(obj.type) + }; + } +} satisfies Partial any>>; + +export async function buildJson(bundleName: string, reflection: td.ProjectReflection, outDir: string) { + const jsonData = reflection.children.reduce((res, element) => { + // Ignore 'type_map' exports if they are present + if (element.name === 'type_map') { + return res; + } + + const parser = parsers[element.kind]; + return { + ...res, + [element.name]: parser + ? parser(element) + : { kind: 'unknown' } + }; + }, {}); + + await fs.writeFile(`${outDir}/json/${bundleName}.json`, JSON.stringify(jsonData, null, 2)); +} From 58abebd928299c94132e8e83da97056b5b491c11 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 15 May 2025 00:03:34 -0400 Subject: [PATCH 012/112] More work --- src/bundles/__mocks__/context.ts | 5 ++ src/bundles/__mocks__/emptyModule.ts | 1 + src/bundles/ar/manifest.json | 5 ++ src/bundles/ar/package.json | 7 ++- src/bundles/arcade_2d/manifest.json | 5 ++ src/bundles/arcade_2d/package.json | 5 +- src/bundles/arcade_2d/src/gameobject.ts | 2 +- src/bundles/binary_tree/manifest.json | 3 + src/bundles/binary_tree/package.json | 4 +- src/bundles/communication/manifest.json | 3 + src/bundles/communication/package.json | 4 +- src/bundles/copy_gc/manifest.json | 5 ++ src/bundles/copy_gc/package.json | 4 +- src/bundles/copy_gc/src/index.ts | 68 ++++++++-------------- src/bundles/copy_gc/tsconfig.json | 1 + src/bundles/csg/manifest.json | 5 ++ src/bundles/csg/package.json | 4 +- src/bundles/curve/manifest.json | 5 +- src/bundles/curve/package.json | 9 ++- src/bundles/curve/src/index.ts | 2 +- src/bundles/curve/src/type_interface.ts | 2 +- src/bundles/curve/tsconfig.json | 3 +- src/bundles/curve/tsconfig.prod.json | 9 +++ src/bundles/game/manifest.json | 5 ++ src/bundles/game/package.json | 4 +- src/bundles/jest.config.js | 30 ++++++++++ src/bundles/jest.polyfills.js | 8 +++ src/bundles/mark_sweep/manifest.json | 5 ++ src/bundles/mark_sweep/package.json | 4 +- src/bundles/mark_sweep/src/index.ts | 4 ++ src/bundles/nbody/manifest.json | 5 ++ src/bundles/nbody/package.json | 4 +- src/bundles/package.json | 14 ++++- src/bundles/painter/manifest.json | 5 ++ src/bundles/painter/package.json | 4 +- src/bundles/physics_2d/manifest.json | 5 ++ src/bundles/physics_2d/package.json | 4 +- src/bundles/pix_n_flix/manifest.json | 5 ++ src/bundles/pix_n_flix/package.json | 4 +- src/bundles/plotly/manifest.json | 5 ++ src/bundles/plotly/package.json | 4 +- src/bundles/plotly/src/index.ts | 1 + src/bundles/remote_execution/manifest.json | 3 + src/bundles/remote_execution/package.json | 4 +- src/bundles/repeat/manifest.json | 5 ++ src/bundles/repeat/package.json | 4 +- src/bundles/repeat/src/index.ts | 1 + src/bundles/repl/manifest.json | 5 ++ src/bundles/repl/package.json | 4 +- src/bundles/robot_simulation/manifest.json | 5 ++ src/bundles/robot_simulation/package.json | 4 +- src/bundles/rune/manifest.json | 5 ++ src/bundles/rune/package.json | 4 +- src/bundles/rune/src/display.ts | 2 +- src/bundles/rune/src/functions.ts | 2 +- src/bundles/rune/src/index.ts | 2 +- src/bundles/rune/src/rune.ts | 2 +- src/bundles/rune_in_words/manifest.json | 3 + src/bundles/rune_in_words/package.json | 4 +- src/bundles/scrabble/manifest.json | 3 + src/bundles/scrabble/package.json | 4 +- src/bundles/scrabble/src/index.ts | 1 + src/bundles/sound/manifest.json | 5 ++ src/bundles/sound/package.json | 7 ++- src/bundles/sound_matrix/manifest.json | 5 ++ src/bundles/sound_matrix/package.json | 4 +- src/bundles/sound_matrix/src/index.ts | 1 + src/bundles/stereo_sound/manifest.json | 5 ++ src/bundles/stereo_sound/package.json | 7 ++- src/bundles/tsconfig.json | 1 - src/bundles/unittest/manifest.json | 5 ++ src/bundles/unittest/package.json | 7 ++- src/bundles/unittest/src/index.ts | 23 ++------ src/bundles/unity_academy/manifest.json | 5 ++ src/bundles/unity_academy/package.json | 8 ++- src/bundles/unity_academy/tsconfig.json | 5 +- src/bundles/wasm/manifest.json | 3 + src/bundles/wasm/package.json | 8 ++- src/tabs/ArcadeTwod/package.json | 7 ++- src/tabs/AugmentedReality/package.json | 5 ++ src/tabs/CopyGc/package.json | 7 ++- src/tabs/Csg/package.json | 7 ++- src/tabs/Curve/package.json | 7 ++- src/tabs/Curve/src/__tests__/Curve.tsx | 6 +- src/tabs/Curve/tsconfig.json | 4 +- src/tabs/Game/package.json | 7 ++- src/tabs/MarkSweep/package.json | 7 ++- src/tabs/Nbody/package.json | 7 ++- src/tabs/Painter/package.json | 7 ++- src/tabs/Painter/tsconfig.json | 4 -- src/tabs/Physics2D/package.json | 7 ++- src/tabs/Pixnflix/package.json | 7 ++- src/tabs/Plotly/package.json | 7 ++- src/tabs/Repeat/package.json | 7 ++- src/tabs/Repl/package.json | 7 ++- src/tabs/RobotSimulation/package.json | 7 ++- src/tabs/Rune/package.json | 7 ++- src/tabs/Sound/package.json | 7 ++- src/tabs/SoundMatrix/package.json | 7 ++- src/tabs/StereoSound/package.json | 7 ++- src/tabs/Unittest/package.json | 7 ++- src/tabs/UnityAcademy/package.json | 7 ++- src/tabs/tsconfig.json | 8 ++- 103 files changed, 448 insertions(+), 166 deletions(-) create mode 100644 src/bundles/__mocks__/context.ts create mode 100644 src/bundles/__mocks__/emptyModule.ts create mode 100644 src/bundles/ar/manifest.json create mode 100644 src/bundles/arcade_2d/manifest.json create mode 100644 src/bundles/binary_tree/manifest.json create mode 100644 src/bundles/communication/manifest.json create mode 100644 src/bundles/copy_gc/manifest.json create mode 100644 src/bundles/csg/manifest.json create mode 100644 src/bundles/curve/tsconfig.prod.json create mode 100644 src/bundles/game/manifest.json create mode 100644 src/bundles/jest.config.js create mode 100644 src/bundles/jest.polyfills.js create mode 100644 src/bundles/mark_sweep/manifest.json create mode 100644 src/bundles/nbody/manifest.json create mode 100644 src/bundles/painter/manifest.json create mode 100644 src/bundles/physics_2d/manifest.json create mode 100644 src/bundles/pix_n_flix/manifest.json create mode 100644 src/bundles/plotly/manifest.json create mode 100644 src/bundles/remote_execution/manifest.json create mode 100644 src/bundles/repeat/manifest.json create mode 100644 src/bundles/repl/manifest.json create mode 100644 src/bundles/robot_simulation/manifest.json create mode 100644 src/bundles/rune/manifest.json create mode 100644 src/bundles/rune_in_words/manifest.json create mode 100644 src/bundles/scrabble/manifest.json create mode 100644 src/bundles/sound/manifest.json create mode 100644 src/bundles/sound_matrix/manifest.json create mode 100644 src/bundles/stereo_sound/manifest.json create mode 100644 src/bundles/unittest/manifest.json create mode 100644 src/bundles/unity_academy/manifest.json create mode 100644 src/bundles/wasm/manifest.json diff --git a/src/bundles/__mocks__/context.ts b/src/bundles/__mocks__/context.ts new file mode 100644 index 0000000000..1b55789787 --- /dev/null +++ b/src/bundles/__mocks__/context.ts @@ -0,0 +1,5 @@ +export default { + moduleContexts: new Proxy({}, { + get: () => ({}) + }) +} \ No newline at end of file diff --git a/src/bundles/__mocks__/emptyModule.ts b/src/bundles/__mocks__/emptyModule.ts new file mode 100644 index 0000000000..693da49fc4 --- /dev/null +++ b/src/bundles/__mocks__/emptyModule.ts @@ -0,0 +1 @@ +export {} \ No newline at end of file diff --git a/src/bundles/ar/manifest.json b/src/bundles/ar/manifest.json new file mode 100644 index 0000000000..ac1ecb7242 --- /dev/null +++ b/src/bundles/ar/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "AugmentedReality" + ] +} \ No newline at end of file diff --git a/src/bundles/ar/package.json b/src/bundles/ar/package.json index c9cf1a97af..33eb7458c5 100644 --- a/src/bundles/ar/package.json +++ b/src/bundles/ar/package.json @@ -9,7 +9,8 @@ "./*.js": "./dist/*.js" }, "dependencies": { - "saar": "^1.0.4" + "saar": "^1.0.4", + "uniqid": "^5.4.0" }, "devDependencies": { "@sourceacademy/module-buildtools": "workspace:^", @@ -17,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/arcade_2d/manifest.json b/src/bundles/arcade_2d/manifest.json new file mode 100644 index 0000000000..a6ae91eeec --- /dev/null +++ b/src/bundles/arcade_2d/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "ArcadeTwod" + ] +} \ No newline at end of file diff --git a/src/bundles/arcade_2d/package.json b/src/bundles/arcade_2d/package.json index 6483135a62..9a10008417 100644 --- a/src/bundles/arcade_2d/package.json +++ b/src/bundles/arcade_2d/package.json @@ -7,6 +7,7 @@ }, "devDependencies": { "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "typescript": "^5.8.2" }, "type": "module", @@ -17,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/arcade_2d/src/gameobject.ts b/src/bundles/arcade_2d/src/gameobject.ts index cbe25b2c0b..c09715a154 100644 --- a/src/bundles/arcade_2d/src/gameobject.ts +++ b/src/bundles/arcade_2d/src/gameobject.ts @@ -1,7 +1,7 @@ /** * This file contains the bundle's representation of GameObjects. */ -import type { ReplResult } from '../../typings/type_helpers'; +import type { ReplResult } from '@sourceacademy/modules-lib/types'; import { DEFAULT_INTERACTABLE_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_TRANSFORM_PROPS } from './constants'; import type * as types from './types'; diff --git a/src/bundles/binary_tree/manifest.json b/src/bundles/binary_tree/manifest.json new file mode 100644 index 0000000000..4e75d9a969 --- /dev/null +++ b/src/bundles/binary_tree/manifest.json @@ -0,0 +1,3 @@ +{ + "tabs": [] +} \ No newline at end of file diff --git a/src/bundles/binary_tree/package.json b/src/bundles/binary_tree/package.json index 9b85570248..0ef37e80cc 100644 --- a/src/bundles/binary_tree/package.json +++ b/src/bundles/binary_tree/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/communication/manifest.json b/src/bundles/communication/manifest.json new file mode 100644 index 0000000000..4e75d9a969 --- /dev/null +++ b/src/bundles/communication/manifest.json @@ -0,0 +1,3 @@ +{ + "tabs": [] +} \ No newline at end of file diff --git a/src/bundles/communication/package.json b/src/bundles/communication/package.json index 79384b7970..939651a11c 100644 --- a/src/bundles/communication/package.json +++ b/src/bundles/communication/package.json @@ -18,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/copy_gc/manifest.json b/src/bundles/copy_gc/manifest.json new file mode 100644 index 0000000000..2ad3aec437 --- /dev/null +++ b/src/bundles/copy_gc/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "CopyGc" + ] +} \ No newline at end of file diff --git a/src/bundles/copy_gc/package.json b/src/bundles/copy_gc/package.json index 550e61b072..7f8ef56839 100644 --- a/src/bundles/copy_gc/package.json +++ b/src/bundles/copy_gc/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/copy_gc/src/index.ts b/src/bundles/copy_gc/src/index.ts index 6c8c361788..59f8170fc1 100644 --- a/src/bundles/copy_gc/src/index.ts +++ b/src/bundles/copy_gc/src/index.ts @@ -1,3 +1,7 @@ +/** + * @module copy_gc + */ + import { COMMAND, type CommandHeapObject, type Memory, type MemoryHeaps, type Tag } from './types'; // Global Variables @@ -20,12 +24,12 @@ let FIRST_CHILD_SLOT: number = 2; let LAST_CHILD_SLOT: number = 3; let ROOTS: number[] = []; -function initialize_tag(allTag: number[], types: string[]): void { +export function initialize_tag(allTag: number[], types: string[]): void { tags = allTag; typeTag = types; } -function allHeap(newHeap: number[][]): void { +export function allHeap(newHeap: number[][]): void { memoryHeaps = newHeap; } @@ -33,7 +37,7 @@ function updateFlip(): void { flips.push(commandHeap.length - 1); } -function generateMemory(): void { +export function generateMemory(): void { toMemoryMatrix = []; for (let i = 0; i < ROW / 2; i += 1) { memory = []; @@ -71,7 +75,7 @@ function generateMemory(): void { commandHeap.push(obj); } -function resetFromSpace(fromSpace, heap): number[] { +export function resetFromSpace(fromSpace, heap): number[] { const newHeap: number[] = []; if (fromSpace > 0) { for (let i = 0; i < MEMORY_SIZE / 2; i += 1) { @@ -92,7 +96,7 @@ function resetFromSpace(fromSpace, heap): number[] { return newHeap; } -function initialize_memory(memorySize: number): void { +export function initialize_memory(memorySize: number): void { MEMORY_SIZE = memorySize; ROW = MEMORY_SIZE / COLUMN; TO_SPACE = 0; @@ -100,7 +104,7 @@ function initialize_memory(memorySize: number): void { generateMemory(); } -function newCommand( +export function newCommand( type, toSpace, fromSpace, @@ -148,7 +152,7 @@ function newCommand( commandHeap.push(obj); } -function newCopy(left, right, heap): void { +export function newCopy(left, right, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -170,7 +174,7 @@ function newCopy(left, right, heap): void { ); } -function endFlip(left, heap): void { +export function endFlip(left, heap): void { const { length } = commandHeap; const fromSpace = commandHeap[length - 1].from; const toSpace = commandHeap[length - 1].to; @@ -192,17 +196,17 @@ function endFlip(left, heap): void { updateFlip(); } -function updateRoots(array): void { +export function updateRoots(array): void { for (let i = 0; i < array.length; i += 1) { ROOTS.push(array[i]); } } -function resetRoots(): void { +export function resetRoots(): void { ROOTS = []; } -function startFlip(toSpace, fromSpace, heap): void { +export function startFlip(toSpace, fromSpace, heap): void { const desc = 'Memory is exhausted. Start stop and copy garbage collector.'; newCommand( 'Start of Cheneys', @@ -220,7 +224,7 @@ function startFlip(toSpace, fromSpace, heap): void { updateFlip(); } -function newPush(left, right, heap): void { +export function newPush(left, right, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -240,7 +244,7 @@ function newPush(left, right, heap): void { ); } -function newPop(res, left, right, heap): void { +export function newPop(res, left, right, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -261,7 +265,7 @@ function newPop(res, left, right, heap): void { ); } -function doneShowRoot(heap): void { +export function doneShowRoot(heap): void { const toSpace = 0; const fromSpace = 0; const desc = 'All root nodes are copied'; @@ -280,7 +284,7 @@ function doneShowRoot(heap): void { ); } -function showRoots(left, heap): void { +export function showRoots(left, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -301,7 +305,7 @@ function showRoots(left, heap): void { ); } -function newAssign(res, left, heap): void { +export function newAssign(res, left, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -322,7 +326,7 @@ function newAssign(res, left, heap): void { ); } -function newNew(left, heap): void { +export function newNew(left, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -343,7 +347,7 @@ function newNew(left, heap): void { ); } -function scanFlip(left, right, scan, free, heap): void { +export function scanFlip(left, right, scan, free, heap): void { const { length } = commandHeap; const toSpace = commandHeap[length - 1].to; const fromSpace = commandHeap[length - 1].from; @@ -386,7 +390,7 @@ function scanFlip(left, right, scan, free, heap): void { commandHeap.push(obj); } -function updateSlotSegment( +export function updateSlotSegment( tag: number, size: number, first: number, @@ -462,7 +466,7 @@ function get_row_size(): number { return ROW; } -function init() { +export function init() { return { toReplString: () => '', get_memory_size, @@ -481,27 +485,3 @@ function init() { get_roots }; } - -export { - init, - // initialisation - initialize_memory, - initialize_tag, - generateMemory, - allHeap, - updateSlotSegment, - resetFromSpace, - newCommand, - newCopy, - endFlip, - newPush, - newPop, - newAssign, - newNew, - scanFlip, - startFlip, - updateRoots, - resetRoots, - showRoots, - doneShowRoot -}; diff --git a/src/bundles/copy_gc/tsconfig.json b/src/bundles/copy_gc/tsconfig.json index 1abc1ef424..c5df413ad7 100644 --- a/src/bundles/copy_gc/tsconfig.json +++ b/src/bundles/copy_gc/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "../tsconfig.json", + "exclude": ["./dist"], "include": [ "./src" ], diff --git a/src/bundles/csg/manifest.json b/src/bundles/csg/manifest.json new file mode 100644 index 0000000000..4ccaa72c38 --- /dev/null +++ b/src/bundles/csg/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Csg" + ] +} \ No newline at end of file diff --git a/src/bundles/csg/package.json b/src/bundles/csg/package.json index 3950b90237..df4818d40f 100644 --- a/src/bundles/csg/package.json +++ b/src/bundles/csg/package.json @@ -19,6 +19,6 @@ "type": "module", "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/curve/manifest.json b/src/bundles/curve/manifest.json index e195095ce0..1436baf8c5 100644 --- a/src/bundles/curve/manifest.json +++ b/src/bundles/curve/manifest.json @@ -1,4 +1,5 @@ { - "name": "curve", - "tabs": ["Curve"] + "tabs": [ + "Curve" + ] } \ No newline at end of file diff --git a/src/bundles/curve/package.json b/src/bundles/curve/package.json index e936904de1..2aea349b16 100644 --- a/src/bundles/curve/package.json +++ b/src/bundles/curve/package.json @@ -13,11 +13,10 @@ "type": "module", "exports": { ".": "./dist/index.js", - "./*": "./dist/*.js", - "./*.js": "./dist/*.js" + "./*": "./dist/*.js" }, "scripts": { - "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "tsc": "tsc --project ./tsconfig.prod.json", + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/curve/src/index.ts b/src/bundles/curve/src/index.ts index 8e88b81b5c..a5f3df5971 100644 --- a/src/bundles/curve/src/index.ts +++ b/src/bundles/curve/src/index.ts @@ -79,4 +79,4 @@ export {} from './type_interface'; export { type_map -} from '../../typings/type_map'; +} from '@sourceacademy/modules-lib/type_map'; diff --git a/src/bundles/curve/src/type_interface.ts b/src/bundles/curve/src/type_interface.ts index e31c82fb58..bef1c3a84c 100644 --- a/src/bundles/curve/src/type_interface.ts +++ b/src/bundles/curve/src/type_interface.ts @@ -1,4 +1,4 @@ -import { classDeclaration, typeDeclaration, functionDeclaration } from '../../typings/type_map'; +import { classDeclaration, typeDeclaration, functionDeclaration } from '@sourceacademy/modules-lib/type_map'; @classDeclaration('Point') export class Point {} diff --git a/src/bundles/curve/tsconfig.json b/src/bundles/curve/tsconfig.json index 1abc1ef424..662ac7ea48 100644 --- a/src/bundles/curve/tsconfig.json +++ b/src/bundles/curve/tsconfig.json @@ -1,9 +1,10 @@ +// curve bundle tests { "extends": "../tsconfig.json", "include": [ "./src" ], "compilerOptions": { - "outDir": "./dist" + "noEmit": true } } \ No newline at end of file diff --git a/src/bundles/curve/tsconfig.prod.json b/src/bundles/curve/tsconfig.prod.json new file mode 100644 index 0000000000..c6c1a6261a --- /dev/null +++ b/src/bundles/curve/tsconfig.prod.json @@ -0,0 +1,9 @@ +// Curve Bundle production +{ + "compilerOptions": { + "noEmit": false, + "outDir": "./dist" + }, + "extends": "./tsconfig.json", + "exclude": ["**/__tests__"] +} \ No newline at end of file diff --git a/src/bundles/game/manifest.json b/src/bundles/game/manifest.json new file mode 100644 index 0000000000..aaaccaf965 --- /dev/null +++ b/src/bundles/game/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Game" + ] +} \ No newline at end of file diff --git a/src/bundles/game/package.json b/src/bundles/game/package.json index 4c58b1bb71..6fe383ef4d 100644 --- a/src/bundles/game/package.json +++ b/src/bundles/game/package.json @@ -18,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/jest.config.js b/src/bundles/jest.config.js new file mode 100644 index 0000000000..a91be09fbd --- /dev/null +++ b/src/bundles/jest.config.js @@ -0,0 +1,30 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +export default { + displayName: 'Bundles', + testEnvironment: 'jsdom', + extensionsToTreatAsEsm: [".ts"], + preset: 'ts-jest/presets/default-esm', + // transform: { + // '.ts': ['ts-jest', { + // useESM: true, + // /** + // * ts-jest preset currently has an issue with the 'verbatimModuleSyntax' typescript option: + // * This whole transform bit should be removed once this is resolved: + // * https://github.com/kulshekhar/ts-jest/issues/4081 + // */ + // isolatedModules: true + // }] + // }, + moduleNameMapper: { + '#(.*)': '/node_modules/$1', + '^js-slang/context': '/__mocks__/context.ts', + '^three.+.js': '/__mocks__/emptyModule.ts', + }, + transformIgnorePatterns: [ + 'node_modules/(?!=chalk)/', + '.+\\.js' + ], + setupFiles: [ + './jest.polyfills.js' + ] +}; diff --git a/src/bundles/jest.polyfills.js b/src/bundles/jest.polyfills.js new file mode 100644 index 0000000000..9018b43bb4 --- /dev/null +++ b/src/bundles/jest.polyfills.js @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const { TextDecoder, TextEncoder } = require('node:util'); + +// According to the Jest docs, https://mswjs.io/docs/migrations/1.x-to-2.x#environment +Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, +}); diff --git a/src/bundles/mark_sweep/manifest.json b/src/bundles/mark_sweep/manifest.json new file mode 100644 index 0000000000..5917c70e2c --- /dev/null +++ b/src/bundles/mark_sweep/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "MarkSweep" + ] +} \ No newline at end of file diff --git a/src/bundles/mark_sweep/package.json b/src/bundles/mark_sweep/package.json index e2a2677532..c5363caecd 100644 --- a/src/bundles/mark_sweep/package.json +++ b/src/bundles/mark_sweep/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/mark_sweep/src/index.ts b/src/bundles/mark_sweep/src/index.ts index f8c4466e41..2dcde4ca01 100644 --- a/src/bundles/mark_sweep/src/index.ts +++ b/src/bundles/mark_sweep/src/index.ts @@ -1,3 +1,7 @@ +/** + * @module mark_sweep + */ + import { type MemoryHeaps, type Memory, type Tag, COMMAND, type CommandHeapObject } from './types'; // Global Variables diff --git a/src/bundles/nbody/manifest.json b/src/bundles/nbody/manifest.json new file mode 100644 index 0000000000..074a2c023f --- /dev/null +++ b/src/bundles/nbody/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Nbody" + ] +} \ No newline at end of file diff --git a/src/bundles/nbody/package.json b/src/bundles/nbody/package.json index 29918a36a6..2bb32aa63c 100644 --- a/src/bundles/nbody/package.json +++ b/src/bundles/nbody/package.json @@ -17,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/package.json b/src/bundles/package.json index 839d7476a0..d7dd351568 100644 --- a/src/bundles/package.json +++ b/src/bundles/package.json @@ -1,5 +1,17 @@ { "private": true, "name": "bundles", - "workspaces": ["./*"] + "type": "module", + "workspaces": [ + "./*" + ], + "devDependencies": { + "@types/jest": "^29.0.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.4.1", + "ts-jest": "^29.1.2" + }, + "scripts": { + "test": "jest -c ./jest.config.js" + } } diff --git a/src/bundles/painter/manifest.json b/src/bundles/painter/manifest.json new file mode 100644 index 0000000000..f37d20df31 --- /dev/null +++ b/src/bundles/painter/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Painter" + ] +} \ No newline at end of file diff --git a/src/bundles/painter/package.json b/src/bundles/painter/package.json index 1bbeae9da0..fcc0bea6c5 100644 --- a/src/bundles/painter/package.json +++ b/src/bundles/painter/package.json @@ -18,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/physics_2d/manifest.json b/src/bundles/physics_2d/manifest.json new file mode 100644 index 0000000000..73bdfc85b5 --- /dev/null +++ b/src/bundles/physics_2d/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "physics_2d" + ] +} \ No newline at end of file diff --git a/src/bundles/physics_2d/package.json b/src/bundles/physics_2d/package.json index 16f1d16a95..4ae731c5b8 100644 --- a/src/bundles/physics_2d/package.json +++ b/src/bundles/physics_2d/package.json @@ -17,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/pix_n_flix/manifest.json b/src/bundles/pix_n_flix/manifest.json new file mode 100644 index 0000000000..0cb5cd935a --- /dev/null +++ b/src/bundles/pix_n_flix/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Pixnflix" + ] +} \ No newline at end of file diff --git a/src/bundles/pix_n_flix/package.json b/src/bundles/pix_n_flix/package.json index 9a9bea1bfb..3fe4480102 100644 --- a/src/bundles/pix_n_flix/package.json +++ b/src/bundles/pix_n_flix/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/plotly/manifest.json b/src/bundles/plotly/manifest.json new file mode 100644 index 0000000000..4d672d8344 --- /dev/null +++ b/src/bundles/plotly/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Plotly" + ] +} \ No newline at end of file diff --git a/src/bundles/plotly/package.json b/src/bundles/plotly/package.json index 4e45aa28c4..b98a2ec014 100644 --- a/src/bundles/plotly/package.json +++ b/src/bundles/plotly/package.json @@ -19,6 +19,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/plotly/src/index.ts b/src/bundles/plotly/src/index.ts index e588d53add..a73c0b5282 100644 --- a/src/bundles/plotly/src/index.ts +++ b/src/bundles/plotly/src/index.ts @@ -1,6 +1,7 @@ /** * Bundle for Source Academy Plotly repository * @author Sourabh Raj Jaiswal + * @module plotly */ export { diff --git a/src/bundles/remote_execution/manifest.json b/src/bundles/remote_execution/manifest.json new file mode 100644 index 0000000000..4e75d9a969 --- /dev/null +++ b/src/bundles/remote_execution/manifest.json @@ -0,0 +1,3 @@ +{ + "tabs": [] +} \ No newline at end of file diff --git a/src/bundles/remote_execution/package.json b/src/bundles/remote_execution/package.json index 4d906227ee..826873a64c 100644 --- a/src/bundles/remote_execution/package.json +++ b/src/bundles/remote_execution/package.json @@ -17,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/repeat/manifest.json b/src/bundles/repeat/manifest.json new file mode 100644 index 0000000000..18b6860526 --- /dev/null +++ b/src/bundles/repeat/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Repeat" + ] +} \ No newline at end of file diff --git a/src/bundles/repeat/package.json b/src/bundles/repeat/package.json index 6a21f98c35..12af0d95ab 100644 --- a/src/bundles/repeat/package.json +++ b/src/bundles/repeat/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/repeat/src/index.ts b/src/bundles/repeat/src/index.ts index 8e1023ae46..ae9130f2d8 100644 --- a/src/bundles/repeat/src/index.ts +++ b/src/bundles/repeat/src/index.ts @@ -2,6 +2,7 @@ * Test bundle for Source Academy modules repository * @author Loh Xian Ze, Bryan * @author Tang Xin Kye, Marcus + * @module repeat */ export { repeat, twice, thrice } from './functions'; diff --git a/src/bundles/repl/manifest.json b/src/bundles/repl/manifest.json new file mode 100644 index 0000000000..b425c2cf1e --- /dev/null +++ b/src/bundles/repl/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Repl" + ] +} \ No newline at end of file diff --git a/src/bundles/repl/package.json b/src/bundles/repl/package.json index 2f134582c8..00fcb3cb8e 100644 --- a/src/bundles/repl/package.json +++ b/src/bundles/repl/package.json @@ -17,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/robot_simulation/manifest.json b/src/bundles/robot_simulation/manifest.json new file mode 100644 index 0000000000..eca65caa11 --- /dev/null +++ b/src/bundles/robot_simulation/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "RobotSimulation" + ] +} \ No newline at end of file diff --git a/src/bundles/robot_simulation/package.json b/src/bundles/robot_simulation/package.json index 1a23a85679..a13c04a095 100644 --- a/src/bundles/robot_simulation/package.json +++ b/src/bundles/robot_simulation/package.json @@ -18,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/rune/manifest.json b/src/bundles/rune/manifest.json new file mode 100644 index 0000000000..21c55e6c26 --- /dev/null +++ b/src/bundles/rune/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Rune" + ] +} \ No newline at end of file diff --git a/src/bundles/rune/package.json b/src/bundles/rune/package.json index bac1c8afd0..8dc91f842c 100644 --- a/src/bundles/rune/package.json +++ b/src/bundles/rune/package.json @@ -18,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/rune/src/display.ts b/src/bundles/rune/src/display.ts index d3479c11b9..bc6593a9b4 100644 --- a/src/bundles/rune/src/display.ts +++ b/src/bundles/rune/src/display.ts @@ -1,5 +1,5 @@ import context from 'js-slang/context'; -import { functionDeclaration } from '../../typings/type_map'; +import { functionDeclaration } from '@sourceacademy/modules-lib/type_map'; import { AnaglyphRune, HollusionRune } from './functions'; import { AnimatedRune, NormalRune, type DrawnRune, type Rune, type RuneAnimation } from './rune'; import { throwIfNotRune } from './runes_ops'; diff --git a/src/bundles/rune/src/functions.ts b/src/bundles/rune/src/functions.ts index be70f3a92f..2e031c6842 100644 --- a/src/bundles/rune/src/functions.ts +++ b/src/bundles/rune/src/functions.ts @@ -2,7 +2,7 @@ import { mat4, vec3 } from 'gl-matrix'; import { functionDeclaration, variableDeclaration, -} from '../../typings/type_map'; +} from '@sourceacademy/modules-lib/type_map'; import { Rune, DrawnRune, diff --git a/src/bundles/rune/src/index.ts b/src/bundles/rune/src/index.ts index fd21ea0905..a446cbdbba 100644 --- a/src/bundles/rune/src/index.ts +++ b/src/bundles/rune/src/index.ts @@ -62,4 +62,4 @@ export { export { type_map -} from '../../typings/type_map'; +} from '@sourceacademy/modules-lib/type_map'; diff --git a/src/bundles/rune/src/rune.ts b/src/bundles/rune/src/rune.ts index 76afcb6be4..ba6db5f1c5 100644 --- a/src/bundles/rune/src/rune.ts +++ b/src/bundles/rune/src/rune.ts @@ -1,6 +1,6 @@ import { type AnimFrame, type ReplResult, glAnimation } from '@sourceacademy/modules-lib/types'; import { mat4 } from 'gl-matrix'; -import { classDeclaration } from '../../typings/type_map'; +import { classDeclaration } from '@sourceacademy/modules-lib/type_map'; import { getWebGlFromCanvas, initShaderProgram } from './runes_webgl'; const normalVertexShader = ` diff --git a/src/bundles/rune_in_words/manifest.json b/src/bundles/rune_in_words/manifest.json new file mode 100644 index 0000000000..4e75d9a969 --- /dev/null +++ b/src/bundles/rune_in_words/manifest.json @@ -0,0 +1,3 @@ +{ + "tabs": [] +} \ No newline at end of file diff --git a/src/bundles/rune_in_words/package.json b/src/bundles/rune_in_words/package.json index 1729cfe00c..1e30b77ef9 100644 --- a/src/bundles/rune_in_words/package.json +++ b/src/bundles/rune_in_words/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/scrabble/manifest.json b/src/bundles/scrabble/manifest.json new file mode 100644 index 0000000000..4e75d9a969 --- /dev/null +++ b/src/bundles/scrabble/manifest.json @@ -0,0 +1,3 @@ +{ + "tabs": [] +} \ No newline at end of file diff --git a/src/bundles/scrabble/package.json b/src/bundles/scrabble/package.json index c2e57e2321..cd1cf50e95 100644 --- a/src/bundles/scrabble/package.json +++ b/src/bundles/scrabble/package.json @@ -14,6 +14,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/scrabble/src/index.ts b/src/bundles/scrabble/src/index.ts index 91d459a942..28147eda01 100644 --- a/src/bundles/scrabble/src/index.ts +++ b/src/bundles/scrabble/src/index.ts @@ -1,6 +1,7 @@ /** * Scrabble words for Source Academy * @author Martin Henz + * @module scrabble */ export { scrabble_words, diff --git a/src/bundles/sound/manifest.json b/src/bundles/sound/manifest.json new file mode 100644 index 0000000000..2cf429b785 --- /dev/null +++ b/src/bundles/sound/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Sound" + ] +} \ No newline at end of file diff --git a/src/bundles/sound/package.json b/src/bundles/sound/package.json index 2f97adeffb..152d68864c 100644 --- a/src/bundles/sound/package.json +++ b/src/bundles/sound/package.json @@ -2,6 +2,9 @@ "name": "@sourceacademy/bundle-sound", "version": "1.0.0", "private": true, + "dependencies": { + "js-slang": "^1.0.81" + }, "devDependencies": { "@sourceacademy/module-buildtools": "workspace:^", "typescript": "^5.8.2" @@ -14,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/sound_matrix/manifest.json b/src/bundles/sound_matrix/manifest.json new file mode 100644 index 0000000000..b5adb3795e --- /dev/null +++ b/src/bundles/sound_matrix/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "SoundMatrix" + ] +} \ No newline at end of file diff --git a/src/bundles/sound_matrix/package.json b/src/bundles/sound_matrix/package.json index 3c8b680e81..b35b46d8ef 100644 --- a/src/bundles/sound_matrix/package.json +++ b/src/bundles/sound_matrix/package.json @@ -17,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/sound_matrix/src/index.ts b/src/bundles/sound_matrix/src/index.ts index ad42ec791c..831a2d66bb 100644 --- a/src/bundles/sound_matrix/src/index.ts +++ b/src/bundles/sound_matrix/src/index.ts @@ -1,4 +1,5 @@ /** + * @module sound_matrix * @author Samyukta Sounderraman */ diff --git a/src/bundles/stereo_sound/manifest.json b/src/bundles/stereo_sound/manifest.json new file mode 100644 index 0000000000..13b99fdc04 --- /dev/null +++ b/src/bundles/stereo_sound/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "StereoSound" + ] +} \ No newline at end of file diff --git a/src/bundles/stereo_sound/package.json b/src/bundles/stereo_sound/package.json index f176e93322..230df292c4 100644 --- a/src/bundles/stereo_sound/package.json +++ b/src/bundles/stereo_sound/package.json @@ -6,6 +6,9 @@ "@sourceacademy/module-buildtools": "workspace:^", "typescript": "^5.8.2" }, + "dependencies": { + "js-slang": "^1.0.81" + }, "type": "module", "exports": { ".": "./dist/index.js", @@ -14,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/tsconfig.json b/src/bundles/tsconfig.json index 61e8238399..0049b9dd1f 100644 --- a/src/bundles/tsconfig.json +++ b/src/bundles/tsconfig.json @@ -1,7 +1,6 @@ // bundles tsconfig { "compilerOptions": { - "emitDeclarationOnly": true, "declaration": true, "paths": { "js-slang/context": ["../modules-lib/src/types/js-slang/context.d.ts"] diff --git a/src/bundles/unittest/manifest.json b/src/bundles/unittest/manifest.json new file mode 100644 index 0000000000..87c96de98b --- /dev/null +++ b/src/bundles/unittest/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "Unittest" + ] +} \ No newline at end of file diff --git a/src/bundles/unittest/package.json b/src/bundles/unittest/package.json index f8b522095a..db61ed2cf4 100644 --- a/src/bundles/unittest/package.json +++ b/src/bundles/unittest/package.json @@ -2,6 +2,9 @@ "name": "@sourceacademy/bundle-unittest", "version": "1.0.0", "private": true, + "dependencies": { + "js-slang": "^1.0.81" + }, "devDependencies": { "@sourceacademy/module-buildtools": "workspace:^", "typescript": "^5.8.2" @@ -14,6 +17,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/unittest/src/index.ts b/src/bundles/unittest/src/index.ts index d7c9d79e79..57d6418813 100644 --- a/src/bundles/unittest/src/index.ts +++ b/src/bundles/unittest/src/index.ts @@ -1,9 +1,10 @@ /** * Collection of unit-testing tools for Source. + * @module unittest * @author Jia Xiaodong */ -import { +export { assert_equals, assert_not_equals, assert_contains, @@ -11,26 +12,14 @@ import { assert_greater_equals, assert_length, } from './asserts'; -import { it, describe } from './functions'; -import { mock_fn } from './mocks'; +export { it, describe } from './functions'; +export { mock_fn } from './mocks'; + /** * Increment a number by a value of 1. * @param x the number to be incremented * @returns the incremented value of the number */ -function sample_function(x: number) { +export function sample_function(x: number) { return x + 1; } - -export default { - sample_function, - it, - describe, - assert_equals, - assert_not_equals, - assert_contains, - assert_greater, - assert_greater_equals, - assert_length, - mock_fn, -}; diff --git a/src/bundles/unity_academy/manifest.json b/src/bundles/unity_academy/manifest.json new file mode 100644 index 0000000000..d1cc71a240 --- /dev/null +++ b/src/bundles/unity_academy/manifest.json @@ -0,0 +1,5 @@ +{ + "tabs": [ + "UnityAcademy" + ] +} \ No newline at end of file diff --git a/src/bundles/unity_academy/package.json b/src/bundles/unity_academy/package.json index 6863a249c8..0d6a60d41d 100644 --- a/src/bundles/unity_academy/package.json +++ b/src/bundles/unity_academy/package.json @@ -5,11 +5,13 @@ "dependencies": { "@blueprintjs/core": "^5.10.2", "@blueprintjs/icons": "^5.9.0", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.2", + "@types/react-dom": "^18.3.0", "typescript": "^5.8.2" }, "type": "module", @@ -20,6 +22,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/bundles/unity_academy/tsconfig.json b/src/bundles/unity_academy/tsconfig.json index 4a068b604e..0f8e85f2c7 100644 --- a/src/bundles/unity_academy/tsconfig.json +++ b/src/bundles/unity_academy/tsconfig.json @@ -1,10 +1,11 @@ +// unity_academy bundle { "extends": "../tsconfig.json", "include": [ "./src" ], "compilerOptions": { - "outDir": "./dist", - "jsx": "react-jsx" + "jsx": "react-jsx", + "outDir": "./dist" } } \ No newline at end of file diff --git a/src/bundles/wasm/manifest.json b/src/bundles/wasm/manifest.json new file mode 100644 index 0000000000..4e75d9a969 --- /dev/null +++ b/src/bundles/wasm/manifest.json @@ -0,0 +1,3 @@ +{ + "tabs": [] +} \ No newline at end of file diff --git a/src/bundles/wasm/package.json b/src/bundles/wasm/package.json index 0056f9c779..7eae4c9e2e 100644 --- a/src/bundles/wasm/package.json +++ b/src/bundles/wasm/package.json @@ -6,6 +6,10 @@ "@sourceacademy/module-buildtools": "workspace:^", "typescript": "^5.8.2" }, + "dependencies": { + "source-academy-utils": "^1.0.0", + "source-academy-wabt": "^1.0.4" + }, "type": "module", "exports": { ".": "./dist/index.js", @@ -14,6 +18,6 @@ }, "scripts": { "tsc": "tsc --project ./tsconfig.json", - "build": "buildtools build-bundle ." + "build": "buildtools build bundle ." } -} \ No newline at end of file +} diff --git a/src/tabs/ArcadeTwod/package.json b/src/tabs/ArcadeTwod/package.json index a06567ab34..435f23a2dd 100644 --- a/src/tabs/ArcadeTwod/package.json +++ b/src/tabs/ArcadeTwod/package.json @@ -5,9 +5,14 @@ "dependencies": { "@blueprintjs/core": "^5.10.2", "@blueprintjs/icons": "^5.9.0", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/AugmentedReality/package.json b/src/tabs/AugmentedReality/package.json index 63baff080a..4bfdc429fd 100644 --- a/src/tabs/AugmentedReality/package.json +++ b/src/tabs/AugmentedReality/package.json @@ -5,10 +5,15 @@ "dependencies": { "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", + "react-dom": "^18.3.1", "saar": "^1.0.4" }, "devDependencies": { "@sourceacademy/bundle-ar": "workspace:^", + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/CopyGc/package.json b/src/tabs/CopyGc/package.json index 6940891c5a..0ff1a6529b 100644 --- a/src/tabs/CopyGc/package.json +++ b/src/tabs/CopyGc/package.json @@ -3,9 +3,14 @@ "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Csg/package.json b/src/tabs/Csg/package.json index f36f49d53e..89d6d6b652 100644 --- a/src/tabs/Csg/package.json +++ b/src/tabs/Csg/package.json @@ -7,9 +7,14 @@ "@blueprintjs/icons": "^5.9.0", "@sourceacademy/bundle-csg": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Curve/package.json b/src/tabs/Curve/package.json index 6a71810e7c..5d5b1659e7 100644 --- a/src/tabs/Curve/package.json +++ b/src/tabs/Curve/package.json @@ -7,9 +7,14 @@ "@blueprintjs/icons": "^5.9.0", "@sourceacademy/bundle-curve": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Curve/src/__tests__/Curve.tsx b/src/tabs/Curve/src/__tests__/Curve.tsx index 77428afd60..7e384c9c4f 100644 --- a/src/tabs/Curve/src/__tests__/Curve.tsx +++ b/src/tabs/Curve/src/__tests__/Curve.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { CurveTab } from '..'; -import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected } from '../../../bundles/curve'; -import type { CurveModuleState } from '../../../bundles/curve/types'; -import { mockDebuggerContext } from '../../common/testUtils'; +import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected } from '@sourceacademy/bundle-curve'; +import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; +import { mockDebuggerContext } from '@sourceacademy/modules-lib/utilities'; test('Curve animations error gracefully', () => { const badAnimation = animate_curve(1, 60, draw_connected(200), t => 1 as any); diff --git a/src/tabs/Curve/tsconfig.json b/src/tabs/Curve/tsconfig.json index 97c73a120e..f42c653115 100644 --- a/src/tabs/Curve/tsconfig.json +++ b/src/tabs/Curve/tsconfig.json @@ -1,6 +1,4 @@ { "extends": "../tsconfig.json", - "include": [ - "./src" - ] + "include": ["./src"] } \ No newline at end of file diff --git a/src/tabs/Game/package.json b/src/tabs/Game/package.json index 7de319fa56..a533dcba0c 100644 --- a/src/tabs/Game/package.json +++ b/src/tabs/Game/package.json @@ -3,9 +3,14 @@ "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/MarkSweep/package.json b/src/tabs/MarkSweep/package.json index 57f4249335..9fcd13dfb3 100644 --- a/src/tabs/MarkSweep/package.json +++ b/src/tabs/MarkSweep/package.json @@ -3,9 +3,14 @@ "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Nbody/package.json b/src/tabs/Nbody/package.json index 3b8811deb5..1165b9d2b2 100644 --- a/src/tabs/Nbody/package.json +++ b/src/tabs/Nbody/package.json @@ -4,9 +4,14 @@ "private": true, "dependencies": { "nbody": "^0.2.0", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Painter/package.json b/src/tabs/Painter/package.json index 8ebdf42b01..1b95264151 100644 --- a/src/tabs/Painter/package.json +++ b/src/tabs/Painter/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-painter": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Painter/tsconfig.json b/src/tabs/Painter/tsconfig.json index e4dd09d989..e69de29bb2 100644 --- a/src/tabs/Painter/tsconfig.json +++ b/src/tabs/Painter/tsconfig.json @@ -1,4 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": ["./index.tsx"] -} \ No newline at end of file diff --git a/src/tabs/Physics2D/package.json b/src/tabs/Physics2D/package.json index cff1eb3103..63005eb170 100644 --- a/src/tabs/Physics2D/package.json +++ b/src/tabs/Physics2D/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-physics_2d": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Pixnflix/package.json b/src/tabs/Pixnflix/package.json index 166bfe5a65..3fcdb411d8 100644 --- a/src/tabs/Pixnflix/package.json +++ b/src/tabs/Pixnflix/package.json @@ -4,9 +4,14 @@ "private": true, "dependencies": { "@sourceacademy/bundle-pix_n_flix": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Plotly/package.json b/src/tabs/Plotly/package.json index 905b488234..c3c04eecd7 100644 --- a/src/tabs/Plotly/package.json +++ b/src/tabs/Plotly/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-plotly": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Repeat/package.json b/src/tabs/Repeat/package.json index 8b4be1ca07..533e76cfd9 100644 --- a/src/tabs/Repeat/package.json +++ b/src/tabs/Repeat/package.json @@ -3,9 +3,14 @@ "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Repl/package.json b/src/tabs/Repl/package.json index 3fb21e8177..62da3e8d05 100644 --- a/src/tabs/Repl/package.json +++ b/src/tabs/Repl/package.json @@ -6,9 +6,14 @@ "@sourceacademy/modules-lib": "workspace:^", "ace-builds": "^1.25.1", "react": "^18.3.1", - "react-ace": "^10.1.0" + "react-ace": "^10.1.0", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/RobotSimulation/package.json b/src/tabs/RobotSimulation/package.json index 0c5c7df621..0b92a517e7 100644 --- a/src/tabs/RobotSimulation/package.json +++ b/src/tabs/RobotSimulation/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-robot_simulation": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Rune/package.json b/src/tabs/Rune/package.json index f910867c22..3fe32d30bc 100644 --- a/src/tabs/Rune/package.json +++ b/src/tabs/Rune/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-rune": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Sound/package.json b/src/tabs/Sound/package.json index 1374514ca0..85bec675f1 100644 --- a/src/tabs/Sound/package.json +++ b/src/tabs/Sound/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-sound": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/SoundMatrix/package.json b/src/tabs/SoundMatrix/package.json index e0aa2e2a0b..e10ab75d02 100644 --- a/src/tabs/SoundMatrix/package.json +++ b/src/tabs/SoundMatrix/package.json @@ -3,9 +3,14 @@ "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/StereoSound/package.json b/src/tabs/StereoSound/package.json index 024dc004bd..4be8717ff7 100644 --- a/src/tabs/StereoSound/package.json +++ b/src/tabs/StereoSound/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-stereo_sound": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/Unittest/package.json b/src/tabs/Unittest/package.json index dab36b85c2..b36c25f360 100644 --- a/src/tabs/Unittest/package.json +++ b/src/tabs/Unittest/package.json @@ -5,9 +5,14 @@ "dependencies": { "@sourceacademy/bundle-unittest": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/UnityAcademy/package.json b/src/tabs/UnityAcademy/package.json index e0c7e78c0c..c382a509ad 100644 --- a/src/tabs/UnityAcademy/package.json +++ b/src/tabs/UnityAcademy/package.json @@ -6,9 +6,14 @@ "@blueprintjs/core": "^5.10.2", "@blueprintjs/icons": "^5.9.0", "@sourceacademy/bundle-unity_academy": "workspace:^", - "react": "^18.3.1" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { + "@sourceacademy/module-buildtools": "workspace:^", "@types/react": "^18.3.1" + }, + "scripts": { + "build": "buildtools build tab ." } } diff --git a/src/tabs/tsconfig.json b/src/tabs/tsconfig.json index f628782936..eb85faf3bf 100644 --- a/src/tabs/tsconfig.json +++ b/src/tabs/tsconfig.json @@ -1,6 +1,10 @@ +// Tabs tsconfig { "extends": "../tsconfig.json", "compilerOptions": { - "jsx": "react-jsx" - } + "jsx": "react-jsx", + "noEmit": true + }, + "exclude": [], + "include": ["**/__tests__"] } From ea46cb78f6d1d0ad8dbb0defa92144933c18a509 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 19 May 2025 16:25:22 -0400 Subject: [PATCH 013/112] A working set of commands --- src/buildtools/.gitignore | 2 +- .../{src/build/docs => bin}/docsreadme.md | 0 .../templates/bundle/src/index.ts | 0 .../templates/bundle/tsconfig.json | 0 .../templates => bin}/templates/tab/index.tsx | 0 .../templates/tab/tsconfig.json | 0 src/buildtools/build.js | 25 +++++ src/buildtools/package.json | 13 ++- src/buildtools/src/build/docs/docsUtils.ts | 26 ++--- src/buildtools/src/build/docs/index.ts | 14 ++- .../src/build/{modules => }/manifest.ts | 66 ++++++++++++- src/buildtools/src/build/modules/bundle.ts | 24 ++--- src/buildtools/src/build/modules/commons.ts | 5 +- src/buildtools/src/build/modules/index.ts | 28 +++--- .../src/build/modules/manifest.schema.json | 6 +- src/buildtools/src/build/modules/tab.ts | 49 ++++++++-- .../__tests__/template.test.ts | 5 +- .../__tests__/{testing.ts => testing.test.ts} | 2 +- src/buildtools/src/commands/build.ts | 59 ++++++++--- src/buildtools/src/commands/list.ts | 28 ++++++ src/buildtools/src/commands/main.ts | 6 +- src/buildtools/src/commands/template.ts | 10 +- src/buildtools/src/commands/testing.ts | 10 +- src/buildtools/src/templates/bundle.ts | 8 +- src/buildtools/src/templates/tab.ts | 7 +- src/buildtools/src/templates/utilities.ts | 2 +- src/buildtools/src/testing/runner.ts | 10 ++ src/buildtools/src/utils.ts | 43 ++++++++ src/buildtools/tsconfig.json | 3 +- src/buildtools/workspacer.py | 97 +++++++++++++++++++ 30 files changed, 443 insertions(+), 105 deletions(-) rename src/buildtools/{src/build/docs => bin}/docsreadme.md (100%) rename src/buildtools/{src/templates => bin}/templates/bundle/src/index.ts (100%) rename src/buildtools/{src/templates => bin}/templates/bundle/tsconfig.json (100%) rename src/buildtools/{src/templates => bin}/templates/tab/index.tsx (100%) rename src/buildtools/{src/templates => bin}/templates/tab/tsconfig.json (100%) create mode 100644 src/buildtools/build.js rename src/buildtools/src/build/{modules => }/manifest.ts (53%) rename src/buildtools/src/{templates => commands}/__tests__/template.test.ts (96%) rename src/buildtools/src/commands/__tests__/{testing.ts => testing.test.ts} (92%) create mode 100644 src/buildtools/src/commands/list.ts create mode 100644 src/buildtools/src/testing/runner.ts create mode 100644 src/buildtools/src/utils.ts create mode 100644 src/buildtools/workspacer.py diff --git a/src/buildtools/.gitignore b/src/buildtools/.gitignore index 3f59370baf..55a319dd92 100644 --- a/src/buildtools/.gitignore +++ b/src/buildtools/.gitignore @@ -1 +1 @@ -bin.js \ No newline at end of file +bin/index.js \ No newline at end of file diff --git a/src/buildtools/src/build/docs/docsreadme.md b/src/buildtools/bin/docsreadme.md similarity index 100% rename from src/buildtools/src/build/docs/docsreadme.md rename to src/buildtools/bin/docsreadme.md diff --git a/src/buildtools/src/templates/templates/bundle/src/index.ts b/src/buildtools/bin/templates/bundle/src/index.ts similarity index 100% rename from src/buildtools/src/templates/templates/bundle/src/index.ts rename to src/buildtools/bin/templates/bundle/src/index.ts diff --git a/src/buildtools/src/templates/templates/bundle/tsconfig.json b/src/buildtools/bin/templates/bundle/tsconfig.json similarity index 100% rename from src/buildtools/src/templates/templates/bundle/tsconfig.json rename to src/buildtools/bin/templates/bundle/tsconfig.json diff --git a/src/buildtools/src/templates/templates/tab/index.tsx b/src/buildtools/bin/templates/tab/index.tsx similarity index 100% rename from src/buildtools/src/templates/templates/tab/index.tsx rename to src/buildtools/bin/templates/tab/index.tsx diff --git a/src/buildtools/src/templates/templates/tab/tsconfig.json b/src/buildtools/bin/templates/tab/tsconfig.json similarity index 100% rename from src/buildtools/src/templates/templates/tab/tsconfig.json rename to src/buildtools/bin/templates/tab/tsconfig.json diff --git a/src/buildtools/build.js b/src/buildtools/build.js new file mode 100644 index 0000000000..34688f50fa --- /dev/null +++ b/src/buildtools/build.js @@ -0,0 +1,25 @@ +/** + * Script for building buildtools + */ + +// @ts-check +import { Command } from '@commander-js/extra-typings' +import { build } from 'esbuild' + +const command = new Command() + .option('--dev', 'If specified, the built output is not minified for easier debugging') + .action(async ({ dev }) => { + await build({ + entryPoints: ['./src/commands/index.ts'], + bundle: true, + format: 'esm', + minify: !dev, + outfile: './bin/index.js', + packages: 'external', + platform: 'node', + target: 'node20', + tsconfig: './tsconfig.json' + }) + }) + +await command.parseAsync() diff --git a/src/buildtools/package.json b/src/buildtools/package.json index da9d3fb02b..1005dc0f42 100644 --- a/src/buildtools/package.json +++ b/src/buildtools/package.json @@ -1,5 +1,6 @@ { - "name": "@sourceacademy/module-buildtools", + "name": "@sourceacademy/modules-buildtools", + "private": true, "description": "Tools for building Source Academy bundles and tabs", "version": "1.0.0", "devDependencies": { @@ -8,8 +9,11 @@ "@types/jest": "^29.0.0", "@types/node": "^20.12.12" }, + "exports": { + "*": null + }, "bin": { - "buildtools": "./bin.js" + "buildtools": "./bin/index.js" }, "type": "module", "dependencies": { @@ -19,14 +23,15 @@ "commander": "^13.0.0", "console-table-printer": "^2.11.1", "esbuild": "^0.25.0", + "eslint": "^9.21.0", "jest": "^29.7.0", "jsonschema": "^1.5.0", "ts-jest": "^29.1.2", - "typedoc": "^0.25.12", + "typedoc": "^0.28.4", "typescript": "^5.8.2" }, "scripts": { - "build": "esbuild ./src/commands/index.ts --bundle --format=esm --packages=external --target=node20 --outfile=./bin.js --tsconfig=./tsconfig.json", + "build": "node ./build.js", "tsc": "tsc --project ./tsconfig.json" } } diff --git a/src/buildtools/src/build/docs/docsUtils.ts b/src/buildtools/src/build/docs/docsUtils.ts index e6f6b7871f..e78d821331 100644 --- a/src/buildtools/src/build/docs/docsUtils.ts +++ b/src/buildtools/src/build/docs/docsUtils.ts @@ -1,17 +1,22 @@ import * as td from 'typedoc'; -import type { ResolvedBundle } from '../modules/manifest'; +import type { ResolvedBundle } from '../manifest'; import { getGitRoot } from '../../utils'; import pathlib from 'path'; +const commonTypedocOptions: td.Configuration.TypeDocOptions = { + categorizeByGroup: true, + disableSources: true, + excludeInternal: true, + logLevel: 'Error', + skipErrorChecking: true, +} + export async function initTypedocForSingleBundle(bundle: ResolvedBundle) { const app = await td.Application.bootstrap({ + ...commonTypedocOptions, name: bundle.name, - categorizeByGroup: true, entryPoints: [bundle.entryPoint], - excludeInternal: true, - logLevel: 'Error', tsconfig: `${bundle.directory}/tsconfig.json`, - skipErrorChecking: true, }); const reflection = await app.convert() @@ -25,22 +30,17 @@ export async function initTypedocForSingleBundle(bundle: ResolvedBundle) { export async function initTypedoc(manifest: Record) { const entryPoints = Object.values(manifest).map(({ entryPoint }) => entryPoint) const gitRoot = await getGitRoot() - const readme = pathlib.resolve(gitRoot, 'src', 'buildtools', 'src', 'build', 'docs', 'docsreadme.md') const tsconfigPath = pathlib.resolve(gitRoot, 'src', 'bundles', 'tsconfig.json') const app = await td.Application.bootstrap({ + ...commonTypedocOptions, alwaysCreateEntryPointModule: true, - categorizeByGroup: true, entryPoints, - excludeInternal: true, - logLevel: 'Error', name: 'Source Academy Modules', - readme, - skipErrorChecking: true, - tsconfig: tsconfigPath + readme: `${import.meta.dirname}/docsreadme.md`, + tsconfig: tsconfigPath, }); - const project = await app.convert(); if (!project) { throw new Error('Failed to initialize typedoc - Make sure to check that the source files have no compilation errors!'); diff --git a/src/buildtools/src/build/docs/index.ts b/src/buildtools/src/build/docs/index.ts index 725af2a824..e82560bb2a 100644 --- a/src/buildtools/src/build/docs/index.ts +++ b/src/buildtools/src/build/docs/index.ts @@ -1,5 +1,5 @@ import type { ProjectReflection } from 'typedoc' -import { resolveAllBundles } from '../modules/manifest' +import type { ResolvedBundle } from '../manifest' import { initTypedoc } from './docsUtils' import { buildJson } from './json' import chalk from 'chalk' @@ -8,15 +8,21 @@ import chalk from 'chalk' * A more efficient version of documentation building to avoid * having to instantiate typedoc multiple times */ -export async function buildDocs(bundlesDir: string, outDir: string) { - const manifest = await resolveAllBundles(bundlesDir) +export async function buildDocs(manifest: Record, outDir: string) { const [project, app] = await initTypedoc(manifest) + const validModules = new Set(Object.keys(manifest)) + + const unknownChildren = project.children.filter(({ name }) => !validModules.has(name)) + if (unknownChildren.length > 0) { + console.warn(`${chalk.yellow('[warning]')} Unknown modules present in html output`) + } + const jsonPromises = Object.keys(manifest) .map(async bundleName => { const reflection = project.getChildByName(bundleName) if (!reflection) { - console.warn(`${chalk.yellow('[warning]:')} Did not find documentation for ${bundleName}. Did you include a @module tag?`) + console.warn(`${chalk.yellow('[warning]')} Did not find documentation for ${bundleName}. Did you forget a @module tag?`) return; } diff --git a/src/buildtools/src/build/modules/manifest.ts b/src/buildtools/src/build/manifest.ts similarity index 53% rename from src/buildtools/src/build/modules/manifest.ts rename to src/buildtools/src/build/manifest.ts index 69c6987aed..a23dd0ab65 100644 --- a/src/buildtools/src/build/modules/manifest.ts +++ b/src/buildtools/src/build/manifest.ts @@ -1,7 +1,8 @@ import fs from 'fs/promises'; import pathlib from 'path'; import { validate } from 'jsonschema'; -import manifestSchema from './manifest.schema.json' with { type: 'json' }; +import manifestSchema from './modules/manifest.schema.json' with { type: 'json' }; +import { getBundleEntryPoint } from './modules/bundle'; export interface BundleManifest { version?: string @@ -21,7 +22,7 @@ export async function getBundleManifest(manifestFile: string, tabCheck?: boolean try { rawManifest = await fs.readFile(manifestFile, 'utf-8') } catch (error) { - if (error.message === 'ENOENT') { + if (error.code === 'ENOENT') { return undefined } throw error @@ -46,6 +47,9 @@ export async function getBundleManifest(manifestFile: string, tabCheck?: boolean return data; } +/** + * Get all bundle manifests + */ export async function getBundleManifests(bundlesDir: string): Promise { const subdirs = await fs.readdir(bundlesDir) const manifests = await Promise.all(subdirs.map(async fileName => { @@ -70,4 +74,60 @@ export async function getBundleManifests(bundlesDir: string): Promise); -} \ No newline at end of file +} + +export interface ResolvedBundle { + name: string + manifest: BundleManifest + entryPoint: string + directory: string +} + +export async function resolveSingleBundle(bundleDir: string): Promise { + const fullyResolved = pathlib.resolve(bundleDir) + + const stats = await fs.stat(fullyResolved) + if (!stats.isDirectory()) return undefined + + const manifest = await getBundleManifest(`${fullyResolved}/manifest.json`) + if (!manifest) return undefined + + const bundleName = pathlib.basename(fullyResolved) + const entryPoint = await getBundleEntryPoint(fullyResolved) + + return { + name: bundleName, + manifest, + entryPoint, + directory: fullyResolved + } +} + +export async function resolveAllBundles(bundlesDir: string) { + const subdirs = await fs.readdir(bundlesDir) + const manifests = await Promise.all(subdirs.map(subdir => { + const fullPath = pathlib.join(bundlesDir, subdir) + return resolveSingleBundle(fullPath) + })) + + return manifests.reduce((res, entry) => { + if (entry === undefined) return res + + return { + ...res, + [entry.name]: entry + } + }, {} as Record) +} +export async function resolvePaths(...paths: string[]) { + for (const path of paths) { + try { + await fs.access(path, fs.constants.R_OK); + return path + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } + } + + return undefined; +} diff --git a/src/buildtools/src/build/modules/bundle.ts b/src/buildtools/src/build/modules/bundle.ts index 654226c3e5..e660fb1c63 100644 --- a/src/buildtools/src/build/modules/bundle.ts +++ b/src/buildtools/src/build/modules/bundle.ts @@ -1,8 +1,7 @@ import fs from 'fs/promises'; import { build as esbuild } from 'esbuild'; import { commonEsbuildOptions, outputBundleOrTab } from './commons'; -import pathlib from 'path'; -import { getBundleManifest } from './manifest'; +import type { ResolvedBundle } from '../manifest'; export async function getBundleEntryPoint(bundleDir: string) { let bundlePath = `${bundleDir}/src/index.ts` @@ -18,24 +17,15 @@ export async function getBundleEntryPoint(bundleDir: string) { } /** - * Build a bundle at the given directory + * Build the given resolved bundle */ -export async function buildBundle(bundleDir: string, outDir: string) { - const fullyResolved = pathlib.resolve(bundleDir) - const manifest = await getBundleManifest(fullyResolved) - if (manifest === undefined) { - throw new Error(`Could not find a bundle at ${fullyResolved}`) - } - - const entryPoint = await getBundleEntryPoint(fullyResolved) - const bundleName = pathlib.basename(fullyResolved) - +export async function buildBundle(bundle: ResolvedBundle, outDir: string) { const { outputFiles: [result] } = await esbuild({ ...commonEsbuildOptions, - entryPoints: [entryPoint], - tsconfig: `${fullyResolved}/tsconfig.json`, - outfile: `/bundle/${bundleName}`, + entryPoints: [bundle.entryPoint], + tsconfig: `${bundle.directory}/tsconfig.json`, + outfile: `/bundle/${bundle.name}`, }); - await outputBundleOrTab(result, bundleName, 'bundle', outDir); + await outputBundleOrTab(result, bundle.name, 'bundle', outDir); } diff --git a/src/buildtools/src/build/modules/commons.ts b/src/buildtools/src/build/modules/commons.ts index b8c5443259..2ad41fcf7e 100644 --- a/src/buildtools/src/build/modules/commons.ts +++ b/src/buildtools/src/build/modules/commons.ts @@ -11,8 +11,9 @@ export const commonEsbuildOptions: ESBuildOptions = { process: JSON.stringify({ env: { NODE_ENV: 'production' - } - }) + }, + }), + global: 'globalThis' }, external: ['js-slang*'], globalName: 'module', diff --git a/src/buildtools/src/build/modules/index.ts b/src/buildtools/src/build/modules/index.ts index 99a181c3c7..475182586a 100644 --- a/src/buildtools/src/build/modules/index.ts +++ b/src/buildtools/src/build/modules/index.ts @@ -1,21 +1,27 @@ import fs from 'fs/promises'; -import pathlib from 'path'; import { buildBundle } from './bundle'; -import { getBundleManifests } from './manifest'; +import type { ResolvedBundle } from '../manifest'; + +/** + * Writes the module manifest to the output directory + */ +export async function writeManifest(manifests: Record, outDir: string) { + const toWrite = Object.entries(manifests).reduce((res, [key, { manifest }]) => ({ + ...res, + [key]: manifest + }), {}) + await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(toWrite, null, 2)) +} /** * Search the given directory for valid bundles, then build and write - * them to the given output directory, along with the modules manifest + * them to the given output directory, */ -export async function buildBundles(directory: string, outDir: string) { - const manifests = await getBundleManifests(directory) - await Promise.all(Object.keys(manifests).map(async name => { - const fullPath = pathlib.join(directory, name) - await buildBundle(fullPath, outDir); +export async function buildBundles(manifests: Record, outDir: string) { + await Promise.all(Object.values(manifests).map(async bundle => { + await buildBundle(bundle, outDir); })) - - await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(manifests)); } export { buildBundle }; -export { buildTab } from './tab'; \ No newline at end of file +export { buildTab, buildTabs } from './tab'; \ No newline at end of file diff --git a/src/buildtools/src/build/modules/manifest.schema.json b/src/buildtools/src/build/modules/manifest.schema.json index c633dca965..8a3394d479 100644 --- a/src/buildtools/src/build/modules/manifest.schema.json +++ b/src/buildtools/src/build/modules/manifest.schema.json @@ -14,10 +14,10 @@ "description": "Version of your bundle" }, "requires": { - "enum": [1, 2, 3, 4] + "enum": [1, 2, 3, 4], + "description": "Minimum Source version required to run this bundle" }, "additionalProperties": false - }, - "required": ["name"] + } } } \ No newline at end of file diff --git a/src/buildtools/src/build/modules/tab.ts b/src/buildtools/src/build/modules/tab.ts index 3d88dab1e8..3859073b93 100644 --- a/src/buildtools/src/build/modules/tab.ts +++ b/src/buildtools/src/build/modules/tab.ts @@ -2,6 +2,7 @@ import fs from 'fs/promises'; import pathlib from 'path' import { build as esbuild, type Plugin as ESBuildPlugin } from 'esbuild'; import { commonEsbuildOptions, outputBundleOrTab } from './commons'; +import { resolvePaths, type ResolvedBundle } from '../manifest'; const tabContextPlugin: ESBuildPlugin = { name: 'Tab Context', @@ -26,17 +27,40 @@ export async function getTabEntryPoint(tabDir: string) { } } +interface ResolvedTab { + directory: string + entryPoint: string + name: string +} + +export async function resolveSingleTab(tabDir: string): Promise { + const fullyResolved = pathlib.resolve(tabDir) + + const tabPath = await resolvePaths( + `${fullyResolved}/src/index.tsx`, + `${fullyResolved}/index.tsx` + ) + + if (tabPath === undefined) { + throw new Error(`No tab found at ${fullyResolved}!`) + } + + return { + directory: fullyResolved, + entryPoint: tabPath, + name: pathlib.basename(fullyResolved) + } +} + /** * Build a tab at the given directory */ export async function buildTab(tabDir: string, outDir: string) { - let tabPath = await getTabEntryPoint(tabDir) - const fullyResolved = pathlib.resolve(tabDir) - const tabName = pathlib.basename(fullyResolved) + const tab = await resolveSingleTab(tabDir) const { outputFiles: [result]} = await esbuild({ ...commonEsbuildOptions, - entryPoints: [tabPath], + entryPoints: [tab.entryPoint], external: [ ...commonEsbuildOptions.external, 'react', @@ -45,9 +69,18 @@ export async function buildTab(tabDir: string, outDir: string) { 'react/jsx-runtime', '@blueprintjs/*' ], - outfile:`/tabs/${tabName}`, - tsconfig: `${fullyResolved}/tsconfig.json`, - plugins: [tabContextPlugin] + tsconfig: `${tab.directory}/tsconfig.json`, + plugins: [tabContextPlugin], }); - await outputBundleOrTab(result, tabName, 'tab', outDir); + await outputBundleOrTab(result, tab.name, 'tab', outDir); +} + +export async function buildTabs(manifest: Record, outDir: string) { + await Promise.all(Object.values(manifest).map(async ({ manifest: { tabs } }) => { + if (tabs) { + await Promise.all(tabs.map(async tabName => { + await buildTab(tabName, outDir) + })) + } + })) } diff --git a/src/buildtools/src/templates/__tests__/template.test.ts b/src/buildtools/src/commands/__tests__/template.test.ts similarity index 96% rename from src/buildtools/src/templates/__tests__/template.test.ts rename to src/buildtools/src/commands/__tests__/template.test.ts index 9b2e5adec7..7907424e61 100644 --- a/src/buildtools/src/templates/__tests__/template.test.ts +++ b/src/buildtools/src/commands/__tests__/template.test.ts @@ -1,8 +1,8 @@ import fs from 'fs/promises'; import type { MockedFunction } from 'jest-mock'; -import getTemplateCommand from '..'; -import { askQuestion } from '../print'; +import getTemplateCommand from '../template'; +import { askQuestion } from '../../templates/print'; jest.mock('../print', () => ({ ...jest.requireActual('../print'), @@ -140,7 +140,6 @@ describe('Test adding new tab', () => { ] ); - const oldManifest = await retrieveManifest('modules.json'); const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; expect(manifestPath) .toEqual('modules.json'); diff --git a/src/buildtools/src/commands/__tests__/testing.ts b/src/buildtools/src/commands/__tests__/testing.test.ts similarity index 92% rename from src/buildtools/src/commands/__tests__/testing.ts rename to src/buildtools/src/commands/__tests__/testing.test.ts index 86fb1c5f71..5c7e59efd6 100644 --- a/src/buildtools/src/commands/__tests__/testing.ts +++ b/src/buildtools/src/commands/__tests__/testing.test.ts @@ -10,7 +10,7 @@ const runCommand = (...args: string[]) => getTestCommand() const mockRunJest = runner.runJest as MockedFunction; test('Check that the test command properly passes options to jest', async () => { - await runCommand('-u', '-w', '--srcDir', 'gg', './src/folder'); + await runCommand('-u', '-w', './src/folder'); const [call] = mockRunJest.mock.calls; expect(call[0]) diff --git a/src/buildtools/src/commands/build.ts b/src/buildtools/src/commands/build.ts index 09082bf137..23c7ce312b 100644 --- a/src/buildtools/src/commands/build.ts +++ b/src/buildtools/src/commands/build.ts @@ -1,35 +1,68 @@ import fs from 'fs/promises' -import pathlib from 'path' import { Command } from "@commander-js/extra-typings"; -import { buildBundle } from "../build/modules/bundle"; -import { getBundleManifests } from "../build/modules/manifest"; -import { buildTab } from '../build/modules/tab'; -import { getGitRoot } from '../utils'; +import { buildBundle, buildBundles, buildTab, buildTabs, writeManifest } from "../build/modules"; +import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; +import { initTypedocForSingleBundle } from '../build/docs/docsUtils'; +import { buildDocs, buildJson } from '../build/docs'; +import { getBundlesDir, getOutDir } from '../utils'; -const gitRoot = await getGitRoot() -const bundlesDir = pathlib.join(gitRoot, 'src', 'bundles') -const outDir = pathlib.join(gitRoot, 'build') +const outDir = await getOutDir() +const bundlesDir = await getBundlesDir() export const getBuildBundleCommand = () => new Command('bundle') .argument('', 'Directory in which the bundle\'s source files are located') .action(async bundleDir => { - await buildBundle(bundleDir, outDir) + const bundle = await resolveSingleBundle(bundleDir) + if (!bundle) { + throw new Error(`No bundle found at ${bundleDir}!`) + } + await buildBundle(bundle, outDir) }) export const getBuildTabCommand = () => new Command('tab') .argument('', 'Directory in which the tab\'s source files are located') - .action(async tabDir => { - await buildTab(tabDir, outDir) + .action(tabDir => buildTab(tabDir, outDir)) + +export const getBuildJsonCommand = () => new Command('json') + .argument('', 'Directory in which the bundle\'s source files are located') + .action(async bundleDir => { + const bundle = await resolveSingleBundle(bundleDir) + const reflection = await initTypedocForSingleBundle(bundle) + await fs.mkdir(`${outDir}/json`, { recursive: true }) + await buildJson(bundle.name, reflection, outDir) + }) + +export const getBuildDocsCommand = () => new Command('docs') + .action(async () => { + const manifest = await resolveAllBundles(bundlesDir) + await buildDocs(manifest, outDir) }) export const getBuildManifestCommand = () => new Command('manifest') .action(async () => { - const manifest = await getBundleManifests(bundlesDir) + const manifest = await resolveAllBundles(bundlesDir) await fs.mkdir(outDir, { recursive: true }) - await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(manifest, null, 2)) + await writeManifest(manifest, outDir) + }) + +export const getBuildAllCommand = () => new Command('all') +.description('Build all bundles, tabs and documentation') + .action(async () => { + const manifest = await resolveAllBundles(bundlesDir) + await fs.mkdir(outDir, { recursive: true }) + + await Promise.all([ + writeManifest(manifest, outDir), + buildDocs(manifest, outDir), + buildBundles(manifest, outDir), + buildTabs(manifest, outDir) + ]) }) export const getBuildCommand = () => new Command('build') + .addCommand(getBuildAllCommand()) .addCommand(getBuildBundleCommand()) .addCommand(getBuildTabCommand()) + .addCommand(getBuildJsonCommand()) + .addCommand(getBuildDocsCommand()) .addCommand(getBuildManifestCommand()) diff --git a/src/buildtools/src/commands/list.ts b/src/buildtools/src/commands/list.ts new file mode 100644 index 0000000000..f24a7ebf5e --- /dev/null +++ b/src/buildtools/src/commands/list.ts @@ -0,0 +1,28 @@ +import { Command } from "@commander-js/extra-typings"; +import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; +import { getBundlesDir } from "../utils"; +import chalk from "chalk"; + +export const getListBundlesCommand = () => new Command('bundle') + .description('Lists all the bundles present or the information for a specific bundle in a given directory') + .argument('[directory]') + .action(async directory => { + if (directory === undefined) { + const bundlesDir = await getBundlesDir() + const manifest = await resolveAllBundles(bundlesDir) + const bundleNames = Object.keys(manifest) + + if (bundleNames.length > 0 ) { + const bundlesStr = bundleNames.map((each, i) => `${i+1}. ${each}`).join('\n') + console.log(`${chalk.magentaBright(`Detected ${bundleNames.length} bundles in ${directory}:`)}\n${bundlesStr}`) + } else { + console.log(chalk.redBright(`No bundles in ${directory}`)) + } + } else { + const manifest = await resolveSingleBundle(directory) + console.log(chalk.magentaBright(`Bundle '${manifest.name}' found in ${directory}`)) + } + }) + +export const getListCommand = () => new Command('list') + .addCommand(getListBundlesCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/main.ts b/src/buildtools/src/commands/main.ts index f6fb94b0a2..d554ec2439 100644 --- a/src/buildtools/src/commands/main.ts +++ b/src/buildtools/src/commands/main.ts @@ -3,10 +3,12 @@ import { getBuildCommand } from "./build"; import getTemplateCommand from "./template"; import getTestCommand from "./testing"; import { getLintCommand, getTscCommand } from "./prebuild"; +import { getListCommand } from "./list"; export const getMainCommand = () => new Command() .addCommand(getBuildCommand()) + .addCommand(getLintCommand()) + .addCommand(getListCommand()) .addCommand(getTemplateCommand()) .addCommand(getTestCommand()) - .addCommand(getTscCommand()) - .addCommand(getLintCommand()) \ No newline at end of file + .addCommand(getTscCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/template.ts b/src/buildtools/src/commands/template.ts index 48205378c7..77709b4c07 100644 --- a/src/buildtools/src/commands/template.ts +++ b/src/buildtools/src/commands/template.ts @@ -4,8 +4,7 @@ import { Command } from '@commander-js/extra-typings'; import { addNew as addNewModule } from '../templates/bundle'; import { error as _error, askQuestion, getRl, info, warn } from '../templates/print'; import { addNew as addNewTab } from '../templates/tab'; -import { getGitRoot } from '../utils'; -import path from 'path'; +import { getBundlesDir, getTabsDir } from '../utils'; async function askMode(rl: Interface) { while (true) { @@ -22,9 +21,10 @@ export default function getTemplateCommand() { return new Command('template') .description('Interactively create a new module or tab') .action(async () => { - const gitRoot = await getGitRoot() - const bundlesDir = path.join(gitRoot, 'src', 'bundles') - const tabsDir = path.join(gitRoot, 'src', 'tabs') + const [bundlesDir, tabsDir] = await Promise.all([ + getBundlesDir(), + getTabsDir() + ]) const rl = getRl(); try { diff --git a/src/buildtools/src/commands/testing.ts b/src/buildtools/src/commands/testing.ts index a39e257dec..b06672f031 100644 --- a/src/buildtools/src/commands/testing.ts +++ b/src/buildtools/src/commands/testing.ts @@ -1,4 +1,3 @@ -import pathlib from 'path'; import { Command } from '@commander-js/extra-typings'; import { runJest } from '../testing/runner'; @@ -8,11 +7,10 @@ const getTestCommand = () => new Command('test') .allowUnknownOption() .argument('[patterns...]') .action((patterns, args: Record) => { - const jestArgs = [ - ...Object.entries(args).flat(), - ...patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)) - ] - return runJest(jestArgs); + return runJest( + Object.entries(args).flat(), + patterns + ); }); export default getTestCommand; diff --git a/src/buildtools/src/templates/bundle.ts b/src/buildtools/src/templates/bundle.ts index 3b9d2b08de..0cf783020e 100644 --- a/src/buildtools/src/templates/bundle.ts +++ b/src/buildtools/src/templates/bundle.ts @@ -2,7 +2,7 @@ import fs from 'fs/promises'; import type { Interface } from 'readline/promises'; import { askQuestion, success, warn } from './print'; import { check, isSnakeCase } from './utilities'; -import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/modules/manifest'; +import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; async function askModuleName(manifest: ModulesManifest, rl: Interface) { while (true) { @@ -21,14 +21,15 @@ export async function addNew(bundlesDir: string, rl: Interface) { const manifest = await getBundleManifests(bundlesDir) const moduleName = await askModuleName(manifest, rl); const bundleDestination = `${bundlesDir}/${moduleName}`; - await fs.cp('./src/templates/templates/bundle', bundleDestination) + + await fs.cp(`${import.meta.dirname}/templates/bundle`, bundleDestination) const packageJson = { name: `@sourceacademy/bundle-${moduleName}`, private: true, version: "1.0.0", devDependencies: { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, type: "module", @@ -44,7 +45,6 @@ export async function addNew(bundlesDir: string, rl: Interface) { } const bundleManifest: BundleManifest = { - name: moduleName, tabs: [] } diff --git a/src/buildtools/src/templates/tab.ts b/src/buildtools/src/templates/tab.ts index 17f2592097..6624c3117a 100644 --- a/src/buildtools/src/templates/tab.ts +++ b/src/buildtools/src/templates/tab.ts @@ -1,8 +1,9 @@ +import pathlib from 'path' import fs from 'fs/promises'; import type { Interface } from 'readline/promises'; import { askQuestion, success, warn } from './print'; import { check, isPascalCase } from './utilities'; -import { getBundleManifest, getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/modules/manifest'; +import { getBundleManifest, getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; async function askModuleName(manifest: ModulesManifest, rl: Interface) { while (true) { @@ -46,7 +47,7 @@ export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) private: true, version: "1.0.0", devDependencies: { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1", "typescript": "^5.8.2" }, @@ -58,7 +59,7 @@ export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) } } - await fs.cp('buildtools/src/templates/templates/tabs', tabDestination) + await fs.cp(`${import.meta.dirname}/templates/tabs`, tabDestination) await Promise.all([ fs.writeFile(`${tabDestination}/package.json`, JSON.stringify(packageJson, null, 2)), getBundleManifest(`${bundlesDir}/${moduleName}`) diff --git a/src/buildtools/src/templates/utilities.ts b/src/buildtools/src/templates/utilities.ts index 37d2124b67..274ddc1ea7 100644 --- a/src/buildtools/src/templates/utilities.ts +++ b/src/buildtools/src/templates/utilities.ts @@ -1,7 +1,7 @@ // Snake case regex has been changed from `/\b[a-z]+(?:_[a-z]+)*\b/u` to `/\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u` // to be consistent with the naming of the `arcade_2d` and `physics_2d` modules. -import type { ModulesManifest } from "../build/modules/manifest"; +import type { ModulesManifest } from "../build/manifest"; // This change should not affect other modules, since the set of possible names is only expanded. const snakeCaseRegex = /\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u; diff --git a/src/buildtools/src/testing/runner.ts b/src/buildtools/src/testing/runner.ts new file mode 100644 index 0000000000..9736bc204b --- /dev/null +++ b/src/buildtools/src/testing/runner.ts @@ -0,0 +1,10 @@ +import pathlib from 'path' +import jest from 'jest'; + +export function runJest(jestArgs: string[], patterns: string[]) { + const filePatterns = patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)) + return jest.run([ + ...jestArgs, + ...filePatterns + ]); +} diff --git a/src/buildtools/src/utils.ts b/src/buildtools/src/utils.ts new file mode 100644 index 0000000000..c82e6b32f2 --- /dev/null +++ b/src/buildtools/src/utils.ts @@ -0,0 +1,43 @@ +import { execFile } from "child_process" +import _ from "lodash" +import pathlib from "path" + +function rawGetGitRoot() { + return new Promise((resolve, reject) => { + execFile('git', ['rev-parse', '--show-toplevel'], (err, stdout, stderr) => { + const possibleError = err || stderr + if (possibleError) { + reject(possibleError) + } + + resolve(stdout.trim()) + }) + }) +} + +/** + * Get the path to the root of the git repository + */ +export const getGitRoot = _.memoize(rawGetGitRoot) +export const getBundlesDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'bundles')) +export const getTabsDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'tabs')) +export const getOutDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'build')) + +export type AwaitedReturn Promise> = Awaited> + +export type Severity = 'success' | 'warn' | 'error' + +export function findSeverity(items: T[], mapper: (each: T) => Severity): Severity { + let output: Severity = 'success' + for (const item of items) { + const severity = mapper(item) + if (severity === 'error') return 'error' + if (severity === 'warn') { + output = 'warn' + } + } + + return output; +} + +export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFixed(2); \ No newline at end of file diff --git a/src/buildtools/tsconfig.json b/src/buildtools/tsconfig.json index 7929b73931..1e9c99fbe6 100644 --- a/src/buildtools/tsconfig.json +++ b/src/buildtools/tsconfig.json @@ -1,3 +1,4 @@ +// buildtools tsconfig { "compilerOptions": { "esModuleInterop": true, @@ -9,7 +10,7 @@ }, "include": ["./src", "jest.setup.ts"], "exclude": [ - "./src/templates/templates/**", + "./bin", "./src/build/docs/__tests__/test_mocks" ] } diff --git a/src/buildtools/workspacer.py b/src/buildtools/workspacer.py new file mode 100644 index 0000000000..9e53c95ae4 --- /dev/null +++ b/src/buildtools/workspacer.py @@ -0,0 +1,97 @@ +""" +This file contains a bunch of python scripts that can be used to update all +the tsconfigs or package.jsons of all tabs and bundles at once +(say if a new script needed to be added) +""" + +import os +import json + +def get_tabs(): + for name in os.listdir('./src/tabs'): + if not os.path.isdir(f'./src/tabs/{name}'): + continue + yield name + +def get_bundles(): + for name in os.listdir('./src/bundles'): + if name == '__mocks__': + continue + + if not os.path.isdir(f'./src/bundles/{name}'): + continue + yield name + +def update_tab_packages(): + for name in get_tabs(): + with open(f'./src/tabs/{name}/package.json') as file: + original = json.load(file) + + if '@sourceacademy/module-buildtools' in original['devDependencies']: + del original['devDependencies']['@sourceacademy/module-buildtools'] + original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" + + with open(f'./src/tabs/{name}/package.json', 'w') as file: + json.dump(original, file, indent=2) + +def create_bundle_manifest(): + with open('modules.json') as file: + current_manifest = json.load(file) + + for moduleName in current_manifest.keys(): + with open(f'./src/bundles/{moduleName}/manifest.json', 'w') as manifest_file: + manifest = {} + + if 'tabs' in current_manifest[moduleName]: + manifest['tabs'] = current_manifest[moduleName]['tabs'] + + json.dump(manifest, manifest_file, indent=2) + +def update_tab_tsconfigs(): + for name in get_tabs(): + tsconfigPath = f'./src/tabs/{name}/tsconfig.json' + with open(tsconfigPath) as file: + original = json.load(file) + + with open(f'./src/tabs/{name}/tsconfig.json', 'w') as file: + compilerOptions = original.setdefault('compilerOptions', dict()) + compilerOptions['outDir'] = './dist' + json.dump(original, file, indent=2) + +def update_bundle_tsconfigs(): + for name in os.listdir('./src/bundles'): + if not os.path.isdir(f'./src/bundles/{name}'): + continue + + with open(f'./src/bundles/{name}/tsconfig.json') as file: + original = json.load(file) + + if 'compilerOptions' in original: + original['compilerOptions']['outDir'] = './dist' + else: + original['compilerOptions'] = { + "outDir": './dist' + } + + with open(f'./src/bundles/{name}/tsconfig.json', 'w') as file: + json.dump(original, file, indent=2) + +def update_bundle_packages(): + for name in get_bundles(): + with open(f'./src/bundles/{name}/package.json') as file: + original = json.load(file) + + if '@sourceacademy/module-buildtools' in original['devDependencies']: + del original['devDependencies']['@sourceacademy/module-buildtools'] + original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" + + with open(f'./src/bundles/{name}/package.json', 'w') as file: + json.dump(original, file, indent=2) + +if __name__ == '__main__': + # create_bundle_manifest() + update_bundle_packages() + # update_bundle_tsconfigs() + # update_tab_packages() + # update_tab_tsconfigs() + \ No newline at end of file From 36958df457760cab1a75f0e539a8f1cc08e1da66 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 19 May 2025 16:25:58 -0400 Subject: [PATCH 014/112] Add new commands for tabs and potentially jsdoc for bundles --- src/lintplugin/package.json | 11 ++- src/lintplugin/src/configs.ts | 78 +++++++++++++++++++ src/lintplugin/src/index.ts | 13 +++- .../src/rules/__tests__/modulestag.test.ts | 54 +++++++++++++ .../src/rules/__tests__/typeimports.test.ts | 20 ++--- src/lintplugin/src/rules/moduleTag.ts | 56 +++++++++++++ src/lintplugin/src/rules/tabType.ts | 78 +++++++++++++++++++ src/lintplugin/src/rules/typeimports.ts | 19 +++-- src/lintplugin/tsconfig.json | 7 +- src/lintplugin/tsconfig.prod.json | 10 +++ 10 files changed, 317 insertions(+), 29 deletions(-) create mode 100644 src/lintplugin/src/configs.ts create mode 100644 src/lintplugin/src/rules/__tests__/modulestag.test.ts create mode 100644 src/lintplugin/src/rules/moduleTag.ts create mode 100644 src/lintplugin/src/rules/tabType.ts create mode 100644 src/lintplugin/tsconfig.prod.json diff --git a/src/lintplugin/package.json b/src/lintplugin/package.json index f2c1a14d7a..4aa4c420f9 100644 --- a/src/lintplugin/package.json +++ b/src/lintplugin/package.json @@ -1,19 +1,28 @@ { "name": "@sourceacademy/lint-plugin", "version": "1.0.0", + "type": "module", "exports": { ".": "./dist/index.js" }, "dependencies": { + "@es-joy/jsdoccomment": "^0.50.1", + "@typescript-eslint/utils": "^8.32.1", + "eslint-plugin-import": "^2.31.0", + "typescript-eslint": "^8.24.1" + }, + "peerDependencies": { "eslint": "^9.21.0" }, "devDependencies": { + "@typescript-eslint/rule-tester": "^8.32.1", "jest": "^29.7.0", "ts-jest": "^29.1.2", "typescript": "^5.8.2" }, "scripts": { - "build": "tsc --project ./tsconfig.json", + "build": "tsc --project ./tsconfig.prod.json", + "tsc": "tsc --project ./tsconfig.json", "test": "jest" }, "jest": { diff --git a/src/lintplugin/src/configs.ts b/src/lintplugin/src/configs.ts new file mode 100644 index 0000000000..95d7bedc49 --- /dev/null +++ b/src/lintplugin/src/configs.ts @@ -0,0 +1,78 @@ +import stylePlugin from '@stylistic/eslint-plugin'; +import type { Linter } from 'eslint' +import * as importPlugin from 'eslint-plugin-import'; +import tseslint from 'typescript-eslint'; +import globals from 'globals' +import saLintPlugin from '.' + +const todoTreeKeywordsWarning = ['TODO', 'TODOS', 'TODO WIP', 'FIXME', 'WIP']; +const todoTreeKeywordsAll = [...todoTreeKeywordsWarning, 'NOTE', 'NOTES', 'LIST']; + +export const jsConfig = { + // Global JS Rules + languageOptions: { + globals: { + ...globals.node, + ...globals.es2022 + } + }, + plugins: { + import: importPlugin, + '@stylistic': stylePlugin, + }, + rules: { + 'import/no-duplicates': ['warn', { 'prefer-inline': false }], + 'import/order': [ + 'warn', + { + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + alphabetize: { + order: 'asc', + orderImportKind: 'asc' + }, + } + ], + + '@stylistic/brace-style': ['warn', '1tbs', { allowSingleLine: true }], + '@stylistic/eol-last': 'warn', + '@stylistic/indent': ['warn', 2, { SwitchCase: 1 }], + '@stylistic/no-mixed-spaces-and-tabs': 'warn', + '@stylistic/no-multi-spaces': 'warn', + '@stylistic/no-multiple-empty-lines': ['warn', { max: 1, maxEOF: 0 }], + '@stylistic/no-trailing-spaces': 'warn', + '@stylistic/quotes': ['warn', 'single', { avoidEscape: true }], + '@stylistic/semi': ['warn', 'always'], + '@stylistic/spaced-comment': [ + 'warn', + 'always', + { markers: todoTreeKeywordsAll } + ], + } +} satisfies Linter.Config + +export const tsConfig = { + // Global typescript rules + files: ['**/*.ts*'], + languageOptions: { + // @ts-expect-error typescript eslint's type definitions are different + parser: tseslint.parser + }, + plugins: { + // @ts-expect-error typescript eslint's type definitions are different + '@typescript-eslint': tseslint.plugin, + '@sourceacademy': saLintPlugin + }, + rules: { + 'no-unused-vars': 'off', // Use the typescript eslint rule instead + + '@typescript-eslint/ban-types': 'off', // Was 'error' + '@typescript-eslint/no-duplicate-type-constituents': 'off', // Was 'error' + '@typescript-eslint/no-explicit-any': 'off', // Was 'error' + '@typescript-eslint/no-redundant-type-constituents': 'off', // Was 'error' + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], // Was 'error' + '@typescript-eslint/prefer-ts-expect-error': 'warn', + '@typescript-eslint/sort-type-constituents': 'warn', + + '@sourceacademy/collate-type-imports': 'warn' + } +} satisfies Linter.Config \ No newline at end of file diff --git a/src/lintplugin/src/index.ts b/src/lintplugin/src/index.ts index a93f28d850..37b03e1569 100644 --- a/src/lintplugin/src/index.ts +++ b/src/lintplugin/src/index.ts @@ -1,11 +1,18 @@ import type { ESLint } from 'eslint'; -import collateTypeImports from './rules/typeimports'; +import collateTypeImports from './rules/typeimports.js'; +import { jsConfig, tsConfig } from './configs.js'; +import moduleTagPresent from './rules/moduleTag.js'; const plugin: ESLint.Plugin = { name: 'Source Academy Lint Plugin', rules: { - 'collate-type-imports': collateTypeImports + 'collate-type-imports': collateTypeImports, + 'module-tag-present': moduleTagPresent + }, + configs: { + js: jsConfig, + ts: tsConfig } -}; +} export default plugin; diff --git a/src/lintplugin/src/rules/__tests__/modulestag.test.ts b/src/lintplugin/src/rules/__tests__/modulestag.test.ts new file mode 100644 index 0000000000..4c275ed9c5 --- /dev/null +++ b/src/lintplugin/src/rules/__tests__/modulestag.test.ts @@ -0,0 +1,54 @@ +import { RuleTester } from 'eslint'; +import moduleTagPresent from '../moduleTag' + +describe('Test moduleTagPresent', () => { + const tester = new RuleTester({ + 'languageOptions': { + // eslint-disable-next-line @typescript-eslint/no-require-imports + parser: require('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } + }, + }); + + tester.run( + 'module-tag-present', + moduleTagPresent, + { + valid: [` + /** + * @module hi + */ + `], + invalid: [ + // { + // code: '', + // errors: 1, + // output: '/**\n * @module module_name\n */\n' + // }, { + // code: '// A comment', + // errors: 1, + // output: '/**\n * @module module_name\n */\n// A comment' + // }, { + // code: '/* A comment */', + // errors: 1, + // output: '/**\n * @module module_name\n */\n/* A comment */' + // }, { + // code: '/**\n *\n */', + // errors: 1, + // output: '/**\n * @module module_name\n */\n/**\n *\n */' + // }, + { + code: ` + /** + * @author yo + */ + `, + errors: 1, + output: '/**\n * @author yo\n * @module module_name\n */' + }] + } + ) +}) \ No newline at end of file diff --git a/src/lintplugin/src/rules/__tests__/typeimports.test.ts b/src/lintplugin/src/rules/__tests__/typeimports.test.ts index a17ef96cf7..32c465e99b 100644 --- a/src/lintplugin/src/rules/__tests__/typeimports.test.ts +++ b/src/lintplugin/src/rules/__tests__/typeimports.test.ts @@ -1,18 +1,8 @@ -import { RuleTester } from 'eslint'; +import { RuleTester } from '@typescript-eslint/rule-tester'; import collateTypeImports from '../typeimports'; describe('Test collateTypeImports', () => { - const tester = new RuleTester({ - 'languageOptions': { - // eslint-disable-next-line @typescript-eslint/no-require-imports - parser: require('@typescript-eslint/parser'), - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } - }, - }); - + const tester = new RuleTester(); tester.run( 'collate-type-imports', collateTypeImports, @@ -29,15 +19,15 @@ describe('Test collateTypeImports', () => { ], invalid: [{ code: 'import { type a, type b } from "wherever"', - errors: 1, + errors: [{ messageId: 'msg' }], output: 'import type { a, b } from \'wherever\'' }, { code: 'import { type a } from "wherever"', - errors: 1, + errors: [{ messageId: 'msg' }], output: 'import type { a } from \'wherever\'' }, { code: 'import { type a as b } from "wherever"', - errors: 1, + errors: [{ messageId: 'msg' }], output: "import type { a as b } from 'wherever'" }] } diff --git a/src/lintplugin/src/rules/moduleTag.ts b/src/lintplugin/src/rules/moduleTag.ts new file mode 100644 index 0000000000..46a6771495 --- /dev/null +++ b/src/lintplugin/src/rules/moduleTag.ts @@ -0,0 +1,56 @@ +import { parseComment, getJSDocComment } from '@es-joy/jsdoccomment' +import type { Rule } from "eslint"; + +const moduleTagPresent = { + meta: { + type: 'suggestion', + hasSuggestions: true, + fixable: 'code' + }, + create: ({ sourceCode, report }) => ({ + Program(node) { + let maxLines: number + + if (node.body.length === 0) { + maxLines = 1 + } else { + const firstNode = node.body[0] + maxLines = firstNode.loc.start.line + } + + const comment = getJSDocComment(sourceCode, node as any, { + maxLines, + minLines: 1 + }) + + if (comment === null) { + report({ + node, + message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', + fix: fixer => [ + fixer.insertTextBeforeRange([0, 0], '/**\n * @module module_name\n */\n') + ] + }) + return; + } + + const parsed = parseComment(comment) + const tag = parsed.tags.find(tag => tag.tag === 'module') + + if (!tag) { + console.log(comment.loc.end) + const commentEndLine = (comment as any).loc.end.line + report({ + node, + message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', + fix: fixer => [ + fixer.insertTextAfterRange([commentEndLine, commentEndLine], '\n * @module module_name\n') + ] + }) + return + } + } + }) +} satisfies Rule.RuleModule + +export default moduleTagPresent \ No newline at end of file diff --git a/src/lintplugin/src/rules/tabType.ts b/src/lintplugin/src/rules/tabType.ts new file mode 100644 index 0000000000..7d54d85d54 --- /dev/null +++ b/src/lintplugin/src/rules/tabType.ts @@ -0,0 +1,78 @@ +import { ESLintUtils, type TSESTree as es } from "@typescript-eslint/utils"; + +const createRule = ESLintUtils.RuleCreator.withoutDocs + +const tabType = createRule({ + meta: { + type: 'problem', + schema: [{ + type: 'string', + description: 'Import path' + }], + messages: { + noExport: 'Your tab should export an object using the defineTab helper', + useHelper: 'Use the defineTab helper from {{ source }}' + } + }, + defaultOptions: [ + '@sourceacademy/modules-lib/tabs/utils' + ], + create: (context, options) => ({ + Program(node) { + const exportNode = node.body.find((stmt): stmt is es.ExportDefaultDeclaration => stmt.type === 'ExportDefaultDeclaration') + if (!exportNode) { + context.report({ + messageId: 'noExport', + node + }) + return + } + + const { declaration: exportDeclaration } = exportNode + if (exportDeclaration.type !== 'CallExpression') { + context.report({ + messageId: 'useHelper', + data: { + source: options[0] + }, + node: exportDeclaration + }) + return + } + + const importDeclarations = node.body.filter((stmt): stmt is es.ImportDeclaration => { + return stmt.type === 'ImportDeclaration' && stmt.source.value === options[0] + }) + + if (importDeclarations.length === 0) { + context.report({ + messageId: 'useHelper', + data: { + source: options[0] + }, + node + }) + return + } + + const specifiers = importDeclarations.flatMap(({ specifiers }) => specifiers) + .filter(spec => spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier' && spec.imported.name === 'defineTab') + + const defineNames = specifiers.map(spec => spec.local.name) + + const { callee } = exportDeclaration + if (callee.type !== 'Identifier' || defineNames.includes(callee.name)) { + context.report({ + messageId: 'useHelper', + data: { + source: options[0] + }, + node: callee + }) + return + } + } + }) +}) + +export default tabType \ No newline at end of file diff --git a/src/lintplugin/src/rules/typeimports.ts b/src/lintplugin/src/rules/typeimports.ts index 774e578084..ef98741c59 100644 --- a/src/lintplugin/src/rules/typeimports.ts +++ b/src/lintplugin/src/rules/typeimports.ts @@ -1,5 +1,6 @@ -import type { Rule } from 'eslint'; -import type es from 'estree'; +import { ESLintUtils, type TSESTree as es } from '@typescript-eslint/utils'; + +const createRule = ESLintUtils.RuleCreator.withoutDocs function isImportSpecifier(spec: es.ImportDeclaration['specifiers'][number]): spec is es.ImportSpecifier { return spec.type === 'ImportSpecifier'; @@ -15,22 +16,26 @@ function specToString(spec: es.ImportSpecifier) { return ''; } -const collateTypeImports = { +const collateTypeImports = createRule({ meta: { type: 'suggestion', + messages: { + msg: 'Use a single type specifier', + }, + schema: [], + hasSuggestions: true, fixable: 'code' }, + defaultOptions: [], create: context => ({ ImportDeclaration(node) { - // @ts-expect-error import kind is unknown property if (node.importKind === 'type' || node.specifiers.length === 0) return; - // @ts-expect-error import kind is unknown property if (node.specifiers.some(spec => !isImportSpecifier(spec) || spec.importKind !== 'type')) return; context.report({ node, - message: 'Use a single type specifier', + messageId: 'msg', fix(fixer) { const regularSpecs = (node.specifiers as es.ImportSpecifier[]).map(specToString); @@ -45,6 +50,6 @@ const collateTypeImports = { }); } }) -} satisfies Rule.RuleModule; +}) export default collateTypeImports; diff --git a/src/lintplugin/tsconfig.json b/src/lintplugin/tsconfig.json index c8101e9f35..b887a15b86 100644 --- a/src/lintplugin/tsconfig.json +++ b/src/lintplugin/tsconfig.json @@ -1,11 +1,12 @@ +// lintplugin with tests { "compilerOptions": { "esModuleInterop": true, - "declaration": true, "isolatedModules": true, "module": "ESNext", "moduleResolution": "bundler", - "outDir": "dist" + "noEmit": true, + "target": "ESNext" }, - "include": ["./src"] + "include": ["./src"], } \ No newline at end of file diff --git a/src/lintplugin/tsconfig.prod.json b/src/lintplugin/tsconfig.prod.json new file mode 100644 index 0000000000..f5000f7fc2 --- /dev/null +++ b/src/lintplugin/tsconfig.prod.json @@ -0,0 +1,10 @@ +// lintplugin build config +{ + "compilerOptions": { + "declaration": true, + "noEmit": false, + "outDir": "./dist" + }, + "extends": "./tsconfig.json", + "exclude": ["**/__tests__"] +} \ No newline at end of file From be9c95a4e11d55645b6244dc65eb108c828ac7c1 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 19 May 2025 19:33:44 -0400 Subject: [PATCH 015/112] Add manifests --- src/bundles/ar/package.json | 2 +- src/bundles/arcade_2d/package.json | 2 +- src/bundles/arcade_2d/src/functions.ts | 170 +++++++++++----------- src/bundles/binary_tree/package.json | 2 +- src/bundles/communication/package.json | 2 +- src/bundles/copy_gc/package.json | 2 +- src/bundles/csg/package.json | 2 +- src/bundles/curve/package.json | 10 +- src/bundles/curve/src/functions.ts | 12 ++ src/bundles/curve/tsconfig.json | 3 + src/bundles/game/package.json | 2 +- src/bundles/jest.config.js | 23 +-- src/bundles/mark_sweep/package.json | 2 +- src/bundles/nbody/package.json | 2 +- src/bundles/package.json | 2 +- src/bundles/painter/package.json | 2 +- src/bundles/physics_2d/package.json | 2 +- src/bundles/pix_n_flix/package.json | 2 +- src/bundles/plotly/package.json | 5 +- src/bundles/plotly/src/functions.ts | 9 +- src/bundles/remote_execution/package.json | 2 +- src/bundles/repeat/package.json | 2 +- src/bundles/repl/package.json | 2 +- src/bundles/robot_simulation/package.json | 2 +- src/bundles/rune/package.json | 2 +- src/bundles/rune/src/runes_ops.ts | 44 +++--- src/bundles/rune_in_words/package.json | 2 +- src/bundles/scrabble/package.json | 2 +- src/bundles/sound/package.json | 2 +- src/bundles/sound_matrix/package.json | 2 +- src/bundles/stereo_sound/package.json | 2 +- src/bundles/unittest/package.json | 2 +- src/bundles/unity_academy/package.json | 2 +- src/bundles/wasm/package.json | 2 +- 34 files changed, 170 insertions(+), 158 deletions(-) diff --git a/src/bundles/ar/package.json b/src/bundles/ar/package.json index 33eb7458c5..92c78ac34c 100644 --- a/src/bundles/ar/package.json +++ b/src/bundles/ar/package.json @@ -13,7 +13,7 @@ "uniqid": "^5.4.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "scripts": { diff --git a/src/bundles/arcade_2d/package.json b/src/bundles/arcade_2d/package.json index 9a10008417..cdfb83bb14 100644 --- a/src/bundles/arcade_2d/package.json +++ b/src/bundles/arcade_2d/package.json @@ -6,7 +6,7 @@ "phaser": "^3.54.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", "typescript": "^5.8.2" }, diff --git a/src/bundles/arcade_2d/src/functions.ts b/src/bundles/arcade_2d/src/functions.ts index d2a4caef2d..a02d8d590a 100644 --- a/src/bundles/arcade_2d/src/functions.ts +++ b/src/bundles/arcade_2d/src/functions.ts @@ -89,13 +89,13 @@ export const config = { * ``` * @category GameObject */ -export const create_rectangle: (width: number, height: number) => ShapeGameObject = (width: number, height: number) => { +export function create_rectangle(width: number, height: number): ShapeGameObject { const rectangle = { width, height } as RectangleProps; return new RectangleGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, rectangle); -}; +} /** * Creates a CircleGameObject that takes in circle shape properties. @@ -107,12 +107,12 @@ export const create_rectangle: (width: number, height: number) => ShapeGameObjec * ``` * @category GameObject */ -export const create_circle: (radius: number) => ShapeGameObject = (radius: number) => { +export function create_circle(radius: number): ShapeGameObject { const circle = { radius } as CircleProps; return new CircleGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, circle); -}; +} /** * Creates a TriangleGameObject that takes in an downright isosceles triangle shape properties. @@ -124,7 +124,7 @@ export const create_circle: (radius: number) => ShapeGameObject = (radius: numbe * ``` * @category GameObject */ -export const create_triangle: (width: number, height: number) => ShapeGameObject = (width: number, height: number) => { +export function create_triangle(width: number, height: number): ShapeGameObject { const triangle = { x1: 0, y1: 0, @@ -134,7 +134,7 @@ export const create_triangle: (width: number, height: number) => ShapeGameObject y3: height } as TriangleProps; return new TriangleGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, triangle); -}; +} /** * Creates a GameObject that contains a text reference. @@ -146,12 +146,12 @@ export const create_triangle: (width: number, height: number) => ShapeGameObject * ``` * @category GameObject */ -export const create_text: (text: string) => TextGameObject = (text: string) => { +export function create_text(text: string): TextGameObject { const displayText = { text } as DisplayText; return new TextGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, displayText); -}; +} /** * Creates a GameObject that contains a Sprite image reference. @@ -170,7 +170,7 @@ export const create_text: (text: string) => TextGameObject = (text: string) => { * ``` * @category GameObject */ -export const create_sprite: (image_url: string) => SpriteGameObject = (image_url: string) => { +export function create_sprite(image_url: string): SpriteGameObject { if (image_url === '') { throw new Error('image_url cannot be empty'); } @@ -181,7 +181,7 @@ export const create_sprite: (image_url: string) => SpriteGameObject = (image_url imageUrl: image_url } as Sprite; return new SpriteGameObject(DEFAULT_TRANSFORM_PROPS, DEFAULT_RENDER_PROPS, DEFAULT_INTERACTABLE_PROPS, sprite); -}; +} // ============================================================================= // Manipulation of GameObjects @@ -199,8 +199,7 @@ export const create_sprite: (image_url: string) => SpriteGameObject = (image_url * ``` * @category GameObject */ -export const update_position: (gameObject: GameObject, [x, y]: PositionXY) => GameObject -= (gameObject: GameObject, [x, y]: PositionXY) => { +export function update_position(gameObject: GameObject, [x, y]: PositionXY): GameObject { if (gameObject instanceof GameObject) { gameObject.setTransform({ ...gameObject.getTransform(), @@ -209,7 +208,7 @@ export const update_position: (gameObject: GameObject, [x, y]: PositionXY) => Ga return gameObject; } throw new TypeError('Cannot update position of a non-GameObject'); -}; +} /** * Updates the scale transform of the GameObject. @@ -223,8 +222,7 @@ export const update_position: (gameObject: GameObject, [x, y]: PositionXY) => Ga * ``` * @category GameObject */ -export const update_scale: (gameObject: GameObject, [x, y]: ScaleXY) => GameObject -= (gameObject: GameObject, [x, y]: ScaleXY) => { +export function update_scale(gameObject: GameObject, [x, y]: ScaleXY): GameObject { if (gameObject instanceof GameObject) { gameObject.setTransform({ ...gameObject.getTransform(), @@ -233,7 +231,7 @@ export const update_scale: (gameObject: GameObject, [x, y]: ScaleXY) => GameObje return gameObject; } throw new TypeError('Cannot update scale of a non-GameObject'); -}; +} /** * Updates the rotation transform of the GameObject. @@ -247,8 +245,7 @@ export const update_scale: (gameObject: GameObject, [x, y]: ScaleXY) => GameObje * ``` * @category GameObject */ -export const update_rotation: (gameObject: GameObject, radians: number) => GameObject -= (gameObject: GameObject, radians: number) => { +export function update_rotation(gameObject: GameObject, radians: number): GameObject { if (gameObject instanceof GameObject) { gameObject.setTransform({ ...gameObject.getTransform(), @@ -257,7 +254,7 @@ export const update_rotation: (gameObject: GameObject, radians: number) => GameO return gameObject; } throw new TypeError('Cannot update rotation of a non-GameObject'); -}; +} /** * Updates the color of the GameObject. @@ -272,8 +269,7 @@ export const update_rotation: (gameObject: GameObject, radians: number) => GameO * ``` * @category GameObject */ -export const update_color: (gameObject: GameObject, color: ColorRGBA) => GameObject -= (gameObject: GameObject, color: ColorRGBA) => { +export function update_color(gameObject: GameObject, color: ColorRGBA): GameObject { if (color.length !== 4) { throw new Error('color must be a 4-element array'); } @@ -285,7 +281,7 @@ export const update_color: (gameObject: GameObject, color: ColorRGBA) => GameObj return gameObject; } throw new TypeError('Cannot update color of a non-GameObject'); -}; +} /** * Updates the flip state of the GameObject. @@ -299,8 +295,7 @@ export const update_color: (gameObject: GameObject, color: ColorRGBA) => GameObj * ``` * @category GameObject */ -export const update_flip: (gameObject: GameObject, flip: FlipXY) => GameObject -= (gameObject: GameObject, flip: FlipXY) => { +export function update_flip(gameObject: GameObject, flip: FlipXY): GameObject { if (flip.length !== 2) { throw new Error('flip must be a 2-element array'); } @@ -312,7 +307,7 @@ export const update_flip: (gameObject: GameObject, flip: FlipXY) => GameObject return gameObject; } throw new TypeError('Cannot update flip of a non-GameObject'); -}; +} /** * Updates the text of the TextGameObject. @@ -327,8 +322,7 @@ export const update_flip: (gameObject: GameObject, flip: FlipXY) => GameObject * ``` * @category GameObject */ -export const update_text: (textGameObject: TextGameObject, text: string) => GameObject -= (textGameObject: TextGameObject, text: string) => { +export function update_text(textGameObject: TextGameObject, text: string): GameObject { if (textGameObject instanceof TextGameObject) { textGameObject.setText({ text @@ -336,7 +330,7 @@ export const update_text: (textGameObject: TextGameObject, text: string) => Game return textGameObject; } throw new TypeError('Cannot update text onto a non-TextGameObject'); -}; +} /** * Renders this GameObject in front of all other GameObjects. @@ -348,14 +342,13 @@ export const update_text: (textGameObject: TextGameObject, text: string) => Game * ``` * @category GameObject */ -export const update_to_top: (gameObject: GameObject) => GameObject -= (gameObject: GameObject) => { +export function update_to_top(gameObject: GameObject): GameObject { if (gameObject instanceof RenderableGameObject) { gameObject.setBringToTopFlag(); return gameObject; } throw new TypeError('Cannot update to top a non-GameObject'); -}; +} // ============================================================================= // Querying of GameObjects @@ -376,12 +369,12 @@ export const update_to_top: (gameObject: GameObject) => GameObject * ``` * @category GameObject */ -export const query_id: (gameObject: GameObject) => number = (gameObject: GameObject) => { +export function query_id(gameObject: GameObject): number { if (gameObject instanceof GameObject) { return gameObject.id; } throw new TypeError('Cannot query id of non-GameObject'); -}; +} /** * Queries the [x, y] position transform of the GameObject. @@ -395,13 +388,12 @@ export const query_id: (gameObject: GameObject) => number = (gameObject: GameObj * ``` * @category GameObject */ -export const query_position: (gameObject: GameObject) => PositionXY -= (gameObject: GameObject) => { +export function query_position(gameObject: GameObject): PositionXY { if (gameObject instanceof GameObject) { return [...gameObject.getTransform().position]; } throw new TypeError('Cannot query position of non-GameObject'); -}; +} /** * Queries the z-rotation transform of the GameObject. @@ -415,13 +407,12 @@ export const query_position: (gameObject: GameObject) => PositionXY * ``` * @category GameObject */ -export const query_rotation: (gameObject: GameObject) => number -= (gameObject: GameObject) => { +export function query_rotation(gameObject: GameObject): number { if (gameObject instanceof GameObject) { return gameObject.getTransform().rotation; } throw new TypeError('Cannot query rotation of non-GameObject'); -}; +} /** * Queries the [x, y] scale transform of the GameObject. @@ -435,13 +426,12 @@ export const query_rotation: (gameObject: GameObject) => number * ``` * @category GameObject */ -export const query_scale: (gameObject: GameObject) => ScaleXY -= (gameObject: GameObject) => { +export function query_scale(gameObject: GameObject): ScaleXY { if (gameObject instanceof GameObject) { return [...gameObject.getTransform().scale]; } throw new TypeError('Cannot query scale of non-GameObject'); -}; +} /** * Queries the [r, g, b, a] color property of the GameObject. @@ -455,13 +445,12 @@ export const query_scale: (gameObject: GameObject) => ScaleXY * ``` * @category GameObject */ -export const query_color: (gameObject: RenderableGameObject) => ColorRGBA -= (gameObject: RenderableGameObject) => { +export function query_color(gameObject: RenderableGameObject): ColorRGBA { if (gameObject instanceof RenderableGameObject) { return [...gameObject.getColor()]; } throw new TypeError('Cannot query color of non-GameObject'); -}; +} /** * Queries the [x, y] flip property of the GameObject. @@ -475,13 +464,12 @@ export const query_color: (gameObject: RenderableGameObject) => ColorRGBA * ``` * @category GameObject */ -export const query_flip: (gameObject: RenderableGameObject) => FlipXY -= (gameObject: RenderableGameObject) => { +export function query_flip(gameObject: RenderableGameObject): FlipXY { if (gameObject instanceof RenderableGameObject) { return [...gameObject.getFlipState()]; } throw new TypeError('Cannot query flip of non-GameObject'); -}; +} /** * Queries the text of a Text GameObject. @@ -496,13 +484,12 @@ export const query_flip: (gameObject: RenderableGameObject) => FlipXY * ``` * @category GameObject */ -export const query_text: (textGameObject: TextGameObject) => string -= (textGameObject: TextGameObject) => { +export function query_text(textGameObject: TextGameObject): string { if (textGameObject instanceof TextGameObject) { return textGameObject.getText().text; } throw new TypeError('Cannot query text of non-TextGameObject'); -}; +} /** * Queries the (mouse) pointer position. @@ -515,8 +502,9 @@ export const query_text: (textGameObject: TextGameObject) => string * position[1]; // y * ``` */ -export const query_pointer_position: () => PositionXY -= () => gameState.pointerProps.pointerPosition; +export function query_pointer_position(): PositionXY { + return gameState.pointerProps.pointerPosition; +} // ============================================================================= // Game configuration @@ -554,9 +542,9 @@ const withinRange: (num: number, min: number, max: number) => number * set_fps(60); * ``` */ -export const set_fps: (fps: number) => void = (fps: number) => { +export function set_fps(fps: number) { config.fps = withinRange(fps, MIN_FPS, MAX_FPS); -}; +} /** * Sets the dimensions of the canvas, which should be between the @@ -569,13 +557,13 @@ export const set_fps: (fps: number) => void = (fps: number) => { * set_dimensions([500, 400]); * ``` */ -export const set_dimensions: (dimensions: DimensionsXY) => void = (dimensions: DimensionsXY) => { +export function set_dimensions(dimensions: DimensionsXY) { if (dimensions.length !== 2) { throw new Error('dimensions must be a 2-element array'); } config.width = withinRange(dimensions[0], MIN_WIDTH, MAX_WIDTH); config.height = withinRange(dimensions[1], MIN_HEIGHT, MAX_HEIGHT); -}; +} /** * Sets the scale (zoom) of the pixels in the canvas. @@ -590,9 +578,9 @@ export const set_dimensions: (dimensions: DimensionsXY) => void = (dimensions: D * set_scale(2); * ``` */ -export const set_scale: (scale: number) => void = (scale: number) => { +export function set_scale(scale: number) { config.scale = withinRange(scale, MIN_SCALE, MAX_SCALE); -}; +} /** * Enables debug mode. @@ -608,9 +596,9 @@ export const set_scale: (scale: number) => void = (scale: number) => { * }); * ``` */ -export const enable_debug: () => void = () => { +export function enable_debug() { config.isDebugEnabled = true; -}; +} /** * Logs any information passed into it within the `update_loop`. @@ -626,11 +614,11 @@ export const enable_debug: () => void = () => { * }); * ``` */ -export const debug_log: (info: string) => void = (info: string) => { +export function debug_log(info: string) { if (config.isDebugEnabled) { gameState.debugLogArray.push(info); } -}; +} // ============================================================================= // Game loop @@ -651,7 +639,9 @@ export const debug_log: (info: string) => void = (info: string) => { * ``` * @category Logic */ -export const input_key_down: (key_name: string) => boolean = (key_name: string) => gameState.inputKeysDown.has(key_name); +export function input_key_down(key_name: string) { + return gameState.inputKeysDown.has(key_name); +} /** * Detects if the left mouse button is pressed down. @@ -666,7 +656,9 @@ export const input_key_down: (key_name: string) => boolean = (key_name: string) * ``` * @category Logic */ -export const input_left_mouse_down: () => boolean = () => gameState.pointerProps.isPointerPrimaryDown; +export function input_left_mouse_down() { + return gameState.pointerProps.isPointerPrimaryDown; +} /** * Detects if the right mouse button is pressed down. @@ -681,7 +673,9 @@ export const input_left_mouse_down: () => boolean = () => gameState.pointerProps * ``` * @category Logic */ -export const input_right_mouse_down: () => boolean = () => gameState.pointerProps.isPointerSecondaryDown; +export function input_right_mouse_down() { + return gameState.pointerProps.isPointerSecondaryDown; +} /** * Detects if the (mouse) pointer is over the gameobject. @@ -700,12 +694,13 @@ export const input_right_mouse_down: () => boolean = () => gameState.pointerProp * ``` * @category Logic */ -export const pointer_over_gameobject = (gameObject: GameObject) => { +export function pointer_over_gameobject(gameObject: GameObject) { if (gameObject instanceof GameObject) { return gameState.pointerProps.pointerOverGameObjectsId.has(gameObject.id); } throw new TypeError('Cannot check pointer over non-GameObject'); -}; +} + /** * Checks if two gameobjects overlap with each other, using a rectangular bounding box. * This bounding box is rectangular, for all GameObjects. @@ -724,13 +719,13 @@ export const pointer_over_gameobject = (gameObject: GameObject) => { * ``` * @category Logic */ -export const gameobjects_overlap: (gameObject1: InteractableGameObject, gameObject2: InteractableGameObject) => boolean -= (gameObject1: InteractableGameObject, gameObject2: InteractableGameObject) => { +export function gameobjects_overlap(gameObject1: InteractableGameObject, gameObject2: InteractableGameObject) { if (gameObject1 instanceof InteractableGameObject && gameObject2 instanceof InteractableGameObject) { return gameObject1.isOverlapping(gameObject2); } throw new TypeError('Cannot check overlap of non-GameObject'); -}; +} + /** * Gets the current in-game time, which is based off the start time. * This function should be called in your update function. @@ -743,7 +738,9 @@ export const gameobjects_overlap: (gameObject1: InteractableGameObject, gameObje * } * ``` */ -export const get_game_time: () => number = () => gameState.gameTime; +export function get_game_time() { + return gameState.gameTime; +} /** * Gets the current loop count, which is the number of frames that have run. @@ -758,7 +755,9 @@ export const get_game_time: () => number = () => gameState.gameTime; * } * ``` */ -export const get_loop_count: () => number = () => gameState.loopCount; +export function get_loop_count() { + return gameState.loopCount; +} /** * This sets the update loop in the canvas. @@ -783,12 +782,12 @@ export const get_loop_count: () => number = () => gameState.loopCount; * }) * ``` */ -export const update_loop: (update_function: UpdateFunction) => void = (update_function: UpdateFunction) => { +export function update_loop(update_function: UpdateFunction) { // Test for error in user update function // This cannot not check for errors inside a block that is not run. update_function([]); config.userUpdateFunction = update_function; -}; +} /** * Builds the game. @@ -801,7 +800,7 @@ export const update_loop: (update_function: UpdateFunction) => void = (update_fu * build_game(); * ``` */ -export const build_game: () => BuildGame = () => { +export function build_game(): BuildGame { // Reset frame and time counters. gameState.loopCount = 0; gameState.gameTime = 0; @@ -837,7 +836,7 @@ export const build_game: () => BuildGame = () => { toReplString: () => '[Arcade 2D]', gameConfig }; -}; +} // ============================================================================= // Audio functions @@ -861,8 +860,7 @@ export const build_game: () => BuildGame = () => { * ``` * @category Audio */ -export const create_audio: (audio_url: string, volume_level: number) => AudioClip -= (audio_url: string, volume_level: number) => { +export function create_audio(audio_url: string, volume_level: number) { if (typeof audio_url !== 'string') { throw new Error('audio_url must be a string'); } @@ -870,7 +868,7 @@ export const create_audio: (audio_url: string, volume_level: number) => AudioCli throw new Error('volume_level must be a number'); } return AudioClip.of(audio_url, withinRange(volume_level, MIN_VOLUME, MAX_VOLUME)); -}; +} /** * Loops the audio clip provided, which will play the audio clip indefinitely. @@ -884,13 +882,13 @@ export const create_audio: (audio_url: string, volume_level: number) => AudioCli * ``` * @category Audio */ -export const loop_audio: (audio_clip: AudioClip) => AudioClip = (audio_clip: AudioClip) => { +export function loop_audio(audio_clip: AudioClip) { if (audio_clip instanceof AudioClip) { audio_clip.setShouldAudioClipLoop(true); return audio_clip; } throw new TypeError('Cannot loop a non-AudioClip'); -}; +} /** * Plays the audio clip, and stops when the audio clip is over. @@ -903,13 +901,13 @@ export const loop_audio: (audio_clip: AudioClip) => AudioClip = (audio_clip: Aud * ``` * @category Audio */ -export const play_audio: (audio_clip: AudioClip) => AudioClip = (audio_clip: AudioClip) => { +export function play_audio(audio_clip: AudioClip) { if (audio_clip instanceof AudioClip) { audio_clip.setShouldAudioClipPlay(true); return audio_clip; } throw new TypeError('Cannot play a non-AudioClip'); -}; +} /** * Stops the audio clip immediately. @@ -922,10 +920,10 @@ export const play_audio: (audio_clip: AudioClip) => AudioClip = (audio_clip: Aud * ``` * @category Audio */ -export const stop_audio: (audio_clip: AudioClip) => AudioClip = (audio_clip: AudioClip) => { +export function stop_audio(audio_clip: AudioClip) { if (audio_clip instanceof AudioClip) { audio_clip.setShouldAudioClipPlay(false); return audio_clip; } throw new TypeError('Cannot stop a non-AudioClip'); -}; +} diff --git a/src/bundles/binary_tree/package.json b/src/bundles/binary_tree/package.json index 0ef37e80cc..3ed095c1fc 100644 --- a/src/bundles/binary_tree/package.json +++ b/src/bundles/binary_tree/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/communication/package.json b/src/bundles/communication/package.json index 939651a11c..a627ae8f27 100644 --- a/src/bundles/communication/package.json +++ b/src/bundles/communication/package.json @@ -7,7 +7,7 @@ "uniqid": "^5.4.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/copy_gc/package.json b/src/bundles/copy_gc/package.json index 7f8ef56839..72dcbfcf9e 100644 --- a/src/bundles/copy_gc/package.json +++ b/src/bundles/copy_gc/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/csg/package.json b/src/bundles/csg/package.json index df4818d40f..68fbbd5003 100644 --- a/src/bundles/csg/package.json +++ b/src/bundles/csg/package.json @@ -13,7 +13,7 @@ "./*.js": "./dist/*.js" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/curve/package.json b/src/bundles/curve/package.json index 2aea349b16..1f00d503a7 100644 --- a/src/bundles/curve/package.json +++ b/src/bundles/curve/package.json @@ -7,13 +7,17 @@ "gl-matrix": "^3.3.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", "exports": { - ".": "./dist/index.js", - "./*": "./dist/*.js" + ".": { + "default": "./dist/index.js" + }, + "./*": { + "default": "./dist/*.js" + } }, "scripts": { "tsc": "tsc --project ./tsconfig.prod.json", diff --git a/src/bundles/curve/src/functions.ts b/src/bundles/curve/src/functions.ts index c47a46da70..ddcc4b1b3a 100644 --- a/src/bundles/curve/src/functions.ts +++ b/src/bundles/curve/src/functions.ts @@ -59,6 +59,7 @@ function createDrawFunction( * Curve at `num` sample points and connecting each pair with a line. * The parts between (0,0) and (1,1) of the resulting Drawing are shown in the window. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -75,6 +76,7 @@ export const draw_connected = createDrawFunction('none', 'lines', '2D', false); * translated and stretched/shrunk to show the full curve and maximize its width * and height, with some padding. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -96,6 +98,7 @@ export const draw_connected_full_view = createDrawFunction( * is translated and scaled proportionally to show the full curve and maximize * its size, with some padding. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -117,6 +120,7 @@ export const draw_connected_full_view_proportional = createDrawFunction( * points, and does not connect them. The parts between (0,0) and (1,1) of the * resulting Drawing are shown in the window. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1,there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -134,6 +138,7 @@ export const draw_points = createDrawFunction('none', 'points', '2D', false); * stretched/shrunk to show the full curve and maximize its width and height, * with some padding. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -156,6 +161,7 @@ export const draw_points_full_view = createDrawFunction( * proportionally with its size maximized to fit entirely inside the window, * with some padding. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -177,6 +183,7 @@ export const draw_points_full_view_proportional = createDrawFunction( * a line. The parts between (0,0,0) and (1,1,1) of the resulting Drawing are * shown within the unit cube. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -198,6 +205,7 @@ export const draw_3D_connected = createDrawFunction( * a line. The Drawing is translated and stretched/shrunk to show the full * curve and maximize its width and height within the cube. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -219,6 +227,7 @@ export const draw_3D_connected_full_view = createDrawFunction( * a line. The Drawing is translated and scaled proportionally with its size * maximized to fit entirely inside the cube. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -240,6 +249,7 @@ export const draw_3D_connected_full_view_proportional = createDrawFunction( * isolated points, and does not connect them. The parts between (0,0,0) * and (1,1,1) of the resulting Drawing are shown within the unit cube. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -256,6 +266,7 @@ export const draw_3D_points = createDrawFunction('none', 'points', '3D', false); * isolated points, and does not connect them. The Drawing is translated and * stretched/shrunk to maximize its size to fit entirely inside the cube. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -277,6 +288,7 @@ export const draw_3D_points_full_view = createDrawFunction( * isolated points, and does not connect them. The Drawing is translated and * scaled proportionally with its size maximized to fit entirely inside the cube. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing diff --git a/src/bundles/curve/tsconfig.json b/src/bundles/curve/tsconfig.json index 662ac7ea48..54acc9daf7 100644 --- a/src/bundles/curve/tsconfig.json +++ b/src/bundles/curve/tsconfig.json @@ -6,5 +6,8 @@ ], "compilerOptions": { "noEmit": true + }, + "typedocOptions": { + "name": "curve" } } \ No newline at end of file diff --git a/src/bundles/game/package.json b/src/bundles/game/package.json index 6fe383ef4d..eefa3f1d24 100644 --- a/src/bundles/game/package.json +++ b/src/bundles/game/package.json @@ -7,7 +7,7 @@ "phaser": "^3.54.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/jest.config.js b/src/bundles/jest.config.js index a91be09fbd..8d868a8011 100644 --- a/src/bundles/jest.config.js +++ b/src/bundles/jest.config.js @@ -1,30 +1,19 @@ +// Jest Config for bundles + /** @type {import('ts-jest').JestConfigWithTsJest} */ export default { displayName: 'Bundles', testEnvironment: 'jsdom', - extensionsToTreatAsEsm: [".ts"], + extensionsToTreatAsEsm: ['.ts'], preset: 'ts-jest/presets/default-esm', - // transform: { - // '.ts': ['ts-jest', { - // useESM: true, - // /** - // * ts-jest preset currently has an issue with the 'verbatimModuleSyntax' typescript option: - // * This whole transform bit should be removed once this is resolved: - // * https://github.com/kulshekhar/ts-jest/issues/4081 - // */ - // isolatedModules: true - // }] - // }, moduleNameMapper: { - '#(.*)': '/node_modules/$1', '^js-slang/context': '/__mocks__/context.ts', '^three.+.js': '/__mocks__/emptyModule.ts', }, - transformIgnorePatterns: [ - 'node_modules/(?!=chalk)/', - '.+\\.js' - ], setupFiles: [ './jest.polyfills.js' + ], + testMatch: [ + "/**/__tests__/**/*.ts" ] }; diff --git a/src/bundles/mark_sweep/package.json b/src/bundles/mark_sweep/package.json index c5363caecd..cc0787d783 100644 --- a/src/bundles/mark_sweep/package.json +++ b/src/bundles/mark_sweep/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/nbody/package.json b/src/bundles/nbody/package.json index 2bb32aa63c..ab228af9ee 100644 --- a/src/bundles/nbody/package.json +++ b/src/bundles/nbody/package.json @@ -6,7 +6,7 @@ "nbody": "^0.2.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/package.json b/src/bundles/package.json index d7dd351568..cdb1d0352e 100644 --- a/src/bundles/package.json +++ b/src/bundles/package.json @@ -1,6 +1,6 @@ { "private": true, - "name": "bundles", + "name": "@sourceacademy/bundles", "type": "module", "workspaces": [ "./*" diff --git a/src/bundles/painter/package.json b/src/bundles/painter/package.json index fcc0bea6c5..3ceb8afb98 100644 --- a/src/bundles/painter/package.json +++ b/src/bundles/painter/package.json @@ -6,7 +6,7 @@ "plotly.js-dist": "^2.17.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/plotly.js": "^2.35.4", "typescript": "^5.8.2" }, diff --git a/src/bundles/physics_2d/package.json b/src/bundles/physics_2d/package.json index 4ae731c5b8..ff825a2757 100644 --- a/src/bundles/physics_2d/package.json +++ b/src/bundles/physics_2d/package.json @@ -6,7 +6,7 @@ "@box2d/core": "^0.10.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/pix_n_flix/package.json b/src/bundles/pix_n_flix/package.json index 3fe4480102..baf3d0afbd 100644 --- a/src/bundles/pix_n_flix/package.json +++ b/src/bundles/pix_n_flix/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/plotly/package.json b/src/bundles/plotly/package.json index b98a2ec014..2a0cd017b3 100644 --- a/src/bundles/plotly/package.json +++ b/src/bundles/plotly/package.json @@ -4,10 +4,11 @@ "private": true, "dependencies": { "@sourceacademy/bundle-sound": "workspace:^", - "js-slang": "^1.0.81" + "js-slang": "^1.0.81", + "plotly.js-dist": "^2.17.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/plotly.js": "^2.35.4", "typescript": "^5.8.2" }, diff --git a/src/bundles/plotly/src/functions.ts b/src/bundles/plotly/src/functions.ts index a254a20945..0e56009a44 100644 --- a/src/bundles/plotly/src/functions.ts +++ b/src/bundles/plotly/src/functions.ts @@ -311,6 +311,7 @@ function createPlotFunction( * Returns a function that turns a given Curve into a Drawing, by sampling the * Curve at `num` sample points and connecting each pair with a line. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing @@ -338,6 +339,7 @@ export const draw_connected_2d = createPlotFunction( * Returns a function that turns a given 3D Curve into a Drawing, by sampling the * 3D Curve at `num` sample points and connecting each pair with a line. * + * @function * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type 3D Curve → Drawing @@ -360,6 +362,7 @@ export const draw_connected_3d = createPlotFunction( * * * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points + * @function * @return function of type 2D Curve → Drawing * @example * ``` @@ -385,6 +388,7 @@ export const draw_points_2d = createPlotFunction( * * * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points + * @function * @return function of type 3D Curve → Drawing * @example * ``` @@ -400,7 +404,7 @@ export const draw_points_3d = createPlotFunction( * Visualizes the sound on a 2d line graph * @param sound the sound which is to be visualized on plotly */ -export const draw_sound_2d = (sound: Sound) => { +export function draw_sound_2d(sound: Sound) { const FS: number = 44100; // Output sample rate if (!is_sound(sound)) { throw new Error( @@ -411,7 +415,6 @@ export const draw_sound_2d = (sound: Sound) => { throw new Error('draw_sound_2d: duration of sound is negative'); } else { // Instantiate audio context if it has not been instantiated. - // Create mono buffer const channel: number[] = []; const time_stamps: number[] = []; @@ -461,7 +464,7 @@ export const draw_sound_2d = (sound: Sound) => { ); if (drawnPlots) drawnPlots.push(plot); } -}; +} function draw_new_curve(divId: string, data: Data, layout: Partial) { Plotly.react(divId, [data], layout); diff --git a/src/bundles/remote_execution/package.json b/src/bundles/remote_execution/package.json index 826873a64c..98faa5c3d6 100644 --- a/src/bundles/remote_execution/package.json +++ b/src/bundles/remote_execution/package.json @@ -6,7 +6,7 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/repeat/package.json b/src/bundles/repeat/package.json index 12af0d95ab..71b2071a5f 100644 --- a/src/bundles/repeat/package.json +++ b/src/bundles/repeat/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/repl/package.json b/src/bundles/repl/package.json index 00fcb3cb8e..ed90b93cff 100644 --- a/src/bundles/repl/package.json +++ b/src/bundles/repl/package.json @@ -6,7 +6,7 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/robot_simulation/package.json b/src/bundles/robot_simulation/package.json index a13c04a095..589dfe7209 100644 --- a/src/bundles/robot_simulation/package.json +++ b/src/bundles/robot_simulation/package.json @@ -6,7 +6,7 @@ "three": "^0.175.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/three": "^0.175.0", "typescript": "^5.8.2" }, diff --git a/src/bundles/rune/package.json b/src/bundles/rune/package.json index 8dc91f842c..75902e56ff 100644 --- a/src/bundles/rune/package.json +++ b/src/bundles/rune/package.json @@ -7,7 +7,7 @@ "gl-matrix": "^3.3.0" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/rune/src/runes_ops.ts b/src/bundles/rune/src/runes_ops.ts index 4e5c935629..b2d011c584 100644 --- a/src/bundles/rune/src/runes_ops.ts +++ b/src/bundles/rune/src/runes_ops.ts @@ -22,7 +22,7 @@ export function throwIfNotRune(name: string, ...runes: any) { /** * primitive Rune in the rune of a full square * */ -export const getSquare: () => Rune = () => { +export function getSquare() { const vertexList: number[] = []; const colorList: number[] = []; @@ -39,9 +39,11 @@ export const getSquare: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} -export const getBlank: () => Rune = () => Rune.of(); +export function getBlank() { + return Rune.of(); +} /** * primitive Rune in the rune of a @@ -49,7 +51,7 @@ export const getBlank: () => Rune = () => Rune.of(); * each diagonally split into a * black and white half * */ -export const getRcross: () => Rune = () => { +export function getRcross() { const vertexList: number[] = []; const colorList: number[] = []; // lower small triangle @@ -80,12 +82,12 @@ export const getRcross: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of a sail * */ -export const getSail: () => Rune = () => { +export function getSail() { const vertexList: number[] = []; const colorList: number[] = []; @@ -99,12 +101,12 @@ export const getSail: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of a triangle * */ -export const getTriangle: () => Rune = () => { +export function getTriangle() { const vertexList: number[] = []; const colorList: number[] = []; @@ -118,13 +120,13 @@ export const getTriangle: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune with black triangle, * filling upper right corner * */ -export const getCorner: () => Rune = () => { +export function getCorner() { const vertexList: number[] = []; const colorList: number[] = []; vertexList.push(1, 0, 0, 1); @@ -137,14 +139,14 @@ export const getCorner: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of two overlapping * triangles, residing in the upper half * of * */ -export const getNova: () => Rune = () => { +export function getNova() { const vertexList: number[] = []; const colorList: number[] = []; vertexList.push(0, 1, 0, 1); @@ -161,12 +163,12 @@ export const getNova: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of a circle * */ -export const getCircle: () => Rune = () => { +export function getCircle() { const vertexList: number[] = []; const colorList: number[] = []; const circleDiv = 60; @@ -183,12 +185,12 @@ export const getCircle: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of a heart * */ -export const getHeart: () => Rune = () => { +export function getHeart() { const vertexList: number[] = []; const colorList: number[] = []; @@ -244,12 +246,12 @@ export const getHeart: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of a pentagram * */ -export const getPentagram: () => Rune = () => { +export function getPentagram() { const vertexList: number[] = []; const colorList: number[] = []; @@ -278,13 +280,13 @@ export const getPentagram: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} /** * primitive Rune in the rune of a ribbon * winding outwards in an anticlockwise spiral * */ -export const getRibbon: () => Rune = () => { +export function getRibbon() { const vertexList: number[] = []; const colorList: number[] = []; @@ -319,7 +321,7 @@ export const getRibbon: () => Rune = () => { vertices: new Float32Array(vertexList), colors: new Float32Array(colorList) }); -}; +} // ============================================================================= // Coloring Functions diff --git a/src/bundles/rune_in_words/package.json b/src/bundles/rune_in_words/package.json index 1e30b77ef9..248e8928ad 100644 --- a/src/bundles/rune_in_words/package.json +++ b/src/bundles/rune_in_words/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/scrabble/package.json b/src/bundles/scrabble/package.json index cd1cf50e95..767cf120c8 100644 --- a/src/bundles/scrabble/package.json +++ b/src/bundles/scrabble/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/sound/package.json b/src/bundles/sound/package.json index 152d68864c..54fea2febc 100644 --- a/src/bundles/sound/package.json +++ b/src/bundles/sound/package.json @@ -6,7 +6,7 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/sound_matrix/package.json b/src/bundles/sound_matrix/package.json index b35b46d8ef..582b219eec 100644 --- a/src/bundles/sound_matrix/package.json +++ b/src/bundles/sound_matrix/package.json @@ -6,7 +6,7 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/stereo_sound/package.json b/src/bundles/stereo_sound/package.json index 230df292c4..5fa8970b38 100644 --- a/src/bundles/stereo_sound/package.json +++ b/src/bundles/stereo_sound/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "dependencies": { diff --git a/src/bundles/unittest/package.json b/src/bundles/unittest/package.json index db61ed2cf4..8db4384569 100644 --- a/src/bundles/unittest/package.json +++ b/src/bundles/unittest/package.json @@ -6,7 +6,7 @@ "js-slang": "^1.0.81" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "type": "module", diff --git a/src/bundles/unity_academy/package.json b/src/bundles/unity_academy/package.json index 0d6a60d41d..fe87dc6061 100644 --- a/src/bundles/unity_academy/package.json +++ b/src/bundles/unity_academy/package.json @@ -9,7 +9,7 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", "typescript": "^5.8.2" diff --git a/src/bundles/wasm/package.json b/src/bundles/wasm/package.json index 7eae4c9e2e..7c28fc0e66 100644 --- a/src/bundles/wasm/package.json +++ b/src/bundles/wasm/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "typescript": "^5.8.2" }, "dependencies": { From ff1941cdc426638d18a88db3681af9ec389e1a63 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 19 May 2025 20:27:52 -0700 Subject: [PATCH 016/112] Move stuff to a separate lib folder --- lib/.gitignore | 1 + lib/buildtools/.gitignore | 1 + lib/buildtools/bin/docsreadme.md | 18 ++ .../bin/templates/bundle/src/index.ts | 25 +++ .../bin/templates/bundle/tsconfig.json | 4 + lib/buildtools/bin/templates/tab/index.tsx | 71 +++++++ .../bin/templates/tab/tsconfig.json | 4 + lib/buildtools/build.js | 25 +++ lib/buildtools/build/bundle/test0.js | 49 +++++ lib/buildtools/jest.config.js | 26 +++ lib/buildtools/jest.setup.ts | 20 ++ lib/buildtools/package.json | 38 ++++ .../__test_mocks__/bundles/test0/index.ts | 17 ++ .../bundles/test0/tsconfig.json | 4 + .../__test_mocks__/bundles/test1/index.ts | 4 + .../bundles/test1/tsconfig.json | 4 + .../__test_mocks__/bundles/tsconfig.json | 45 +++++ .../src/build/docs/__mocks__/docsUtils.ts | 16 ++ .../src/build/docs/__tests__/building.test.ts | 76 +++++++ lib/buildtools/src/build/docs/docsUtils.ts | 63 ++++++ lib/buildtools/src/build/docs/drawdown.ts | 190 ++++++++++++++++++ lib/buildtools/src/build/docs/index.ts | 39 ++++ lib/buildtools/src/build/docs/json.ts | 73 +++++++ lib/buildtools/src/build/manifest.ts | 133 ++++++++++++ .../build/modules/__tests__/bundle.test.ts | 48 +++++ lib/buildtools/src/build/modules/bundle.ts | 31 +++ lib/buildtools/src/build/modules/commons.ts | 62 ++++++ lib/buildtools/src/build/modules/index.ts | 27 +++ .../src/build/modules/manifest.schema.json | 23 +++ lib/buildtools/src/build/modules/tab.ts | 96 +++++++++ .../src/commands/__tests__/main.test.ts | 9 + .../src/commands/__tests__/template.test.ts | 172 ++++++++++++++++ .../src/commands/__tests__/testing.test.ts | 32 +++ lib/buildtools/src/commands/build.ts | 68 +++++++ lib/buildtools/src/commands/index.ts | 3 + lib/buildtools/src/commands/list.ts | 56 ++++++ lib/buildtools/src/commands/main.ts | 14 ++ lib/buildtools/src/commands/prebuild.ts | 49 +++++ lib/buildtools/src/commands/template.ts | 41 ++++ lib/buildtools/src/commands/testing.ts | 16 ++ lib/buildtools/src/commands/utils.ts | 71 +++++++ lib/buildtools/src/prebuild/lint.ts | 39 ++++ lib/buildtools/src/prebuild/typecheck.ts | 82 ++++++++ .../src/templates/__tests__/names.test.ts | 22 ++ lib/buildtools/src/templates/bundle.ts | 60 ++++++ lib/buildtools/src/templates/print.ts | 27 +++ lib/buildtools/src/templates/tab.ts | 85 ++++++++ lib/buildtools/src/templates/utilities.ts | 20 ++ lib/buildtools/src/testing/runner.ts | 10 + lib/buildtools/src/utils.ts | 43 ++++ lib/buildtools/tsconfig.json | 15 ++ lib/buildtools/workspacer.py | 97 +++++++++ lib/lintplugin/package.json | 38 ++++ lib/lintplugin/src/configs.ts | 78 +++++++ lib/lintplugin/src/index.ts | 18 ++ .../src/rules/__tests__/modulestag.test.ts | 54 +++++ .../src/rules/__tests__/typeimports.test.ts | 35 ++++ lib/lintplugin/src/rules/moduleTag.ts | 56 ++++++ lib/lintplugin/src/rules/tabType.ts | 78 +++++++ lib/lintplugin/src/rules/typeimports.ts | 55 +++++ lib/lintplugin/tsconfig.json | 5 + lib/lintplugin/tsconfig.prod.json | 10 + lib/tsconfig.json | 11 + 63 files changed, 2702 insertions(+) create mode 100644 lib/.gitignore create mode 100644 lib/buildtools/.gitignore create mode 100644 lib/buildtools/bin/docsreadme.md create mode 100644 lib/buildtools/bin/templates/bundle/src/index.ts create mode 100644 lib/buildtools/bin/templates/bundle/tsconfig.json create mode 100644 lib/buildtools/bin/templates/tab/index.tsx create mode 100644 lib/buildtools/bin/templates/tab/tsconfig.json create mode 100644 lib/buildtools/build.js create mode 100644 lib/buildtools/build/bundle/test0.js create mode 100644 lib/buildtools/jest.config.js create mode 100644 lib/buildtools/jest.setup.ts create mode 100644 lib/buildtools/package.json create mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/test0/index.ts create mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/test0/tsconfig.json create mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/test1/index.ts create mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/test1/tsconfig.json create mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json create mode 100644 lib/buildtools/src/build/docs/__mocks__/docsUtils.ts create mode 100644 lib/buildtools/src/build/docs/__tests__/building.test.ts create mode 100644 lib/buildtools/src/build/docs/docsUtils.ts create mode 100644 lib/buildtools/src/build/docs/drawdown.ts create mode 100644 lib/buildtools/src/build/docs/index.ts create mode 100644 lib/buildtools/src/build/docs/json.ts create mode 100644 lib/buildtools/src/build/manifest.ts create mode 100644 lib/buildtools/src/build/modules/__tests__/bundle.test.ts create mode 100644 lib/buildtools/src/build/modules/bundle.ts create mode 100644 lib/buildtools/src/build/modules/commons.ts create mode 100644 lib/buildtools/src/build/modules/index.ts create mode 100644 lib/buildtools/src/build/modules/manifest.schema.json create mode 100644 lib/buildtools/src/build/modules/tab.ts create mode 100644 lib/buildtools/src/commands/__tests__/main.test.ts create mode 100644 lib/buildtools/src/commands/__tests__/template.test.ts create mode 100644 lib/buildtools/src/commands/__tests__/testing.test.ts create mode 100644 lib/buildtools/src/commands/build.ts create mode 100644 lib/buildtools/src/commands/index.ts create mode 100644 lib/buildtools/src/commands/list.ts create mode 100644 lib/buildtools/src/commands/main.ts create mode 100644 lib/buildtools/src/commands/prebuild.ts create mode 100644 lib/buildtools/src/commands/template.ts create mode 100644 lib/buildtools/src/commands/testing.ts create mode 100644 lib/buildtools/src/commands/utils.ts create mode 100644 lib/buildtools/src/prebuild/lint.ts create mode 100644 lib/buildtools/src/prebuild/typecheck.ts create mode 100644 lib/buildtools/src/templates/__tests__/names.test.ts create mode 100644 lib/buildtools/src/templates/bundle.ts create mode 100644 lib/buildtools/src/templates/print.ts create mode 100644 lib/buildtools/src/templates/tab.ts create mode 100644 lib/buildtools/src/templates/utilities.ts create mode 100644 lib/buildtools/src/testing/runner.ts create mode 100644 lib/buildtools/src/utils.ts create mode 100644 lib/buildtools/tsconfig.json create mode 100644 lib/buildtools/workspacer.py create mode 100644 lib/lintplugin/package.json create mode 100644 lib/lintplugin/src/configs.ts create mode 100644 lib/lintplugin/src/index.ts create mode 100644 lib/lintplugin/src/rules/__tests__/modulestag.test.ts create mode 100644 lib/lintplugin/src/rules/__tests__/typeimports.test.ts create mode 100644 lib/lintplugin/src/rules/moduleTag.ts create mode 100644 lib/lintplugin/src/rules/tabType.ts create mode 100644 lib/lintplugin/src/rules/typeimports.ts create mode 100644 lib/lintplugin/tsconfig.json create mode 100644 lib/lintplugin/tsconfig.prod.json create mode 100644 lib/tsconfig.json diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000000..53c37a1660 --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/lib/buildtools/.gitignore b/lib/buildtools/.gitignore new file mode 100644 index 0000000000..55a319dd92 --- /dev/null +++ b/lib/buildtools/.gitignore @@ -0,0 +1 @@ +bin/index.js \ No newline at end of file diff --git a/lib/buildtools/bin/docsreadme.md b/lib/buildtools/bin/docsreadme.md new file mode 100644 index 0000000000..0adf8f5beb --- /dev/null +++ b/lib/buildtools/bin/docsreadme.md @@ -0,0 +1,18 @@ +# Overview + +The Source Academy allows programmers to import functions and constants from a module, using JavaScript's `import` directive. For example, the programmer may decide to import the function `thrice` from the module `repeat` by starting the program with +``` +import { thrice } from "repeat"; +``` + +When evaluating such a directive, the Source Academy looks for a module with the matching name, here `repeat`, in a preconfigured modules site. The Source Academy at https://sourceacademy.org uses the default modules site (located at https://source-academy.github.io/modules). + +After importing functions or constants from a module, they can be used as usual. +``` +thrice(display)(8); // displays 8 three times +``` +if `thrice` is declared in the module `repeat` as follows: +``` +const thrice = f => x => f(f(f(x))); +``` +[List of modules](modules.html) available at the default modules site. \ No newline at end of file diff --git a/lib/buildtools/bin/templates/bundle/src/index.ts b/lib/buildtools/bin/templates/bundle/src/index.ts new file mode 100644 index 0000000000..297cca4d76 --- /dev/null +++ b/lib/buildtools/bin/templates/bundle/src/index.ts @@ -0,0 +1,25 @@ +/** + * A single sentence summarising the module (this sentence is displayed larger). + * + * Sentences describing the module. More sentences about the module. + * + * @module module_name + * @author Author Name + * @author Author Name + */ + +/* + To access things like the context or module state you can just import the context + using the import below + */ +import context from 'js-slang/context'; + +/** + * Sample function. Increments a number by 1. + * + * @param x The number to be incremented. + * @returns The incremented value of the number. + */ +export function sample_function(x: number): number { + return ++x; +} // Then any functions or variables you want to expose to the user is exported from the bundle's index.ts file \ No newline at end of file diff --git a/lib/buildtools/bin/templates/bundle/tsconfig.json b/lib/buildtools/bin/templates/bundle/tsconfig.json new file mode 100644 index 0000000000..2536da7baf --- /dev/null +++ b/lib/buildtools/bin/templates/bundle/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends" : "../tsconfig.json", + "include": ["./src"] +} \ No newline at end of file diff --git a/lib/buildtools/bin/templates/tab/index.tsx b/lib/buildtools/bin/templates/tab/index.tsx new file mode 100644 index 0000000000..34eb95b091 --- /dev/null +++ b/lib/buildtools/bin/templates/tab/index.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +/** + * + * @author + * @author + */ + +/** + * React Component props for the Tab. + */ +type Props = { + children?: never; + className?: never; + context?: any; +}; + +/** + * React Component state for the Tab. + */ +type State = { + counter: number; +}; + +/** + * The main React Component of the Tab. + */ +class Repeat extends React.Component { + constructor(props) { + super(props); + this.state = { + counter: 0, + }; + } + + public render() { + const { counter } = this.state; + return ( +
    This is spawned from the repeat package. Counter is {counter}
    + ); + } +} + +export default { + /** + * This function will be called to determine if the component will be + * rendered. Currently spawns when the result in the REPL is "test". + * @param {DebuggerContext} context + * @returns {boolean} + */ + toSpawn: (context: any) => context.result.value === 'test', + + /** + * This function will be called to render the module tab in the side contents + * on Source Academy frontend. + * @param {DebuggerContext} context + */ + body: (context: any) => , + + /** + * The Tab's icon tooltip in the side contents on Source Academy frontend. + */ + label: 'Sample Tab', + + /** + * BlueprintJS IconName element's name, used to render the icon which will be + * displayed in the side contents panel. + * @see https://blueprintjs.com/docs/#icons + */ + iconName: 'build', +}; \ No newline at end of file diff --git a/lib/buildtools/bin/templates/tab/tsconfig.json b/lib/buildtools/bin/templates/tab/tsconfig.json new file mode 100644 index 0000000000..8e4645929c --- /dev/null +++ b/lib/buildtools/bin/templates/tab/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["./index.tsx" ] +} \ No newline at end of file diff --git a/lib/buildtools/build.js b/lib/buildtools/build.js new file mode 100644 index 0000000000..34688f50fa --- /dev/null +++ b/lib/buildtools/build.js @@ -0,0 +1,25 @@ +/** + * Script for building buildtools + */ + +// @ts-check +import { Command } from '@commander-js/extra-typings' +import { build } from 'esbuild' + +const command = new Command() + .option('--dev', 'If specified, the built output is not minified for easier debugging') + .action(async ({ dev }) => { + await build({ + entryPoints: ['./src/commands/index.ts'], + bundle: true, + format: 'esm', + minify: !dev, + outfile: './bin/index.js', + packages: 'external', + platform: 'node', + target: 'node20', + tsconfig: './tsconfig.json' + }) + }) + +await command.parseAsync() diff --git a/lib/buildtools/build/bundle/test0.js b/lib/buildtools/build/bundle/test0.js new file mode 100644 index 0000000000..bf04a0037e --- /dev/null +++ b/lib/buildtools/build/bundle/test0.js @@ -0,0 +1,49 @@ +export default require => { + var __create = Object.create; + var __defProp = Object.defineProperty; + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; + var __getOwnPropNames = Object.getOwnPropertyNames; + var __getProtoOf = Object.getPrototypeOf; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __require = (x => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { + get: (a, b) => (typeof require !== "undefined" ? require : a)[b] + }) : x)(function (x) { + if (typeof require !== "undefined") return require.apply(this, arguments); + throw Error('Dynamic require of "' + x + '" is not supported'); + }); + var __export = (target, all) => { + for (var name in all) __defProp(target, name, { + get: all[name], + enumerable: true + }); + }; + var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable + }); + } + return to; + }; + var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { + value: mod, + enumerable: true + }) : target, mod)); + var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { + value: true + }), mod); + var index_exports = {}; + __export(index_exports, { + test_function: () => test_function, + test_function2: () => test_function2 + }); + var import_context = __toESM(__require("js-slang/context"), 1); + function test_function(_param0) { + return 0; + } + function test_function2() { + return import_context.default.moduleContexts.test0.state.data; + } + return __toCommonJS(index_exports); +}; \ No newline at end of file diff --git a/lib/buildtools/jest.config.js b/lib/buildtools/jest.config.js new file mode 100644 index 0000000000..7ee62fb59b --- /dev/null +++ b/lib/buildtools/jest.config.js @@ -0,0 +1,26 @@ +// @ts-check + +/** + * Buildtools Jest configuration + * @type {import('jest').Config} + */ +const jestConfig = { + clearMocks: true, + displayName: 'Build Tools', + extensionsToTreatAsEsm: ['.ts'], + testEnvironment: 'node', + transform: { + '.ts': ['ts-jest', { + useESM: true, + }] + }, + transformIgnorePatterns: [ + '/node_modules/(?!typedoc/)', + ], + setupFilesAfterEnv: ['/jest.setup.ts'], + testMatch: [ + '/src/**/__tests__/**/*.test.ts', + ], +}; + +export default jestConfig; diff --git a/lib/buildtools/jest.setup.ts b/lib/buildtools/jest.setup.ts new file mode 100644 index 0000000000..965f0f5b35 --- /dev/null +++ b/lib/buildtools/jest.setup.ts @@ -0,0 +1,20 @@ +// Buildtools Jest setup +const mockChalkFunction = new Proxy((x: string) => x, { + get: () => mockChalkFunction +}); + +jest.mock('chalk', () => new Proxy({}, { + get: () => mockChalkFunction +})); + +jest.mock('fs/promises', () => ({ + copyFile: jest.fn(() => Promise.resolve()), + cp: jest.fn(() => Promise.resolve()), + mkdir: jest.fn(() => Promise.resolve()), + open: jest.fn(), + writeFile: jest.fn(() => Promise.resolve()), +})); + +global.process.exit = jest.fn(code => { + throw new Error(`process.exit called with ${code}`); +}); diff --git a/lib/buildtools/package.json b/lib/buildtools/package.json new file mode 100644 index 0000000000..c2c3db922b --- /dev/null +++ b/lib/buildtools/package.json @@ -0,0 +1,38 @@ +{ + "name": "@sourceacademy/modules-buildtools", + "private": true, + "description": "Tools for building Source Academy bundles and tabs", + "version": "1.0.0", + "devDependencies": { + "@commander-js/extra-typings": "^13.0.0", + "@types/estree": "^1.0.0", + "@types/jest": "^29.0.0", + "@types/node": "^20.12.12" + }, + "exports": { + "*": null + }, + "bin": { + "buildtools": "./bin/index.js" + }, + "type": "module", + "dependencies": { + "acorn": "^8.8.1", + "astring": "^1.8.6", + "chalk": "^5.0.1", + "commander": "^13.0.0", + "console-table-printer": "^2.11.1", + "esbuild": "^0.25.0", + "eslint": "^9.21.0", + "jest": "^29.7.0", + "jsonschema": "^1.5.0", + "ts-jest": "^29.1.2", + "typedoc": "^0.28.4", + "typescript": "^5.8.2" + }, + "scripts": { + "build": "node ./build.js", + "tsc": "tsc --project ./tsconfig.json", + "test": "jest" + } +} diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/test0/index.ts b/lib/buildtools/src/build/__test_mocks__/bundles/test0/index.ts new file mode 100644 index 0000000000..6b08e5e7fe --- /dev/null +++ b/lib/buildtools/src/build/__test_mocks__/bundles/test0/index.ts @@ -0,0 +1,17 @@ +import context from 'js-slang/context' + +/** + * This is just some test function + * @param _param0 Test parameter + * @returns Zero + */ +export function test_function(_param0: string) { + return 0; +} + +/** + * @internal + */ +export function test_function2() { + return context.moduleContexts.test0.state.data +} diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/test0/tsconfig.json b/lib/buildtools/src/build/__test_mocks__/bundles/test0/tsconfig.json new file mode 100644 index 0000000000..50e6f69579 --- /dev/null +++ b/lib/buildtools/src/build/__test_mocks__/bundles/test0/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": ["./index.ts"], + "extends": ["../tsconfig.json"] +} \ No newline at end of file diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/test1/index.ts b/lib/buildtools/src/build/__test_mocks__/bundles/test1/index.ts new file mode 100644 index 0000000000..9f313a9cbd --- /dev/null +++ b/lib/buildtools/src/build/__test_mocks__/bundles/test1/index.ts @@ -0,0 +1,4 @@ +/** + * Test variable + */ +export const test_variable: number = 0; diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/test1/tsconfig.json b/lib/buildtools/src/build/__test_mocks__/bundles/test1/tsconfig.json new file mode 100644 index 0000000000..50e6f69579 --- /dev/null +++ b/lib/buildtools/src/build/__test_mocks__/bundles/test1/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": ["./index.ts"], + "extends": ["../tsconfig.json"] +} \ No newline at end of file diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json b/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json new file mode 100644 index 0000000000..d9a93d7cd9 --- /dev/null +++ b/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + /* Allow JavaScript files to be imported inside your project, instead of just .ts and .tsx files. */ + "allowJs": false, + /* When set to true, allowSyntheticDefaultImports allows you to write an import like "import React from "react";" */ + "allowSyntheticDefaultImports": true, + /* See https://www.typescriptlang.org/tsconfig#esModuleInterop */ + "esModuleInterop": true, + /* Controls how JSX constructs are emitted in JavaScript files. This only affects output of JS files that started in .tsx files. */ + "jsx": "react-jsx", + /* See https://www.typescriptlang.org/tsconfig#lib */ + "lib": ["es6", "dom", "es2016", "ESNext", "scripthost"], + /* Sets the module system for the program. See the Modules reference page for more information. */ + "module": "esnext", + /* Specify the module resolution strategy: 'node' (Node.js) or 'classic' (used in TypeScript before the release of 1.6). */ + "moduleResolution": "node", + /* Do not emit compiler output files like JavaScript source code, source-maps or declarations. */ + "noEmit": true, + /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ + "resolveJsonModule": true, + /* The longest common path of all non-declaration input files. */ + "rootDir": "./", + /* Enables the generation of sourcemap files. These files allow debuggers and other tools to display the original TypeScript source code when actually working with the emitted JavaScript files. */ + "sourceMap": false, + /* Skip running typescript on declaration files. This option is needed due to a known bug in react-ace */ + "skipLibCheck": true, + /* The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. */ + "strict": true, + "forceConsistentCasingInFileNames": true, + /* The target setting changes which JS features are downleveled and which are left intact. */ + "target": "es6", + /* In some cases where no type annotations are present, TypeScript will fall back to a type of any for a variable when it cannot infer the type. */ + /* *** TEMPORARILY ADDED UNTIL ALL MODULES HAVE BEEN REFACTORED!!!!!!!!!!! *** */ + "noImplicitAny": false, + "verbatimModuleSyntax": true, + "paths": { + "js-slang/context": ["./typings/js-slang/context.d.ts"] + }, + "ignoreDeprecations": "5.0" + }, + /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ + "include": ["."], + /* Specifies an array of filenames or patterns that should be skipped when resolving include. */ + "exclude": ["jest.config.js"] +} diff --git a/lib/buildtools/src/build/docs/__mocks__/docsUtils.ts b/lib/buildtools/src/build/docs/__mocks__/docsUtils.ts new file mode 100644 index 0000000000..8532cf4da5 --- /dev/null +++ b/lib/buildtools/src/build/docs/__mocks__/docsUtils.ts @@ -0,0 +1,16 @@ +export const initTypedoc = jest.fn(() => { + const proj = { + getChildByName: () => ({ + children: [] + }), + path: '' + } as any; + + const app = { + convert: jest.fn() + .mockReturnValue(proj), + generateDocs: jest.fn(() => Promise.resolve()) + }; + + return Promise.resolve([proj, app]); +}); diff --git a/lib/buildtools/src/build/docs/__tests__/building.test.ts b/lib/buildtools/src/build/docs/__tests__/building.test.ts new file mode 100644 index 0000000000..846731b603 --- /dev/null +++ b/lib/buildtools/src/build/docs/__tests__/building.test.ts @@ -0,0 +1,76 @@ +import fs from 'fs/promises'; +import type { MockedFunction } from 'jest-mock'; +import { initTypedoc, initTypedocForSingleBundle } from '../docsUtils'; +import * as json from '../json'; +import type { ProjectReflection } from 'typedoc'; + +jest.mock('../json', () => ({ + buildJson: jest.fn() +})) + +const mockedWriteFile = fs.writeFile as MockedFunction; + +const test0Obj = { + test_function: { + kind: 'function', + params: [['_param0', 'string']], + description: '

    This is just some test function

    ', + retType: 'number' + } +}; + +const test1Obj = { + test_variable: { + kind: 'variable', + type: 'number', + description: '

    Test variable

    ' + } +}; + +function matchObj(raw: string, expected: T) { + expect(JSON.parse(raw)).toMatchObject(expected); +} + +const testMocksDir = `${__dirname}/../../__test_mocks__` + +test('Building the json documentation for a single bundle', async () => { + const project = await initTypedocForSingleBundle({ + name: 'test0', + manifest: {}, + directory: `${testMocksDir}/bundles/test0`, + entryPoint: `${testMocksDir}/bundles/test0/index.ts`, + }); + await json.buildJson('test0', project, 'build'); + + expect(json.buildJson).toHaveBeenCalledTimes(1); + const [[, test0str]] = mockedWriteFile.mock.calls; + matchObj(test0str as string, test0Obj); +}); + +test('initTypedoc for muiltiple bundles', async () => { + const [project,] = await initTypedoc({ + test0: { + name: 'test0', + manifest: {}, + directory: `${testMocksDir}/bundles/test0`, + entryPoint: `${testMocksDir}/bundles/test0/index.ts`, + }, + test1: { + name: 'test1', + manifest: {}, + directory: `${testMocksDir}/bundles/test1`, + entryPoint: `${testMocksDir}/bundles/test1/index.ts`, + } + }) + + const test0Reflection = project.getChildByName('test0') as ProjectReflection + await json.buildJson('test0', test0Reflection, 'build') + + const test1Reflection = project.getChildByName('test1') as ProjectReflection + await json.buildJson('test1', test1Reflection, 'build') + + expect(json.buildJson).toHaveBeenCalledTimes(2); + const [[, test0str], [, test1str]] = mockedWriteFile.mock.calls; + matchObj(test0str as string, test0Obj); + matchObj(test1str as string, test1Obj); +}) diff --git a/lib/buildtools/src/build/docs/docsUtils.ts b/lib/buildtools/src/build/docs/docsUtils.ts new file mode 100644 index 0000000000..75630a5195 --- /dev/null +++ b/lib/buildtools/src/build/docs/docsUtils.ts @@ -0,0 +1,63 @@ +import * as td from 'typedoc'; +import type { ResolvedBundle } from '../manifest'; +import { getBundlesDir } from '../../utils'; +import pathlib from 'path'; + +const commonTypedocOptions: td.Configuration.TypeDocOptions = { + categorizeByGroup: true, + disableSources: true, + excludeInternal: true, + logLevel: 'Error', + skipErrorChecking: true, +} + + +/** + * Initialize typedoc for a single bundle. Useful for building the JSON + * documentation for a single bundle without having to process every other + * bundle + */ +export async function initTypedocForSingleBundle(bundle: ResolvedBundle) { + const app = await td.Application.bootstrap({ + ...commonTypedocOptions, + name: bundle.name, + entryPoints: [bundle.entryPoint], + tsconfig: `${bundle.directory}/tsconfig.json`, + }); + + const reflection = await app.convert() + if (!reflection) { + throw new Error(`Failed to generate reflection for ${bundle.name}, check that the bundle has no type errors!`) + } + + return reflection +} + +/** + * Initialize typedoc for all bundles at once. Useful for building HTML documentation, + * but can also be used to build the JSON documentation of every single bundle. + * + * More efficient than having to initialize typedoc separately each time + */ +export async function initTypedoc(manifest: Record) { + const entryPoints = Object.values(manifest).map(({ entryPoint }) => entryPoint) + const bundlesDir = await getBundlesDir() + const tsconfigPath = pathlib.resolve(bundlesDir, 'tsconfig.json') + + const app = await td.Application.bootstrap({ + ...commonTypedocOptions, + alwaysCreateEntryPointModule: true, + entryPoints, + name: 'Source Academy Modules', + readme: `${__dirname}/docsreadme.md`, + tsconfig: tsconfigPath, + }); + + const project = await app.convert(); + if (!project) { + throw new Error('Failed to initialize typedoc - Make sure to check that the source files have no compilation errors!'); + } + return [project, app] as [td.ProjectReflection, td.Application]; +} + +export type TypedocInitResult = Awaited>; diff --git a/lib/buildtools/src/build/docs/drawdown.ts b/lib/buildtools/src/build/docs/drawdown.ts new file mode 100644 index 0000000000..8fe5d5aaa8 --- /dev/null +++ b/lib/buildtools/src/build/docs/drawdown.ts @@ -0,0 +1,190 @@ +/* eslint-disable*/ +/** + * Module to convert from markdown into HTML + * drawdown.js + * (c) Adam Leggett + */ + +export default (src: string): string => { + var rx_lt = //g; + var rx_space = /\t|\r|\uf8ff/g; + var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g; + var rx_hr = /^([*\-=_] *){3,}$/gm; + var rx_blockquote = /\n *> *([^]*?)(?=(\n|$){2})/g; + var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g; + var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g; + var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+)|`)(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g; + var rx_code = /\n((```|~~~).*\n?([^]*?)\n?\2|(( {4}.*?\n)+))/g; + var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g; + var rx_table = /\n(( *\|.*?\| *\n)+)/g; + var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/; + var rx_row = /.*\n/g; + var rx_cell = /\||(.*?[^\\])\|/g; + var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g; + var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g; + var rx_stash = /-\d+\uf8ff/g; + + function replace(rex, fn) { + src = src.replace(rex, fn); + } + + function element(tag, content) { + return '<' + tag + '>' + content + ''; + } + + function blockquote(src) { + return src.replace(rx_blockquote, function (all, content) { + return element( + 'blockquote', + blockquote(highlight(content.replace(/^ *> */gm, ''))) + ); + }); + } + + function list(src) { + return src.replace(rx_list, function (all, ind, ol, num, low, content) { + var entry = element( + 'li', + highlight( + content + .split( + RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g') + ) + .map(list) + .join('
  • ') + ) + ); + + return ( + '\n' + + (ol + ? '
      ' + : parseInt(ol, 36) - + 9 + + '" style="list-style-type:' + + (low ? 'low' : 'upp') + + 'er-alpha">') + + entry + + '
    ' + : element('ul', entry)) + ); + }); + } + + function highlight(src) { + return src.replace( + rx_highlight, + function (all, _, p1, emp, sub, sup, small, big, p2, content) { + return ( + _ + + element( + emp + ? p2 + ? 'strong' + : 'em' + : sub + ? p2 + ? 's' + : 'sub' + : sup + ? 'sup' + : small + ? 'small' + : big + ? 'big' + : 'code', + highlight(content) + ) + ); + } + ); + } + + function unesc(str) { + return str.replace(rx_escape, '$1'); + } + + var stash = []; + var si = 0; + + src = '\n' + src + '\n'; + + replace(rx_lt, '<'); + replace(rx_gt, '>'); + replace(rx_space, ' '); + + // blockquote + src = blockquote(src); + + // horizontal rule + replace(rx_hr, '
    '); + + // list + src = list(src); + replace(rx_listjoin, ''); + + // code + replace(rx_code, function (all, p1, p2, p3, p4) { + stash[--si] = element( + 'pre', + element('code', p3 || p4.replace(/^ {4}/gm, '')) + ); + return si + '\uf8ff'; + }); + + // link or image + replace(rx_link, function (all, p1, p2, p3, p4, p5, p6) { + stash[--si] = p4 + ? p2 + ? '' + p3 + '' + : '' + unesc(highlight(p3)) + '' + : p6; + return si + '\uf8ff'; + }); + + // table + replace(rx_table, function (all, table) { + var sep = table.match(rx_thead)[1]; + return ( + '\n' + + element( + 'table', + table.replace(rx_row, function (row, ri) { + return row == sep + ? '' + : element( + 'tr', + row.replace(rx_cell, function (all, cell, ci) { + return ci + ? element( + sep && !ri ? 'th' : 'td', + unesc(highlight(cell || '')) + ) + : ''; + }) + ); + }) + ) + ); + }); + + // heading + replace(rx_heading, function (all, _, p1, p2) { + return _ + element('h' + p1.length, unesc(highlight(p2))); + }); + + // paragraph + replace(rx_para, function (all, content) { + return element('p', unesc(highlight(content))); + }); + + // stash + replace(rx_stash, function (all) { + return stash[parseInt(all)]; + }); + + return src.trim(); +}; \ No newline at end of file diff --git a/lib/buildtools/src/build/docs/index.ts b/lib/buildtools/src/build/docs/index.ts new file mode 100644 index 0000000000..e82560bb2a --- /dev/null +++ b/lib/buildtools/src/build/docs/index.ts @@ -0,0 +1,39 @@ +import type { ProjectReflection } from 'typedoc' +import type { ResolvedBundle } from '../manifest' +import { initTypedoc } from './docsUtils' +import { buildJson } from './json' +import chalk from 'chalk' + +/** + * A more efficient version of documentation building to avoid + * having to instantiate typedoc multiple times + */ +export async function buildDocs(manifest: Record, outDir: string) { + const [project, app] = await initTypedoc(manifest) + + const validModules = new Set(Object.keys(manifest)) + + const unknownChildren = project.children.filter(({ name }) => !validModules.has(name)) + if (unknownChildren.length > 0) { + console.warn(`${chalk.yellow('[warning]')} Unknown modules present in html output`) + } + + const jsonPromises = Object.keys(manifest) + .map(async bundleName => { + const reflection = project.getChildByName(bundleName) + if (!reflection) { + console.warn(`${chalk.yellow('[warning]')} Did not find documentation for ${bundleName}. Did you forget a @module tag?`) + return; + } + + await buildJson(bundleName, reflection as ProjectReflection, outDir) + }) + + await Promise.all([ + ...jsonPromises, + app.generateDocs(project, `${outDir}/documentation`) + ]) +} + + +export { buildJson } \ No newline at end of file diff --git a/lib/buildtools/src/build/docs/json.ts b/lib/buildtools/src/build/docs/json.ts new file mode 100644 index 0000000000..7d4b2323e4 --- /dev/null +++ b/lib/buildtools/src/build/docs/json.ts @@ -0,0 +1,73 @@ +import fs from 'fs/promises'; +import * as td from 'typedoc'; +import drawdown from './drawdown'; + +const typeToName = (type?: td.SomeType) => type.stringify(td.TypeContext.none); + +const parsers = { + [td.ReflectionKind.Function](obj) { + // Functions should have only 1 signature + if (obj.signatures.length > 1) { + console.warn(`${obj.name} has more than 1 signature; only using the first one`) + } + + const [signature] = obj.signatures; + + let description: string; + if (signature.comment) { + description = drawdown(signature.comment.summary.map(({ text }) => text) + .join('')); + } else { + description = 'No description available'; + } + + const params = signature.parameters.map(({ type, name }) => [name, typeToName(type)] as [string, string]); + + return { + kind: 'function', + name: obj.name, + description, + params, + retType: typeToName(signature.type) + }; + }, + [td.ReflectionKind.Variable](obj) { + let description: string; + if (obj.comment) { + description = drawdown(obj.comment.summary.map(({ text }) => text) + .join('')); + } else { + description = 'No description available'; + } + + return { + kind: 'variable', + name: obj.name, + description, + type: typeToName(obj.type) + }; + } +} satisfies Partial any>>; + +/** + * Build the JSON documentation for a single bundle using the output from + * typedoc + */ +export async function buildJson(bundleName: string, reflection: td.ProjectReflection, outDir: string) { + const jsonData = reflection.children.reduce((res, element) => { + // Ignore 'type_map' exports if they are present + if (element.name === 'type_map') { + return res; + } + + const parser = parsers[element.kind]; + return { + ...res, + [element.name]: parser + ? parser(element) + : { kind: 'unknown' } + }; + }, {}); + + await fs.writeFile(`${outDir}/json/${bundleName}.json`, JSON.stringify(jsonData, null, 2)); +} diff --git a/lib/buildtools/src/build/manifest.ts b/lib/buildtools/src/build/manifest.ts new file mode 100644 index 0000000000..a23dd0ab65 --- /dev/null +++ b/lib/buildtools/src/build/manifest.ts @@ -0,0 +1,133 @@ +import fs from 'fs/promises'; +import pathlib from 'path'; +import { validate } from 'jsonschema'; +import manifestSchema from './modules/manifest.schema.json' with { type: 'json' }; +import { getBundleEntryPoint } from './modules/bundle'; + +export interface BundleManifest { + version?: string + tabs?: string[] +} + +export type ModulesManifest = { + [name: string]: BundleManifest +} + +/** + * Checks that the given bundle manifest was correctly specified + */ +export async function getBundleManifest(manifestFile: string, tabCheck?: boolean): Promise { + let rawManifest: string + + try { + rawManifest = await fs.readFile(manifestFile, 'utf-8') + } catch (error) { + if (error.code === 'ENOENT') { + return undefined + } + throw error + } + + const data = JSON.parse(rawManifest) as BundleManifest; + const { valid, errors } = validate(data, manifestSchema); + + if (!valid) throw errors; + + // Make sure that all the tabs specified exist + if (tabCheck && data.tabs) { + await Promise.all(data.tabs.map(async tabName => { + try { + await fs.access(`./src/tabs/${tabName}`, fs.constants.R_OK); + } catch { + throw new Error(`Failed to find tab with name '${tabName}'!`); + } + })); + } + + return data; +} + +/** + * Get all bundle manifests + */ +export async function getBundleManifests(bundlesDir: string): Promise { + const subdirs = await fs.readdir(bundlesDir) + const manifests = await Promise.all(subdirs.map(async fileName => { + const fullPath = pathlib.join(bundlesDir, fileName); + const stats = await fs.stat(fullPath); + if (stats.isDirectory()) { + const manifest = await getBundleManifest(`${fullPath}/manifest.json`); + if (manifest === undefined) return undefined + return [fileName, manifest] as [string, BundleManifest]; + } + + return undefined + })) + + return manifests.reduce((res, entry) => { + if (entry === undefined) return res; + + const [name, manifest] = entry + + return { + ...res, + [name]: manifest + }; + }, {} as Record); +} + +export interface ResolvedBundle { + name: string + manifest: BundleManifest + entryPoint: string + directory: string +} + +export async function resolveSingleBundle(bundleDir: string): Promise { + const fullyResolved = pathlib.resolve(bundleDir) + + const stats = await fs.stat(fullyResolved) + if (!stats.isDirectory()) return undefined + + const manifest = await getBundleManifest(`${fullyResolved}/manifest.json`) + if (!manifest) return undefined + + const bundleName = pathlib.basename(fullyResolved) + const entryPoint = await getBundleEntryPoint(fullyResolved) + + return { + name: bundleName, + manifest, + entryPoint, + directory: fullyResolved + } +} + +export async function resolveAllBundles(bundlesDir: string) { + const subdirs = await fs.readdir(bundlesDir) + const manifests = await Promise.all(subdirs.map(subdir => { + const fullPath = pathlib.join(bundlesDir, subdir) + return resolveSingleBundle(fullPath) + })) + + return manifests.reduce((res, entry) => { + if (entry === undefined) return res + + return { + ...res, + [entry.name]: entry + } + }, {} as Record) +} +export async function resolvePaths(...paths: string[]) { + for (const path of paths) { + try { + await fs.access(path, fs.constants.R_OK); + return path + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } + } + + return undefined; +} diff --git a/lib/buildtools/src/build/modules/__tests__/bundle.test.ts b/lib/buildtools/src/build/modules/__tests__/bundle.test.ts new file mode 100644 index 0000000000..b26dc00322 --- /dev/null +++ b/lib/buildtools/src/build/modules/__tests__/bundle.test.ts @@ -0,0 +1,48 @@ +import { buildBundle } from "../bundle" +import fs from 'fs/promises'; + +const testMocksDir = `${__dirname}/../../__test_mocks__` + +const written = [] +jest.spyOn(fs, 'open').mockResolvedValue({ + createWriteStream: () => ({ + write: data => { + written.push(data) + } + }), + close: jest.fn() +} as any) + +test('esbuild transpilation', async () => { + await buildBundle({ + manifest: {}, + name: 'test0', + directory: `${testMocksDir}/bundles/test0`, + entryPoint: `${testMocksDir}/bundles/test0/index.ts`, + }, 'build') + + const data = written.join('') + + // Trim the export default + const trimmed = (data as string).slice('export default'.length) + const provider = jest.fn((p: string) => { + if (p === 'js-slang/context') { + return { + moduleContexts: { + test0: { + state: { + data: 'foo' + } + } + } + } + } + + throw new Error(`Dynamic require of ${p} is not supported!`) + }) + + const bundle = eval(trimmed)(provider) + + expect(provider).toHaveBeenCalledTimes(1) + expect(bundle.test_function2()).toEqual('foo') +}) \ No newline at end of file diff --git a/lib/buildtools/src/build/modules/bundle.ts b/lib/buildtools/src/build/modules/bundle.ts new file mode 100644 index 0000000000..e660fb1c63 --- /dev/null +++ b/lib/buildtools/src/build/modules/bundle.ts @@ -0,0 +1,31 @@ +import fs from 'fs/promises'; +import { build as esbuild } from 'esbuild'; +import { commonEsbuildOptions, outputBundleOrTab } from './commons'; +import type { ResolvedBundle } from '../manifest'; + +export async function getBundleEntryPoint(bundleDir: string) { + let bundlePath = `${bundleDir}/src/index.ts` + + try { + await fs.access(bundlePath, fs.constants.R_OK) + return bundlePath + } catch (error) { + bundlePath = `${bundleDir}/index.ts` + await fs.access(bundlePath, fs.constants.R_OK) + return bundlePath + } +} + +/** + * Build the given resolved bundle + */ +export async function buildBundle(bundle: ResolvedBundle, outDir: string) { + const { outputFiles: [result] } = await esbuild({ + ...commonEsbuildOptions, + entryPoints: [bundle.entryPoint], + tsconfig: `${bundle.directory}/tsconfig.json`, + outfile: `/bundle/${bundle.name}`, + }); + + await outputBundleOrTab(result, bundle.name, 'bundle', outDir); +} diff --git a/lib/buildtools/src/build/modules/commons.ts b/lib/buildtools/src/build/modules/commons.ts new file mode 100644 index 0000000000..2ad41fcf7e --- /dev/null +++ b/lib/buildtools/src/build/modules/commons.ts @@ -0,0 +1,62 @@ +import fs from 'fs/promises'; +import { parse } from 'acorn'; +import { generate } from 'astring'; +import type { BuildOptions as ESBuildOptions, OutputFile } from 'esbuild'; +import type es from 'estree'; + +export const commonEsbuildOptions: ESBuildOptions = { + bundle: true, + format: 'iife', + define: { + process: JSON.stringify({ + env: { + NODE_ENV: 'production' + }, + }), + global: 'globalThis' + }, + external: ['js-slang*'], + globalName: 'module', + platform: 'browser', + target: 'es6', + write: false +}; + +export async function outputBundleOrTab({ text }: OutputFile, name: string, type: 'bundle' | 'tab', outDir: string) { + const parsed = parse(text, { ecmaVersion: 6 }) as es.Program; + + // Account for 'use strict'; directives + let declStatement: es.VariableDeclaration; + if (parsed.body[0].type === 'VariableDeclaration') { + declStatement = parsed.body[0]; + } else { + declStatement = parsed.body[1] as es.VariableDeclaration; + } + + const { init: callExpression } = declStatement.declarations[0]; + if (callExpression.type !== 'CallExpression') { + throw new Error(`Expected a CallExpression, got ${callExpression.type}`); + } + + const moduleCode = callExpression.callee; + + if (moduleCode.type !== 'FunctionExpression' && moduleCode.type !== 'ArrowFunctionExpression') { + throw new Error(`Expected a function, got ${moduleCode.type}`); + } + + const output: es.ExportDefaultDeclaration = { + type: 'ExportDefaultDeclaration', + declaration: { + ...moduleCode, + params: [{ + type: 'Identifier', + name: 'require' + }] + } + }; + + await fs.mkdir(`${outDir}/${type}`, { recursive: true }) + const file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); + const writeStream = file.createWriteStream(); + generate(output, { output: writeStream }); +} diff --git a/lib/buildtools/src/build/modules/index.ts b/lib/buildtools/src/build/modules/index.ts new file mode 100644 index 0000000000..475182586a --- /dev/null +++ b/lib/buildtools/src/build/modules/index.ts @@ -0,0 +1,27 @@ +import fs from 'fs/promises'; +import { buildBundle } from './bundle'; +import type { ResolvedBundle } from '../manifest'; + +/** + * Writes the module manifest to the output directory + */ +export async function writeManifest(manifests: Record, outDir: string) { + const toWrite = Object.entries(manifests).reduce((res, [key, { manifest }]) => ({ + ...res, + [key]: manifest + }), {}) + await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(toWrite, null, 2)) +} + +/** + * Search the given directory for valid bundles, then build and write + * them to the given output directory, + */ +export async function buildBundles(manifests: Record, outDir: string) { + await Promise.all(Object.values(manifests).map(async bundle => { + await buildBundle(bundle, outDir); + })) +} + +export { buildBundle }; +export { buildTab, buildTabs } from './tab'; \ No newline at end of file diff --git a/lib/buildtools/src/build/modules/manifest.schema.json b/lib/buildtools/src/build/modules/manifest.schema.json new file mode 100644 index 0000000000..8a3394d479 --- /dev/null +++ b/lib/buildtools/src/build/modules/manifest.schema.json @@ -0,0 +1,23 @@ +{ + "schema": { + "type": "object", + "properties": { + "tabs": { + "description": "Tabs that will be loaded with this bundle", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "type": "string", + "description": "Version of your bundle" + }, + "requires": { + "enum": [1, 2, 3, 4], + "description": "Minimum Source version required to run this bundle" + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/lib/buildtools/src/build/modules/tab.ts b/lib/buildtools/src/build/modules/tab.ts new file mode 100644 index 0000000000..367b08e900 --- /dev/null +++ b/lib/buildtools/src/build/modules/tab.ts @@ -0,0 +1,96 @@ +import pathlib from 'path' +import { build as esbuild, type Plugin as ESBuildPlugin } from 'esbuild'; +import { commonEsbuildOptions, outputBundleOrTab } from './commons'; +import { resolveAllBundles, resolvePaths, type ResolvedBundle } from '../manifest'; + +const tabContextPlugin: ESBuildPlugin = { + name: 'Tab Context', + setup(build) { + build.onResolve({ filter: /^js-slang\/context/ }, () => ({ + errors: [{ + text: 'If you see this message, it means that your tab code is importing js-slang/context directly or indirectly. Do not do this' + }] + })); + } +}; + +interface ResolvedTab { + directory: string + entryPoint: string + name: string +} + +export async function resolveSingleTab(tabDir: string): Promise { + const fullyResolved = pathlib.resolve(tabDir) + + const tabPath = await resolvePaths( + `${fullyResolved}/src/index.tsx`, + `${fullyResolved}/index.tsx` + ) + + if (tabPath === undefined) { + throw new Error(`No tab found at ${fullyResolved}!`) + } + + return { + directory: fullyResolved, + entryPoint: tabPath, + name: pathlib.basename(fullyResolved) + } +} + +export async function resolveAllTabs(bundlesDir: string, tabsDir: string) { + const bundlesManifest = await resolveAllBundles(bundlesDir) + + const rawTabs = await Promise.all( + Object.values(bundlesManifest).map(async ({ manifest }) => { + if (manifest.tabs) { + return Promise.all(manifest.tabs.map(async tabName => { + const resolvedTab = await resolveSingleTab(pathlib.join(tabsDir, tabName)) + return resolvedTab + })) + } + + return [] + })) + + return rawTabs.reduce((res, tabs) => { + return tabs.reduce((res, tab) => ({ + ...res, + [tab.name]: tab + }), res) + }, {} as Record) +} + +/** + * Build a tab at the given directory + */ +export async function buildTab(tabDir: string, outDir: string) { + const tab = await resolveSingleTab(tabDir) + + const { outputFiles: [result]} = await esbuild({ + ...commonEsbuildOptions, + entryPoints: [tab.entryPoint], + external: [ + ...commonEsbuildOptions.external, + 'react', + 'react-ace', + 'react-dom', + 'react/jsx-runtime', + '@blueprintjs/*' + ], + tsconfig: `${tab.directory}/tsconfig.json`, + plugins: [tabContextPlugin], + }); + await outputBundleOrTab(result, tab.name, 'tab', outDir); +} + +export async function buildTabs(manifest: Record, outDir: string) { + await Promise.all(Object.values(manifest).map(async ({ manifest: { tabs } }) => { + if (tabs) { + await Promise.all(tabs.map(async tabName => { + await buildTab(tabName, outDir) + })) + } + })) +} diff --git a/lib/buildtools/src/commands/__tests__/main.test.ts b/lib/buildtools/src/commands/__tests__/main.test.ts new file mode 100644 index 0000000000..4410daaa05 --- /dev/null +++ b/lib/buildtools/src/commands/__tests__/main.test.ts @@ -0,0 +1,9 @@ +import { getMainCommand } from "../main" +describe('Make sure that all subcommands can execute', () => { + const mainCommand = getMainCommand() + mainCommand.commands.map(command => test(command.name(), () => { + return expect(command.parseAsync(['--help'], { from: 'user' })) + .rejects + .toThrow() + })) +}) \ No newline at end of file diff --git a/lib/buildtools/src/commands/__tests__/template.test.ts b/lib/buildtools/src/commands/__tests__/template.test.ts new file mode 100644 index 0000000000..97f73b5b04 --- /dev/null +++ b/lib/buildtools/src/commands/__tests__/template.test.ts @@ -0,0 +1,172 @@ +import fs from 'fs/promises'; +import type pathlib from 'path' +import type { MockedFunction } from 'jest-mock'; + +import getTemplateCommand from '../template'; +import { askQuestion } from '../../templates/print'; +import * as manifest from '../../build/manifest' + +jest.mock('path', () => { + const actualPath: typeof pathlib = jest.requireActual('path') + return { + ...actualPath, + resolve: (...args: string[]) => { + return actualPath.resolve('/', ...args) + } + } +}) + +jest.mock('../../templates/print', () => ({ + ...jest.requireActual('../../templates/print'), + askQuestion: jest.fn(), + error(x: string) { + // The command has a catch-all for errors, + // so we rethrow the error to observe the value + throw new Error(x); + }, + // Because the functions run in perpetual while loops + // We need to throw an error to observe what value warn + // was called with + warn(x: string) { + throw new Error(x); + } +})); + +jest.spyOn(manifest, 'getBundleManifests').mockResolvedValue({ + test0: { tabs: ['tab0'] }, + test1: {}, +}) + +const asMock = any>(func: T) => func as MockedFunction; + +const mockedAskQuestion = asMock(askQuestion); + +function runCommand(...args: string[]) { + return getTemplateCommand() + .parseAsync(args, { from: 'user' }); +} + +function expectCall any>( + func: T, + ...expected: Parameters[]) { + const mocked = asMock(func); + + expect(func) + .toHaveBeenCalledTimes(expected.length); + + mocked.mock.calls.forEach((actual, i) => { + expect(actual) + .toEqual(expected[i]); + }); +} + +async function expectCommandFailure(snapshot: string) { + await expect(runCommand()) + .rejects + // eslint-disable-next-line jest/no-interpolation-in-snapshots + .toMatchInlineSnapshot(`[Error: ERROR: ${snapshot}]`); + + expect(fs.writeFile).not.toHaveBeenCalled(); + expect(fs.copyFile).not.toHaveBeenCalled(); + expect(fs.mkdir).not.toHaveBeenCalled(); +} + +describe('Test adding new module', () => { + beforeEach(() => { + mockedAskQuestion.mockResolvedValueOnce('module'); + }); + + it('should require camel case for module names', async () => { + mockedAskQuestion.mockResolvedValueOnce('camelCase'); + await expectCommandFailure('Module names must be in snake case. (eg. binary_tree)'); + }); + + it('should check for existing modules', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + await expectCommandFailure('A module with the same name already exists.'); + }); + + test('successfully adding a new module', async () => { + mockedAskQuestion.mockResolvedValueOnce('new_module'); + await runCommand(); + + expectCall( + fs.mkdir, + ['src/bundles/new_module', { recursive: true }] + ); + + expectCall( + fs.cp, + [ + './src/', + 'src/bundles/new_module/index.ts' + ] + ); + + const oldManifest = await retrieveManifest('modules.json'); + const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; + expect(manifestPath) + .toEqual('modules.json'); + + expect(JSON.parse(newManifest as string)) + .toMatchObject({ + ...oldManifest, + new_module: { tabs: [] } + }); + }); +}); + +describe('Test adding new tab', () => { + beforeEach(() => { + mockedAskQuestion.mockResolvedValueOnce('tab'); + }); + + it('should require pascal case for tab names', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + mockedAskQuestion.mockResolvedValueOnce('unknown_tab'); + await expectCommandFailure('Tab names must be in pascal case. (eg. BinaryTree)'); + }); + + it('should check if the given tab already exists', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + mockedAskQuestion.mockResolvedValueOnce('tab0'); + await expectCommandFailure('A tab with the same name already exists.'); + }); + + it('should check if the given module exists', async () => { + mockedAskQuestion.mockResolvedValueOnce('unknown_module'); + await expectCommandFailure('Module unknown_module does not exist.'); + }); + + test('Successfully adding new tab', async () => { + mockedAskQuestion.mockResolvedValueOnce('test0'); + mockedAskQuestion.mockResolvedValueOnce('TabNew'); + + await runCommand(); + + expectCall( + fs.mkdir, + ['src/tabs/TabNew', { recursive: true }] + ); + + expectCall( + fs.copyFile, + [ + './scripts/src/templates/templates/__tab__.tsx', + 'src/tabs/TabNew/index.tsx' + ] + ); + + const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; + expect(manifestPath) + .toEqual('modules.json'); + + expect(JSON.parse(newManifest as string)) + .toMatchObject({ + ...oldManifest, + test0: { + tabs: ['tab0', 'TabNew'] + } + }); + }); +}); diff --git a/lib/buildtools/src/commands/__tests__/testing.test.ts b/lib/buildtools/src/commands/__tests__/testing.test.ts new file mode 100644 index 0000000000..c2c8a8f2a8 --- /dev/null +++ b/lib/buildtools/src/commands/__tests__/testing.test.ts @@ -0,0 +1,32 @@ +import type { MockedFunction } from 'jest-mock'; +import getTestCommand from '../testing'; +import { runJest } from '../../testing/runner'; + +jest.mock('../../testing/runner', () => ({ + runJest: jest.fn() +})) + +const runCommand = (...args: string[]) => getTestCommand() + .parseAsync(args, { from: 'user' }); +const mockRunJest = runJest as MockedFunction; + +test('Check that the test command properly passes options to jest', async () => { + await runCommand('-u', '0', '-w', './src/folder'); + + expect(runJest).toHaveBeenCalledTimes(1) + + const [[args, patterns]] = mockRunJest.mock.calls; + expect(args) + .toEqual(['-u', '0']); + expect(patterns) + .toEqual(['-w', './src/folder']); +}); + +test('Check that the test command handles windows paths as posix paths', async () => { + await runCommand('.\\src\\folder'); + expect(runJest).toHaveBeenCalledTimes(1) + + const [call] = mockRunJest.mock.calls; + expect(call[0]) + .toEqual(['./src/folder']); +}); diff --git a/lib/buildtools/src/commands/build.ts b/lib/buildtools/src/commands/build.ts new file mode 100644 index 0000000000..23c7ce312b --- /dev/null +++ b/lib/buildtools/src/commands/build.ts @@ -0,0 +1,68 @@ +import fs from 'fs/promises' +import { Command } from "@commander-js/extra-typings"; +import { buildBundle, buildBundles, buildTab, buildTabs, writeManifest } from "../build/modules"; +import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; +import { initTypedocForSingleBundle } from '../build/docs/docsUtils'; +import { buildDocs, buildJson } from '../build/docs'; +import { getBundlesDir, getOutDir } from '../utils'; + +const outDir = await getOutDir() +const bundlesDir = await getBundlesDir() + +export const getBuildBundleCommand = () => new Command('bundle') + .argument('', 'Directory in which the bundle\'s source files are located') + .action(async bundleDir => { + const bundle = await resolveSingleBundle(bundleDir) + if (!bundle) { + throw new Error(`No bundle found at ${bundleDir}!`) + } + await buildBundle(bundle, outDir) + }) + +export const getBuildTabCommand = () => new Command('tab') + .argument('', 'Directory in which the tab\'s source files are located') + .action(tabDir => buildTab(tabDir, outDir)) + +export const getBuildJsonCommand = () => new Command('json') + .argument('', 'Directory in which the bundle\'s source files are located') + .action(async bundleDir => { + const bundle = await resolveSingleBundle(bundleDir) + const reflection = await initTypedocForSingleBundle(bundle) + await fs.mkdir(`${outDir}/json`, { recursive: true }) + await buildJson(bundle.name, reflection, outDir) + }) + +export const getBuildDocsCommand = () => new Command('docs') + .action(async () => { + const manifest = await resolveAllBundles(bundlesDir) + await buildDocs(manifest, outDir) + }) + +export const getBuildManifestCommand = () => new Command('manifest') + .action(async () => { + const manifest = await resolveAllBundles(bundlesDir) + await fs.mkdir(outDir, { recursive: true }) + await writeManifest(manifest, outDir) + }) + +export const getBuildAllCommand = () => new Command('all') +.description('Build all bundles, tabs and documentation') + .action(async () => { + const manifest = await resolveAllBundles(bundlesDir) + await fs.mkdir(outDir, { recursive: true }) + + await Promise.all([ + writeManifest(manifest, outDir), + buildDocs(manifest, outDir), + buildBundles(manifest, outDir), + buildTabs(manifest, outDir) + ]) + }) + +export const getBuildCommand = () => new Command('build') + .addCommand(getBuildAllCommand()) + .addCommand(getBuildBundleCommand()) + .addCommand(getBuildTabCommand()) + .addCommand(getBuildJsonCommand()) + .addCommand(getBuildDocsCommand()) + .addCommand(getBuildManifestCommand()) diff --git a/lib/buildtools/src/commands/index.ts b/lib/buildtools/src/commands/index.ts new file mode 100644 index 0000000000..c0b3b96360 --- /dev/null +++ b/lib/buildtools/src/commands/index.ts @@ -0,0 +1,3 @@ +import { getMainCommand } from "./main"; + +await getMainCommand().parseAsync() \ No newline at end of file diff --git a/lib/buildtools/src/commands/list.ts b/lib/buildtools/src/commands/list.ts new file mode 100644 index 0000000000..1308745537 --- /dev/null +++ b/lib/buildtools/src/commands/list.ts @@ -0,0 +1,56 @@ +import { Command } from "@commander-js/extra-typings"; +import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; +import { getBundlesDir, getTabsDir } from "../utils"; +import chalk from "chalk"; +import { resolveAllTabs, resolveSingleTab } from "../build/modules/tab"; + +export const getListBundlesCommand = () => new Command('bundle') + .description('Lists all the bundles present or the information for a specific bundle in a given directory') + .argument('[directory]') + .action(async directory => { + if (directory === undefined) { + const bundlesDir = await getBundlesDir() + const manifest = await resolveAllBundles(bundlesDir) + const bundleNames = Object.keys(manifest) + + if (bundleNames.length > 0 ) { + const bundlesStr = bundleNames.map((each, i) => `${i+1}. ${each}`).join('\n') + console.log(`${chalk.magentaBright(`Detected ${bundleNames.length} bundles in ${bundlesDir}:`)}\n${bundlesStr}`) + } else { + console.log(chalk.redBright(`No bundles in ${bundlesDir}`)) + } + } else { + const manifest = await resolveSingleBundle(directory) + console.log(chalk.magentaBright(`Bundle '${manifest.name}' found in ${directory}`)) + } + }) + +export const getListTabsCommand = () => new Command('tabs') + .description('Lists all the tabs present or the information for a specific tab in a given directory') + .argument('[directory]') + .action(async directory => { + if (directory === undefined) { + const [bundlesDir, tabsDir] = await Promise.all([ + getBundlesDir(), + getTabsDir() + ]) + + const tabsManifest = await resolveAllTabs(bundlesDir, tabsDir) + const tabNames = Object.keys(tabsManifest) + + if (tabNames.length > 0) { + const tabsStr = tabNames.map((each, i) => `${i+1}. ${each}`).join('\n') + console.log(`${chalk.magentaBright(`Detected ${tabNames.length} bundles in ${tabsDir}:`)}\n${tabsStr}`) + } else { + console.log(chalk.redBright(`No tabs in ${tabsDir}`)) + } + } else { + const resolvedTab = await resolveSingleTab(directory) + console.log(chalk.magentaBright(`Tab '${resolvedTab.name}' found in ${directory}`)) + } + }) + + +export const getListCommand = () => new Command('list') + .addCommand(getListBundlesCommand()) + .addCommand(getListTabsCommand()) \ No newline at end of file diff --git a/lib/buildtools/src/commands/main.ts b/lib/buildtools/src/commands/main.ts new file mode 100644 index 0000000000..d554ec2439 --- /dev/null +++ b/lib/buildtools/src/commands/main.ts @@ -0,0 +1,14 @@ +import { Command } from "@commander-js/extra-typings"; +import { getBuildCommand } from "./build"; +import getTemplateCommand from "./template"; +import getTestCommand from "./testing"; +import { getLintCommand, getTscCommand } from "./prebuild"; +import { getListCommand } from "./list"; + +export const getMainCommand = () => new Command() + .addCommand(getBuildCommand()) + .addCommand(getLintCommand()) + .addCommand(getListCommand()) + .addCommand(getTemplateCommand()) + .addCommand(getTestCommand()) + .addCommand(getTscCommand()) \ No newline at end of file diff --git a/lib/buildtools/src/commands/prebuild.ts b/lib/buildtools/src/commands/prebuild.ts new file mode 100644 index 0000000000..79c6773f5a --- /dev/null +++ b/lib/buildtools/src/commands/prebuild.ts @@ -0,0 +1,49 @@ +import { Command } from "@commander-js/extra-typings"; +import { runEslint } from "../prebuild/lint"; +import type { AwaitedReturn } from "../utils"; +import { runTsc } from "../prebuild/typecheck"; +import pathlib from 'path'; +import chalk from "chalk"; +import ts from 'typescript' + +export const getLintCommand = () => new Command('lint') + .argument('') + .option('--fix') + .action(async (directory, { fix }) => { + const results = await runEslint(directory, fix) + console.log(eslintResultsLogger(results)) + }) + +export const getTscCommand = () => new Command('typecheck') + .argument('') + .action(async directory => { + const results = await runTsc(directory) + console.log(tscResultsLogger(results)) + }) + +function tscResultsLogger(tscResult: AwaitedReturn) { + if (tscResult.severity === 'error' && tscResult.error) { + return `${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}: ${tscResult.error}`)}`; + } + + const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { + getNewLine: () => '\n', + getCurrentDirectory: () => process.cwd(), + getCanonicalFileName: name => pathlib.basename(name) + }); + + if (tscResult.severity === 'error') { + return `${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}}`)}`; + } + return `${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')}`)}`; +} + +function eslintResultsLogger({ formatted, severity }: AwaitedReturn) { + let errStr: string; + + if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); + else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); + else errStr = chalk.greenBright('successfully'); + + return `${chalk.cyanBright(`Linting completed:`)}\n${formatted}`; +} \ No newline at end of file diff --git a/lib/buildtools/src/commands/template.ts b/lib/buildtools/src/commands/template.ts new file mode 100644 index 0000000000..77709b4c07 --- /dev/null +++ b/lib/buildtools/src/commands/template.ts @@ -0,0 +1,41 @@ +import type { Interface } from 'readline/promises'; +import { Command } from '@commander-js/extra-typings'; + +import { addNew as addNewModule } from '../templates/bundle'; +import { error as _error, askQuestion, getRl, info, warn } from '../templates/print'; +import { addNew as addNewTab } from '../templates/tab'; +import { getBundlesDir, getTabsDir } from '../utils'; + +async function askMode(rl: Interface) { + while (true) { + const mode = await askQuestion('What would you like to create? (module/tab)', rl); + if (mode !== 'module' && mode !== 'tab') { + warn("Please answer with only 'module' or 'tab'."); + } else { + return mode; + } + } +} + +export default function getTemplateCommand() { + return new Command('template') + .description('Interactively create a new module or tab') + .action(async () => { + const [bundlesDir, tabsDir] = await Promise.all([ + getBundlesDir(), + getTabsDir() + ]) + + const rl = getRl(); + try { + const mode = await askMode(rl); + if (mode === 'module') await addNewModule(bundlesDir, rl); + else if (mode === 'tab') await addNewTab(bundlesDir, tabsDir, rl); + } catch (error) { + _error(`ERROR: ${error.message}`); + info('Terminating module app...'); + } finally { + rl.close(); + } + }); +} diff --git a/lib/buildtools/src/commands/testing.ts b/lib/buildtools/src/commands/testing.ts new file mode 100644 index 0000000000..4737bfa581 --- /dev/null +++ b/lib/buildtools/src/commands/testing.ts @@ -0,0 +1,16 @@ +import { Command } from '@commander-js/extra-typings'; + +import { runJest } from '../testing/runner'; + +const getTestCommand = () => new Command('test') + .description('Run jest') + .allowUnknownOption() + .argument('[patterns...]') + .action((patterns, args: Record, command) => { + return runJest( + Object.entries(command.args).flat(), + patterns + ); + }); + +export default getTestCommand; diff --git a/lib/buildtools/src/commands/utils.ts b/lib/buildtools/src/commands/utils.ts new file mode 100644 index 0000000000..b460d63170 --- /dev/null +++ b/lib/buildtools/src/commands/utils.ts @@ -0,0 +1,71 @@ +import { Option } from '@commander-js/extra-typings'; + +class OptionNew< + UsageT extends string = '', + PresetT = undefined, + DefaultT = undefined, + CoerceT = undefined, + Mandatory extends boolean = false, + ChoicesT = undefined +> + extends Option { + default(value: T, description?: string): Option { + return super.default(value, description); + } +} + +export const srcDirOption = new OptionNew('--srcDir ', 'Location of the source files') + .default('src'); + +export const outDirOption = new OptionNew('--outDir ', 'Location of output directory') + .default('build'); + +export const manifestOption = new OptionNew('--manifest ', 'Location of manifest') + .default('modules.json'); + +export const lintOption = new OptionNew('--lint', 'Run ESLint'); + +export const lintFixOption = new OptionNew('--fix', 'Fix automatically fixable linting errors') + .implies({ lint: true }); + +export const bundlesOption = new OptionNew('-b, --bundles ', 'Manually specify which bundles') + .default(null); + +export const tabsOption = new OptionNew('-t, --tabs ', 'Manually specify which tabs') + .default(null); + +export function promiseAll[]>(...args: T): Promise<{ [K in keyof T]: Awaited }> { + return Promise.all(args); +} + +export interface TimedResult { + result: T + elapsed: number +} + +// export function wrapWithTimer Promise>(func: T) { +// return async (...args: Parameters): Promise>> => { +// const startTime = performance.now(); +// const result = await func(...args); +// return { +// result, +// elapsed: performance.now() - startTime +// }; +// }; +// } + +type ValuesOfRecord = T extends Record ? U : never; + +export type EntriesOfRecord> = ValuesOfRecord<{ + [K in keyof T]: [K, T[K]] +}>; + +export function objectEntries>(obj: T) { + return Object.entries(obj) as EntriesOfRecord[]; +} + +export interface BuildInputs { + bundles?: string[] | null; + tabs?: string[] | null; + modulesSpecified?: boolean; +} diff --git a/lib/buildtools/src/prebuild/lint.ts b/lib/buildtools/src/prebuild/lint.ts new file mode 100644 index 0000000000..540857d341 --- /dev/null +++ b/lib/buildtools/src/prebuild/lint.ts @@ -0,0 +1,39 @@ +import { ESLint } from 'eslint' +import { findSeverity, type Severity } from '../utils'; + +interface LintResults { + formatted: string + severity: Severity +} + +export async function runEslint(directory: string, fix: boolean): Promise { + const linter = new ESLint({ fix }); + + try { + const linterResults = await linter.lintFiles(directory); + if (fix) { + await ESLint.outputFixes(linterResults); + } + + const outputFormatter = await linter.loadFormatter('stylish'); + const formatted = await outputFormatter.format(linterResults); + const severity = findSeverity(linterResults, ({ warningCount, errorCount, fatalErrorCount }) => { + + if (fatalErrorCount > 0) return 'error' + if (!fix && errorCount > 0) return 'error' + + if (warningCount > 0) return 'warn'; + return 'success'; + }); + + return { + formatted, + severity + }; + } catch (error) { + return { + severity: 'error', + formatted: error.toString() + }; + } +} \ No newline at end of file diff --git a/lib/buildtools/src/prebuild/typecheck.ts b/lib/buildtools/src/prebuild/typecheck.ts new file mode 100644 index 0000000000..a1d3d2f675 --- /dev/null +++ b/lib/buildtools/src/prebuild/typecheck.ts @@ -0,0 +1,82 @@ +import fs from 'fs/promises' +import pathlib from 'path' +import ts from 'typescript' + +type TsconfigResult = { + severity: 'error', + results?: ts.Diagnostic[] + error?: any +} | { + severity: 'success', + results: ts.CompilerOptions +}; + +type TscResult = { + severity: 'error' + results?: ts.Diagnostic[] + error?: any +} | { + severity: 'success', + results: ts.Diagnostic[] +}; + +async function getTsconfig(srcDir: string): Promise { + // Step 1: Read the text from tsconfig.json + const tsconfigLocation = pathlib.join(srcDir, 'tsconfig.json'); + + try { + const configText = await fs.readFile(tsconfigLocation, 'utf-8'); + + // Step 2: Parse the raw text into a json object + const { error: configJsonError, config: configJson } = ts.parseConfigFileTextToJson(tsconfigLocation, configText); + if (configJsonError) { + return { + severity: 'error', + results: [configJsonError] + }; + } + + // Step 3: Parse the json object into a config object for use by tsc + const { errors: parseErrors, options: tsconfig } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); + if (parseErrors.length > 0) { + return { + severity: 'error', + results: parseErrors + }; + } + + return { + severity: 'success', + results: tsconfig + }; + } catch (error) { + return { + severity: 'error', + error + }; + } +} + +export async function runTsc(srcDir: string): Promise { + const tsconfigRes = await getTsconfig(srcDir); + if (tsconfigRes.severity === 'error') { + return tsconfigRes; + } + + try { + const tsc = ts.createProgram([], tsconfigRes.results); + const results = tsc.emit(); + const diagnostics = ts.getPreEmitDiagnostics(tsc) + .concat(results.diagnostics); + + return { + severity: diagnostics.length > 0 ? 'error' : 'success', + results: diagnostics + }; + } catch (error) { + return { + severity: 'error', + error + }; + } +} diff --git a/lib/buildtools/src/templates/__tests__/names.test.ts b/lib/buildtools/src/templates/__tests__/names.test.ts new file mode 100644 index 0000000000..633cca5014 --- /dev/null +++ b/lib/buildtools/src/templates/__tests__/names.test.ts @@ -0,0 +1,22 @@ +import { isPascalCase, isSnakeCase } from '../utilities'; + +function testFunction( + func: (value: string) => boolean, + tcs: [string, boolean][] +) { + describe(`Testing ${func.name}`, () => test.each(tcs)('%#: %s', (value, valid) => expect(func(value)) + .toEqual(valid))); +} + +testFunction(isPascalCase, [ + ['PascalCase', true], + ['snake_case', false], + ['Snake_Case', false] +]); + +testFunction(isSnakeCase, [ + ['snake_case', true], + ['arcade_2d', true], + ['PascalCase', false], + ['camelCase', false] +]); diff --git a/lib/buildtools/src/templates/bundle.ts b/lib/buildtools/src/templates/bundle.ts new file mode 100644 index 0000000000..41abe2d500 --- /dev/null +++ b/lib/buildtools/src/templates/bundle.ts @@ -0,0 +1,60 @@ +import fs from 'fs/promises'; +import type { Interface } from 'readline/promises'; +import { askQuestion, success, warn } from './print'; +import { check, isSnakeCase } from './utilities'; +import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; +import _package from '../../../../package.json' with { type: 'json' } + +async function askModuleName(manifest: ModulesManifest, rl: Interface) { + while (true) { + const name = await askQuestion('What is the name of your new module? (eg. binary_tree)', rl); + if (!isSnakeCase(name)) { + warn('Module names must be in snake case. (eg. binary_tree)'); + } else if (check(manifest, name)) { + warn('A module with the same name already exists.'); + } else { + return name; + } + } +} + +export async function addNew(bundlesDir: string, rl: Interface) { + const manifest = await getBundleManifests(bundlesDir) + const moduleName = await askModuleName(manifest, rl); + const bundleDestination = `${bundlesDir}/${moduleName}`; + + await fs.cp(`${__dirname}/templates/bundle`, bundleDestination) + + const typescriptVersion = _package.devDependencies.typescript + + const packageJson = { + name: `@sourceacademy/bundle-${moduleName}`, + private: true, + version: "1.0.0", + devDependencies: { + "@sourceacademy/modules-buildtools": "workspace:^", + typescript: typescriptVersion, + }, + type: "module", + scripts: { + tsc: "tsc --project ./tsconfig.json", + build: 'buildtools build bundle .' + }, + exports: { + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./*.js": "./dist/*.js" + } + } + + const bundleManifest: BundleManifest = { + tabs: [] + } + + await Promise.all([ + fs.writeFile(`${bundleDestination}/package.json`, JSON.stringify(packageJson, null, 2)), + fs.writeFile(`${bundleDestination}/manifest.json`, JSON.stringify(bundleManifest, null, 2)) + ]) + + success(`Bundle for module ${moduleName} created at ${bundleDestination}.`); +} diff --git a/lib/buildtools/src/templates/print.ts b/lib/buildtools/src/templates/print.ts new file mode 100644 index 0000000000..f358a2fc66 --- /dev/null +++ b/lib/buildtools/src/templates/print.ts @@ -0,0 +1,27 @@ +import { type Interface, createInterface } from 'readline/promises'; +import chalk from 'chalk'; + +export const getRl = () => createInterface({ + input: process.stdin, + output: process.stdout +}); + +export function info(...args: any[]) { + return console.log(...args.map(string => chalk.grey(string))); +} + +export function error(...args) { + return console.log(...args.map(string => chalk.red(string))); +} + +export function warn(...args) { + return console.log(...args.map(string => chalk.yellow(string))); +} + +export function success(...args) { + return console.log(...args.map(string => chalk.green(string))); +} + +export function askQuestion(question: string, rl: Interface) { + return rl.question(chalk.blueBright(`${question}\n`)); +} diff --git a/lib/buildtools/src/templates/tab.ts b/lib/buildtools/src/templates/tab.ts new file mode 100644 index 0000000000..9c04124d6c --- /dev/null +++ b/lib/buildtools/src/templates/tab.ts @@ -0,0 +1,85 @@ +import fs from 'fs/promises'; +import type { Interface } from 'readline/promises'; +import { askQuestion, success, warn } from './print'; +import { check, isPascalCase } from './utilities'; +import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; +import _package from '../../../../package.json' with { type: 'json' } + +async function askModuleName(manifest: ModulesManifest, rl: Interface) { + while (true) { + const name = await askQuestion('Add a new tab to which module?', rl); + if (!check(manifest, name)) { + warn(`Module ${name} does not exist.`); + } else { + return name; + } + } +} + +function checkTabExists(manifest: ModulesManifest, name: string) { + return Object.values(manifest).flatMap(x => x.tabs).includes(name) +} + +async function askTabName(manifest: ModulesManifest, rl: Interface) { + while (true) { + const name = await askQuestion('What is the name of your new tab? (eg. BinaryTree)', rl); + if (checkTabExists(manifest, name)) { + warn('A tab with the same name already exists.'); + } else if (!isPascalCase(name)) { + warn('Tab names must be in pascal case. (eg. BinaryTree)'); + } else { + return name; + } + } +} + +export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) { + const manifest = await getBundleManifests(bundlesDir) + const moduleName = await askModuleName(manifest, rl); + const tabName = await askTabName(manifest, rl); + + // Copy module tab template into correct destination and show success message + const tabDestination = `${tabsDir}/${tabName}`; + await fs.mkdir(tabDestination, { recursive: true }); + + const reactVersion = _package.dependencies.react + const { + "@types/react": reactTypesVersion, + typescript: typescriptVersion + } = _package.devDependencies + + const packageJson = { + name: `@sourceacademy/tab-${tabName}`, + private: true, + version: "1.0.0", + devDependencies: { + "@sourceacademy/modules-buildtools": "workspace:^", + "@types/react": reactTypesVersion, + "typescript": typescriptVersion, + }, + dependencies: { + react: reactVersion, + }, + scripts: { + "build": "buildtools build tab ." + } + } + + const newManifest: BundleManifest = { + ...manifest[moduleName], + tabs: [ + ...manifest[moduleName].tabs, + tabName + ] + } + + await fs.cp(`${__dirname}/templates/tabs`, tabDestination) + await Promise.all([ + fs.writeFile(`${tabDestination}/package.json`, JSON.stringify(packageJson, null, 2)), + fs.writeFile(`${bundlesDir}/${moduleName}/manifest.json`, JSON.stringify(newManifest, null, 2)) + ]) + + success( + `Tab ${tabName} for module ${moduleName} created at ${tabDestination}.` + ); +} diff --git a/lib/buildtools/src/templates/utilities.ts b/lib/buildtools/src/templates/utilities.ts new file mode 100644 index 0000000000..274ddc1ea7 --- /dev/null +++ b/lib/buildtools/src/templates/utilities.ts @@ -0,0 +1,20 @@ +// Snake case regex has been changed from `/\b[a-z]+(?:_[a-z]+)*\b/u` to `/\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u` +// to be consistent with the naming of the `arcade_2d` and `physics_2d` modules. + +import type { ModulesManifest } from "../build/manifest"; + +// This change should not affect other modules, since the set of possible names is only expanded. +const snakeCaseRegex = /\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u; +const pascalCaseRegex = /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/u; + +export function isSnakeCase(string: string) { + return snakeCaseRegex.test(string); +} + +export function isPascalCase(string: string) { + return pascalCaseRegex.test(string); +} + +export function check(manifest: ModulesManifest, name: string) { + return Object.keys(manifest).includes(name) +} \ No newline at end of file diff --git a/lib/buildtools/src/testing/runner.ts b/lib/buildtools/src/testing/runner.ts new file mode 100644 index 0000000000..9736bc204b --- /dev/null +++ b/lib/buildtools/src/testing/runner.ts @@ -0,0 +1,10 @@ +import pathlib from 'path' +import jest from 'jest'; + +export function runJest(jestArgs: string[], patterns: string[]) { + const filePatterns = patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)) + return jest.run([ + ...jestArgs, + ...filePatterns + ]); +} diff --git a/lib/buildtools/src/utils.ts b/lib/buildtools/src/utils.ts new file mode 100644 index 0000000000..c82e6b32f2 --- /dev/null +++ b/lib/buildtools/src/utils.ts @@ -0,0 +1,43 @@ +import { execFile } from "child_process" +import _ from "lodash" +import pathlib from "path" + +function rawGetGitRoot() { + return new Promise((resolve, reject) => { + execFile('git', ['rev-parse', '--show-toplevel'], (err, stdout, stderr) => { + const possibleError = err || stderr + if (possibleError) { + reject(possibleError) + } + + resolve(stdout.trim()) + }) + }) +} + +/** + * Get the path to the root of the git repository + */ +export const getGitRoot = _.memoize(rawGetGitRoot) +export const getBundlesDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'bundles')) +export const getTabsDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'tabs')) +export const getOutDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'build')) + +export type AwaitedReturn Promise> = Awaited> + +export type Severity = 'success' | 'warn' | 'error' + +export function findSeverity(items: T[], mapper: (each: T) => Severity): Severity { + let output: Severity = 'success' + for (const item of items) { + const severity = mapper(item) + if (severity === 'error') return 'error' + if (severity === 'warn') { + output = 'warn' + } + } + + return output; +} + +export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFixed(2); \ No newline at end of file diff --git a/lib/buildtools/tsconfig.json b/lib/buildtools/tsconfig.json new file mode 100644 index 0000000000..5451ccd1a5 --- /dev/null +++ b/lib/buildtools/tsconfig.json @@ -0,0 +1,15 @@ +// buildtools tsconfig +{ + "compilerOptions": { + "isolatedModules": true, + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true + }, + "extends": "../tsconfig.json", + "include": ["./src", "jest.setup.ts"], + "exclude": [ + "./bin", + "./src/build/__test_mocks__" + ] +} diff --git a/lib/buildtools/workspacer.py b/lib/buildtools/workspacer.py new file mode 100644 index 0000000000..9e53c95ae4 --- /dev/null +++ b/lib/buildtools/workspacer.py @@ -0,0 +1,97 @@ +""" +This file contains a bunch of python scripts that can be used to update all +the tsconfigs or package.jsons of all tabs and bundles at once +(say if a new script needed to be added) +""" + +import os +import json + +def get_tabs(): + for name in os.listdir('./src/tabs'): + if not os.path.isdir(f'./src/tabs/{name}'): + continue + yield name + +def get_bundles(): + for name in os.listdir('./src/bundles'): + if name == '__mocks__': + continue + + if not os.path.isdir(f'./src/bundles/{name}'): + continue + yield name + +def update_tab_packages(): + for name in get_tabs(): + with open(f'./src/tabs/{name}/package.json') as file: + original = json.load(file) + + if '@sourceacademy/module-buildtools' in original['devDependencies']: + del original['devDependencies']['@sourceacademy/module-buildtools'] + original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" + + with open(f'./src/tabs/{name}/package.json', 'w') as file: + json.dump(original, file, indent=2) + +def create_bundle_manifest(): + with open('modules.json') as file: + current_manifest = json.load(file) + + for moduleName in current_manifest.keys(): + with open(f'./src/bundles/{moduleName}/manifest.json', 'w') as manifest_file: + manifest = {} + + if 'tabs' in current_manifest[moduleName]: + manifest['tabs'] = current_manifest[moduleName]['tabs'] + + json.dump(manifest, manifest_file, indent=2) + +def update_tab_tsconfigs(): + for name in get_tabs(): + tsconfigPath = f'./src/tabs/{name}/tsconfig.json' + with open(tsconfigPath) as file: + original = json.load(file) + + with open(f'./src/tabs/{name}/tsconfig.json', 'w') as file: + compilerOptions = original.setdefault('compilerOptions', dict()) + compilerOptions['outDir'] = './dist' + json.dump(original, file, indent=2) + +def update_bundle_tsconfigs(): + for name in os.listdir('./src/bundles'): + if not os.path.isdir(f'./src/bundles/{name}'): + continue + + with open(f'./src/bundles/{name}/tsconfig.json') as file: + original = json.load(file) + + if 'compilerOptions' in original: + original['compilerOptions']['outDir'] = './dist' + else: + original['compilerOptions'] = { + "outDir": './dist' + } + + with open(f'./src/bundles/{name}/tsconfig.json', 'w') as file: + json.dump(original, file, indent=2) + +def update_bundle_packages(): + for name in get_bundles(): + with open(f'./src/bundles/{name}/package.json') as file: + original = json.load(file) + + if '@sourceacademy/module-buildtools' in original['devDependencies']: + del original['devDependencies']['@sourceacademy/module-buildtools'] + original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" + + with open(f'./src/bundles/{name}/package.json', 'w') as file: + json.dump(original, file, indent=2) + +if __name__ == '__main__': + # create_bundle_manifest() + update_bundle_packages() + # update_bundle_tsconfigs() + # update_tab_packages() + # update_tab_tsconfigs() + \ No newline at end of file diff --git a/lib/lintplugin/package.json b/lib/lintplugin/package.json new file mode 100644 index 0000000000..4aa4c420f9 --- /dev/null +++ b/lib/lintplugin/package.json @@ -0,0 +1,38 @@ +{ + "name": "@sourceacademy/lint-plugin", + "version": "1.0.0", + "type": "module", + "exports": { + ".": "./dist/index.js" + }, + "dependencies": { + "@es-joy/jsdoccomment": "^0.50.1", + "@typescript-eslint/utils": "^8.32.1", + "eslint-plugin-import": "^2.31.0", + "typescript-eslint": "^8.24.1" + }, + "peerDependencies": { + "eslint": "^9.21.0" + }, + "devDependencies": { + "@typescript-eslint/rule-tester": "^8.32.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "typescript": "^5.8.2" + }, + "scripts": { + "build": "tsc --project ./tsconfig.prod.json", + "tsc": "tsc --project ./tsconfig.json", + "test": "jest" + }, + "jest": { + "displayName": "Lint Plugin", + "extensionsToTreatAsEsm": [ + ".ts" + ], + "preset": "ts-jest/presets/default-esm", + "testMatch": [ + "/src/**/__tests__/**/*.test.ts" + ] + } +} diff --git a/lib/lintplugin/src/configs.ts b/lib/lintplugin/src/configs.ts new file mode 100644 index 0000000000..95d7bedc49 --- /dev/null +++ b/lib/lintplugin/src/configs.ts @@ -0,0 +1,78 @@ +import stylePlugin from '@stylistic/eslint-plugin'; +import type { Linter } from 'eslint' +import * as importPlugin from 'eslint-plugin-import'; +import tseslint from 'typescript-eslint'; +import globals from 'globals' +import saLintPlugin from '.' + +const todoTreeKeywordsWarning = ['TODO', 'TODOS', 'TODO WIP', 'FIXME', 'WIP']; +const todoTreeKeywordsAll = [...todoTreeKeywordsWarning, 'NOTE', 'NOTES', 'LIST']; + +export const jsConfig = { + // Global JS Rules + languageOptions: { + globals: { + ...globals.node, + ...globals.es2022 + } + }, + plugins: { + import: importPlugin, + '@stylistic': stylePlugin, + }, + rules: { + 'import/no-duplicates': ['warn', { 'prefer-inline': false }], + 'import/order': [ + 'warn', + { + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + alphabetize: { + order: 'asc', + orderImportKind: 'asc' + }, + } + ], + + '@stylistic/brace-style': ['warn', '1tbs', { allowSingleLine: true }], + '@stylistic/eol-last': 'warn', + '@stylistic/indent': ['warn', 2, { SwitchCase: 1 }], + '@stylistic/no-mixed-spaces-and-tabs': 'warn', + '@stylistic/no-multi-spaces': 'warn', + '@stylistic/no-multiple-empty-lines': ['warn', { max: 1, maxEOF: 0 }], + '@stylistic/no-trailing-spaces': 'warn', + '@stylistic/quotes': ['warn', 'single', { avoidEscape: true }], + '@stylistic/semi': ['warn', 'always'], + '@stylistic/spaced-comment': [ + 'warn', + 'always', + { markers: todoTreeKeywordsAll } + ], + } +} satisfies Linter.Config + +export const tsConfig = { + // Global typescript rules + files: ['**/*.ts*'], + languageOptions: { + // @ts-expect-error typescript eslint's type definitions are different + parser: tseslint.parser + }, + plugins: { + // @ts-expect-error typescript eslint's type definitions are different + '@typescript-eslint': tseslint.plugin, + '@sourceacademy': saLintPlugin + }, + rules: { + 'no-unused-vars': 'off', // Use the typescript eslint rule instead + + '@typescript-eslint/ban-types': 'off', // Was 'error' + '@typescript-eslint/no-duplicate-type-constituents': 'off', // Was 'error' + '@typescript-eslint/no-explicit-any': 'off', // Was 'error' + '@typescript-eslint/no-redundant-type-constituents': 'off', // Was 'error' + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], // Was 'error' + '@typescript-eslint/prefer-ts-expect-error': 'warn', + '@typescript-eslint/sort-type-constituents': 'warn', + + '@sourceacademy/collate-type-imports': 'warn' + } +} satisfies Linter.Config \ No newline at end of file diff --git a/lib/lintplugin/src/index.ts b/lib/lintplugin/src/index.ts new file mode 100644 index 0000000000..37b03e1569 --- /dev/null +++ b/lib/lintplugin/src/index.ts @@ -0,0 +1,18 @@ +import type { ESLint } from 'eslint'; +import collateTypeImports from './rules/typeimports.js'; +import { jsConfig, tsConfig } from './configs.js'; +import moduleTagPresent from './rules/moduleTag.js'; + +const plugin: ESLint.Plugin = { + name: 'Source Academy Lint Plugin', + rules: { + 'collate-type-imports': collateTypeImports, + 'module-tag-present': moduleTagPresent + }, + configs: { + js: jsConfig, + ts: tsConfig + } +} + +export default plugin; diff --git a/lib/lintplugin/src/rules/__tests__/modulestag.test.ts b/lib/lintplugin/src/rules/__tests__/modulestag.test.ts new file mode 100644 index 0000000000..4c275ed9c5 --- /dev/null +++ b/lib/lintplugin/src/rules/__tests__/modulestag.test.ts @@ -0,0 +1,54 @@ +import { RuleTester } from 'eslint'; +import moduleTagPresent from '../moduleTag' + +describe('Test moduleTagPresent', () => { + const tester = new RuleTester({ + 'languageOptions': { + // eslint-disable-next-line @typescript-eslint/no-require-imports + parser: require('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } + }, + }); + + tester.run( + 'module-tag-present', + moduleTagPresent, + { + valid: [` + /** + * @module hi + */ + `], + invalid: [ + // { + // code: '', + // errors: 1, + // output: '/**\n * @module module_name\n */\n' + // }, { + // code: '// A comment', + // errors: 1, + // output: '/**\n * @module module_name\n */\n// A comment' + // }, { + // code: '/* A comment */', + // errors: 1, + // output: '/**\n * @module module_name\n */\n/* A comment */' + // }, { + // code: '/**\n *\n */', + // errors: 1, + // output: '/**\n * @module module_name\n */\n/**\n *\n */' + // }, + { + code: ` + /** + * @author yo + */ + `, + errors: 1, + output: '/**\n * @author yo\n * @module module_name\n */' + }] + } + ) +}) \ No newline at end of file diff --git a/lib/lintplugin/src/rules/__tests__/typeimports.test.ts b/lib/lintplugin/src/rules/__tests__/typeimports.test.ts new file mode 100644 index 0000000000..32c465e99b --- /dev/null +++ b/lib/lintplugin/src/rules/__tests__/typeimports.test.ts @@ -0,0 +1,35 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import collateTypeImports from '../typeimports'; + +describe('Test collateTypeImports', () => { + const tester = new RuleTester(); + tester.run( + 'collate-type-imports', + collateTypeImports, + { + valid: [ + 'import type { a, b } from "wherever"', + '', + 'import { type a, b } from "wherever"', + 'import { a, b } from "wherever"', + 'import a, { type b } from "wherever"', + 'import type { a as b } from "wherever"', + 'import { type a as b, c } from "wherever"', + 'import "wherever"', + ], + invalid: [{ + code: 'import { type a, type b } from "wherever"', + errors: [{ messageId: 'msg' }], + output: 'import type { a, b } from \'wherever\'' + }, { + code: 'import { type a } from "wherever"', + errors: [{ messageId: 'msg' }], + output: 'import type { a } from \'wherever\'' + }, { + code: 'import { type a as b } from "wherever"', + errors: [{ messageId: 'msg' }], + output: "import type { a as b } from 'wherever'" + }] + } + ); +}); diff --git a/lib/lintplugin/src/rules/moduleTag.ts b/lib/lintplugin/src/rules/moduleTag.ts new file mode 100644 index 0000000000..46a6771495 --- /dev/null +++ b/lib/lintplugin/src/rules/moduleTag.ts @@ -0,0 +1,56 @@ +import { parseComment, getJSDocComment } from '@es-joy/jsdoccomment' +import type { Rule } from "eslint"; + +const moduleTagPresent = { + meta: { + type: 'suggestion', + hasSuggestions: true, + fixable: 'code' + }, + create: ({ sourceCode, report }) => ({ + Program(node) { + let maxLines: number + + if (node.body.length === 0) { + maxLines = 1 + } else { + const firstNode = node.body[0] + maxLines = firstNode.loc.start.line + } + + const comment = getJSDocComment(sourceCode, node as any, { + maxLines, + minLines: 1 + }) + + if (comment === null) { + report({ + node, + message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', + fix: fixer => [ + fixer.insertTextBeforeRange([0, 0], '/**\n * @module module_name\n */\n') + ] + }) + return; + } + + const parsed = parseComment(comment) + const tag = parsed.tags.find(tag => tag.tag === 'module') + + if (!tag) { + console.log(comment.loc.end) + const commentEndLine = (comment as any).loc.end.line + report({ + node, + message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', + fix: fixer => [ + fixer.insertTextAfterRange([commentEndLine, commentEndLine], '\n * @module module_name\n') + ] + }) + return + } + } + }) +} satisfies Rule.RuleModule + +export default moduleTagPresent \ No newline at end of file diff --git a/lib/lintplugin/src/rules/tabType.ts b/lib/lintplugin/src/rules/tabType.ts new file mode 100644 index 0000000000..7d54d85d54 --- /dev/null +++ b/lib/lintplugin/src/rules/tabType.ts @@ -0,0 +1,78 @@ +import { ESLintUtils, type TSESTree as es } from "@typescript-eslint/utils"; + +const createRule = ESLintUtils.RuleCreator.withoutDocs + +const tabType = createRule({ + meta: { + type: 'problem', + schema: [{ + type: 'string', + description: 'Import path' + }], + messages: { + noExport: 'Your tab should export an object using the defineTab helper', + useHelper: 'Use the defineTab helper from {{ source }}' + } + }, + defaultOptions: [ + '@sourceacademy/modules-lib/tabs/utils' + ], + create: (context, options) => ({ + Program(node) { + const exportNode = node.body.find((stmt): stmt is es.ExportDefaultDeclaration => stmt.type === 'ExportDefaultDeclaration') + if (!exportNode) { + context.report({ + messageId: 'noExport', + node + }) + return + } + + const { declaration: exportDeclaration } = exportNode + if (exportDeclaration.type !== 'CallExpression') { + context.report({ + messageId: 'useHelper', + data: { + source: options[0] + }, + node: exportDeclaration + }) + return + } + + const importDeclarations = node.body.filter((stmt): stmt is es.ImportDeclaration => { + return stmt.type === 'ImportDeclaration' && stmt.source.value === options[0] + }) + + if (importDeclarations.length === 0) { + context.report({ + messageId: 'useHelper', + data: { + source: options[0] + }, + node + }) + return + } + + const specifiers = importDeclarations.flatMap(({ specifiers }) => specifiers) + .filter(spec => spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier' && spec.imported.name === 'defineTab') + + const defineNames = specifiers.map(spec => spec.local.name) + + const { callee } = exportDeclaration + if (callee.type !== 'Identifier' || defineNames.includes(callee.name)) { + context.report({ + messageId: 'useHelper', + data: { + source: options[0] + }, + node: callee + }) + return + } + } + }) +}) + +export default tabType \ No newline at end of file diff --git a/lib/lintplugin/src/rules/typeimports.ts b/lib/lintplugin/src/rules/typeimports.ts new file mode 100644 index 0000000000..ef98741c59 --- /dev/null +++ b/lib/lintplugin/src/rules/typeimports.ts @@ -0,0 +1,55 @@ +import { ESLintUtils, type TSESTree as es } from '@typescript-eslint/utils'; + +const createRule = ESLintUtils.RuleCreator.withoutDocs + +function isImportSpecifier(spec: es.ImportDeclaration['specifiers'][number]): spec is es.ImportSpecifier { + return spec.type === 'ImportSpecifier'; +} + +function specToString(spec: es.ImportSpecifier) { + if (spec.imported.type === 'Identifier') { + if (spec.imported.name === spec.local.name) { + return spec.imported.name; + } + return `${spec.imported.name} as ${spec.local.name}`; + } + return ''; +} + +const collateTypeImports = createRule({ + meta: { + type: 'suggestion', + messages: { + msg: 'Use a single type specifier', + }, + schema: [], + hasSuggestions: true, + fixable: 'code' + }, + defaultOptions: [], + create: context => ({ + ImportDeclaration(node) { + if (node.importKind === 'type' || node.specifiers.length === 0) return; + + if (node.specifiers.some(spec => !isImportSpecifier(spec) || spec.importKind !== 'type')) return; + + context.report({ + node, + messageId: 'msg', + fix(fixer) { + const regularSpecs = (node.specifiers as es.ImportSpecifier[]).map(specToString); + + return [ + fixer.remove(node), + fixer.insertTextAfter( + node, + `import type { ${regularSpecs.join(', ')} } from '${node.source.value}'` + ) + ]; + } + }); + } + }) +}) + +export default collateTypeImports; diff --git a/lib/lintplugin/tsconfig.json b/lib/lintplugin/tsconfig.json new file mode 100644 index 0000000000..7bddf2d957 --- /dev/null +++ b/lib/lintplugin/tsconfig.json @@ -0,0 +1,5 @@ +// lintplugin with tests +{ + "include": ["./src"], + "extends": "../tsconfig.json" +} \ No newline at end of file diff --git a/lib/lintplugin/tsconfig.prod.json b/lib/lintplugin/tsconfig.prod.json new file mode 100644 index 0000000000..f5000f7fc2 --- /dev/null +++ b/lib/lintplugin/tsconfig.prod.json @@ -0,0 +1,10 @@ +// lintplugin build config +{ + "compilerOptions": { + "declaration": true, + "noEmit": false, + "outDir": "./dist" + }, + "extends": "./tsconfig.json", + "exclude": ["**/__tests__"] +} \ No newline at end of file diff --git a/lib/tsconfig.json b/lib/tsconfig.json new file mode 100644 index 0000000000..797f42be42 --- /dev/null +++ b/lib/tsconfig.json @@ -0,0 +1,11 @@ +// Lib tsconfig.json +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "target": "ESNext", + "verbatimModuleSyntax": true + } +} \ No newline at end of file From 0c23c29f768bb83be24570909dab2bb4adb9fd89 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 22 May 2025 09:49:54 +0800 Subject: [PATCH 017/112] fix lintplugin things --- lib/lintplugin/build.js | 25 +++++++ lib/lintplugin/package.json | 11 ++- lib/lintplugin/src/configs.ts | 10 +-- lib/lintplugin/src/index.ts | 14 ++-- .../src/rules/__tests__/modulestag.test.ts | 56 ++++++--------- .../src/rules/__tests__/tabType.test.ts | 44 ++++++++++++ lib/lintplugin/src/rules/moduleTag.ts | 39 ++++------- lib/lintplugin/src/rules/tabType.ts | 69 +++++++++---------- lib/lintplugin/src/rules/typeimports.ts | 4 +- lib/lintplugin/tsconfig.prod.json | 4 +- 10 files changed, 166 insertions(+), 110 deletions(-) create mode 100644 lib/lintplugin/build.js create mode 100644 lib/lintplugin/src/rules/__tests__/tabType.test.ts diff --git a/lib/lintplugin/build.js b/lib/lintplugin/build.js new file mode 100644 index 0000000000..d75f6876c8 --- /dev/null +++ b/lib/lintplugin/build.js @@ -0,0 +1,25 @@ +/** + * Script for building lintplugin + */ + +// @ts-check +import { Command } from '@commander-js/extra-typings'; +import { build } from 'esbuild'; + +const command = new Command() + .option('--dev', 'If specified, the built output is not minified for easier debugging') + .action(async ({ dev }) => { + await build({ + entryPoints: ['./src/index.ts'], + bundle: true, + format: 'esm', + minify: !dev, + outfile: './dist.js', + packages: 'external', + platform: 'node', + target: 'node20', + tsconfig: './tsconfig.prod.json' + }); + }); + +await command.parseAsync(); diff --git a/lib/lintplugin/package.json b/lib/lintplugin/package.json index 4aa4c420f9..5ced3439b6 100644 --- a/lib/lintplugin/package.json +++ b/lib/lintplugin/package.json @@ -3,12 +3,17 @@ "version": "1.0.0", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist.js", + "default": null + } }, "dependencies": { "@es-joy/jsdoccomment": "^0.50.1", + "@stylistic/eslint-plugin": "^4.2.0", "@typescript-eslint/utils": "^8.32.1", "eslint-plugin-import": "^2.31.0", + "globals": "^15.11.0", "typescript-eslint": "^8.24.1" }, "peerDependencies": { @@ -21,9 +26,9 @@ "typescript": "^5.8.2" }, "scripts": { - "build": "tsc --project ./tsconfig.prod.json", + "build": "tsc --project ./tsconfig.prod.json && node ./build.js", "tsc": "tsc --project ./tsconfig.json", - "test": "jest" + "test": "yarn node --experimental-vm-modules $(yarn bin jest)" }, "jest": { "displayName": "Lint Plugin", diff --git a/lib/lintplugin/src/configs.ts b/lib/lintplugin/src/configs.ts index 95d7bedc49..1bb9033277 100644 --- a/lib/lintplugin/src/configs.ts +++ b/lib/lintplugin/src/configs.ts @@ -1,9 +1,9 @@ import stylePlugin from '@stylistic/eslint-plugin'; -import type { Linter } from 'eslint' +import type { Linter } from 'eslint'; import * as importPlugin from 'eslint-plugin-import'; +import globals from 'globals'; import tseslint from 'typescript-eslint'; -import globals from 'globals' -import saLintPlugin from '.' +import saLintPlugin from '.'; const todoTreeKeywordsWarning = ['TODO', 'TODOS', 'TODO WIP', 'FIXME', 'WIP']; const todoTreeKeywordsAll = [...todoTreeKeywordsWarning, 'NOTE', 'NOTES', 'LIST']; @@ -48,7 +48,7 @@ export const jsConfig = { { markers: todoTreeKeywordsAll } ], } -} satisfies Linter.Config +} satisfies Linter.Config; export const tsConfig = { // Global typescript rules @@ -75,4 +75,4 @@ export const tsConfig = { '@sourceacademy/collate-type-imports': 'warn' } -} satisfies Linter.Config \ No newline at end of file +} satisfies Linter.Config; diff --git a/lib/lintplugin/src/index.ts b/lib/lintplugin/src/index.ts index 37b03e1569..1cb457eb75 100644 --- a/lib/lintplugin/src/index.ts +++ b/lib/lintplugin/src/index.ts @@ -1,18 +1,22 @@ import type { ESLint } from 'eslint'; -import collateTypeImports from './rules/typeimports.js'; -import { jsConfig, tsConfig } from './configs.js'; -import moduleTagPresent from './rules/moduleTag.js'; +import { jsConfig, tsConfig } from './configs'; +import moduleTagPresent from './rules/moduleTag'; +import tabType from './rules/tabType'; +import collateTypeImports from './rules/typeimports'; const plugin: ESLint.Plugin = { name: 'Source Academy Lint Plugin', rules: { + // @ts-expect-error typescript-eslint rules are typed differently 'collate-type-imports': collateTypeImports, - 'module-tag-present': moduleTagPresent + 'module-tag-present': moduleTagPresent, + 'tab-type': tabType }, configs: { js: jsConfig, + // @ts-expect-error tseslint doesn't play nice with eslint's typing ts: tsConfig } -} +}; export default plugin; diff --git a/lib/lintplugin/src/rules/__tests__/modulestag.test.ts b/lib/lintplugin/src/rules/__tests__/modulestag.test.ts index 4c275ed9c5..a2a043ab07 100644 --- a/lib/lintplugin/src/rules/__tests__/modulestag.test.ts +++ b/lib/lintplugin/src/rules/__tests__/modulestag.test.ts @@ -1,17 +1,8 @@ import { RuleTester } from 'eslint'; -import moduleTagPresent from '../moduleTag' +import moduleTagPresent from '../moduleTag'; describe('Test moduleTagPresent', () => { - const tester = new RuleTester({ - 'languageOptions': { - // eslint-disable-next-line @typescript-eslint/no-require-imports - parser: require('@typescript-eslint/parser'), - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } - }, - }); + const tester = new RuleTester(); tester.run( 'module-tag-present', @@ -23,32 +14,27 @@ describe('Test moduleTagPresent', () => { */ `], invalid: [ - // { - // code: '', - // errors: 1, - // output: '/**\n * @module module_name\n */\n' - // }, { - // code: '// A comment', - // errors: 1, - // output: '/**\n * @module module_name\n */\n// A comment' - // }, { - // code: '/* A comment */', - // errors: 1, - // output: '/**\n * @module module_name\n */\n/* A comment */' - // }, { - // code: '/**\n *\n */', - // errors: 1, - // output: '/**\n * @module module_name\n */\n/**\n *\n */' - // }, - { - code: ` + { + code: '', + errors: 1, + }, { + code: '// A comment', + errors: 1, + }, { + code: '/* A comment */', + errors: 1, + }, { + code: '/**\n *\n */', + errors: 1, + }, + { + code: ` /** * @author yo */ `, - errors: 1, - output: '/**\n * @author yo\n * @module module_name\n */' - }] + errors: 1, + }] } - ) -}) \ No newline at end of file + ); +}); diff --git a/lib/lintplugin/src/rules/__tests__/tabType.test.ts b/lib/lintplugin/src/rules/__tests__/tabType.test.ts new file mode 100644 index 0000000000..01644ed624 --- /dev/null +++ b/lib/lintplugin/src/rules/__tests__/tabType.test.ts @@ -0,0 +1,44 @@ +import { RuleTester } from 'eslint'; +import tabType from '../tabType'; + +describe('Test collateTypeImports', () => { + const tester = new RuleTester(); + tester.run( + 'tab-type', + tabType, + { + valid: [{ + code: ` + import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; + export default defineTab({}) + ` + }, { + code: ` + import { defineTab as definer } from '@sourceacademy/modules-lib/tabs/utils'; + export default definer({}) + ` + }, { + code: ` + import { stuff } from 'somewhere'; + import { defineTab as definer } from '@sourceacademy/modules-lib/tabs/utils'; + export default definer({ stuff }) + `}], + invalid: [{ + code: '', + errors: 1 + }, { + code: ` + import { stuff } from 'somwhere'; + export default 0; + `, + errors: 1 + }, { + code: ` + import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; + export default 0; + `, + errors: 1 + }] + } + ); +}); diff --git a/lib/lintplugin/src/rules/moduleTag.ts b/lib/lintplugin/src/rules/moduleTag.ts index 46a6771495..211b44a4d1 100644 --- a/lib/lintplugin/src/rules/moduleTag.ts +++ b/lib/lintplugin/src/rules/moduleTag.ts @@ -1,56 +1,47 @@ -import { parseComment, getJSDocComment } from '@es-joy/jsdoccomment' -import type { Rule } from "eslint"; +import { parseComment, getJSDocComment } from '@es-joy/jsdoccomment'; +import type { Rule } from 'eslint'; const moduleTagPresent = { meta: { type: 'suggestion', - hasSuggestions: true, - fixable: 'code' }, create: ({ sourceCode, report }) => ({ Program(node) { - let maxLines: number + console.log(sourceCode.getAllComments()); + let maxLines: number; if (node.body.length === 0) { - maxLines = 1 + maxLines = 1; } else { - const firstNode = node.body[0] - maxLines = firstNode.loc.start.line + const firstNode = node.body[0]; + maxLines = firstNode.loc.start.line; } const comment = getJSDocComment(sourceCode, node as any, { maxLines, minLines: 1 - }) + }); if (comment === null) { report({ node, message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', - fix: fixer => [ - fixer.insertTextBeforeRange([0, 0], '/**\n * @module module_name\n */\n') - ] - }) + }); return; } - const parsed = parseComment(comment) - const tag = parsed.tags.find(tag => tag.tag === 'module') + const parsed = parseComment(comment); + const tag = parsed.tags.find(tag => tag.tag === 'module'); if (!tag) { - console.log(comment.loc.end) - const commentEndLine = (comment as any).loc.end.line report({ node, message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', - fix: fixer => [ - fixer.insertTextAfterRange([commentEndLine, commentEndLine], '\n * @module module_name\n') - ] - }) - return + }); + return; } } }) -} satisfies Rule.RuleModule +} satisfies Rule.RuleModule; -export default moduleTagPresent \ No newline at end of file +export default moduleTagPresent; diff --git a/lib/lintplugin/src/rules/tabType.ts b/lib/lintplugin/src/rules/tabType.ts index 7d54d85d54..825992b8b9 100644 --- a/lib/lintplugin/src/rules/tabType.ts +++ b/lib/lintplugin/src/rules/tabType.ts @@ -1,78 +1,77 @@ -import { ESLintUtils, type TSESTree as es } from "@typescript-eslint/utils"; +import type { Rule } from 'eslint'; +import type es from 'estree'; -const createRule = ESLintUtils.RuleCreator.withoutDocs - -const tabType = createRule({ +const tabType = { meta: { type: 'problem', schema: [{ type: 'string', - description: 'Import path' + description: 'Enforces typing for Source Academy tabs' }], messages: { noExport: 'Your tab should export an object using the defineTab helper', useHelper: 'Use the defineTab helper from {{ source }}' - } + }, + defaultOptions: [ + '@sourceacademy/modules-lib/tabs/utils' + ], }, - defaultOptions: [ - '@sourceacademy/modules-lib/tabs/utils' - ], - create: (context, options) => ({ - Program(node) { - const exportNode = node.body.find((stmt): stmt is es.ExportDefaultDeclaration => stmt.type === 'ExportDefaultDeclaration') + create: context => ({ + Program(program) { + const exportNode = program.body.find((stmt): stmt is es.ExportDefaultDeclaration => stmt.type === 'ExportDefaultDeclaration'); if (!exportNode) { context.report({ messageId: 'noExport', - node - }) - return + node: program + }); + return; } - const { declaration: exportDeclaration } = exportNode + const { declaration: exportDeclaration } = exportNode; if (exportDeclaration.type !== 'CallExpression') { context.report({ messageId: 'useHelper', data: { - source: options[0] + source: context.options[0] }, node: exportDeclaration - }) - return + }); + return; } - const importDeclarations = node.body.filter((stmt): stmt is es.ImportDeclaration => { - return stmt.type === 'ImportDeclaration' && stmt.source.value === options[0] - }) + const importDeclarations = program.body.filter((stmt): stmt is es.ImportDeclaration => { + return stmt.type === 'ImportDeclaration' && stmt.source.value === context.options[0]; + }); if (importDeclarations.length === 0) { context.report({ messageId: 'useHelper', data: { - source: options[0] + source: context.options[0] }, - node - }) - return + node: exportDeclaration + }); + return; } const specifiers = importDeclarations.flatMap(({ specifiers }) => specifiers) - .filter(spec => spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier' && spec.imported.name === 'defineTab') + .filter(spec => spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier' && spec.imported.name === 'defineTab'); - const defineNames = specifiers.map(spec => spec.local.name) + const defineNames = specifiers.map(spec => spec.local.name); - const { callee } = exportDeclaration - if (callee.type !== 'Identifier' || defineNames.includes(callee.name)) { + const { callee } = exportDeclaration; + if (callee.type !== 'Identifier' || !defineNames.includes(callee.name)) { context.report({ messageId: 'useHelper', data: { - source: options[0] + source: context.options[0] }, node: callee - }) - return + }); + return; } } }) -}) +} satisfies Rule.RuleModule; -export default tabType \ No newline at end of file +export default tabType; diff --git a/lib/lintplugin/src/rules/typeimports.ts b/lib/lintplugin/src/rules/typeimports.ts index ef98741c59..bf36c615e8 100644 --- a/lib/lintplugin/src/rules/typeimports.ts +++ b/lib/lintplugin/src/rules/typeimports.ts @@ -1,6 +1,6 @@ import { ESLintUtils, type TSESTree as es } from '@typescript-eslint/utils'; -const createRule = ESLintUtils.RuleCreator.withoutDocs +const createRule = ESLintUtils.RuleCreator.withoutDocs; function isImportSpecifier(spec: es.ImportDeclaration['specifiers'][number]): spec is es.ImportSpecifier { return spec.type === 'ImportSpecifier'; @@ -50,6 +50,6 @@ const collateTypeImports = createRule({ }); } }) -}) +}); export default collateTypeImports; diff --git a/lib/lintplugin/tsconfig.prod.json b/lib/lintplugin/tsconfig.prod.json index f5000f7fc2..98a3a9cfc0 100644 --- a/lib/lintplugin/tsconfig.prod.json +++ b/lib/lintplugin/tsconfig.prod.json @@ -2,8 +2,10 @@ { "compilerOptions": { "declaration": true, + "emitDeclarationOnly": true, "noEmit": false, - "outDir": "./dist" + "outDir": "./dist", + "skipLibCheck": true }, "extends": "./tsconfig.json", "exclude": ["**/__tests__"] From 4de476edcad1fdf6e77ef19d89433b495d9ffdb5 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 22 May 2025 12:27:57 +0800 Subject: [PATCH 018/112] Some working tests --- lib/buildtools/README.md | 10 ++ lib/buildtools/bin/templates/tab/index.tsx | 24 +--- lib/buildtools/build.js | 13 +- lib/buildtools/build/bundle/test0.js | 49 ------- lib/buildtools/jest.config.js | 4 +- lib/buildtools/jest.setup.ts | 2 +- lib/buildtools/package.json | 5 +- lib/buildtools/src/__tests__/utils.test.ts | 15 +++ .../src/build/docs/__tests__/building.test.ts | 20 +-- lib/buildtools/src/build/docs/docsUtils.ts | 21 ++- lib/buildtools/src/build/docs/index.ts | 31 +++-- lib/buildtools/src/build/docs/json.ts | 2 +- lib/buildtools/src/build/manifest.ts | 56 ++++---- .../build/modules/__tests__/bundle.test.ts | 30 ++--- lib/buildtools/src/build/modules/bundle.ts | 16 +-- lib/buildtools/src/build/modules/commons.ts | 2 +- lib/buildtools/src/build/modules/index.ts | 10 +- lib/buildtools/src/build/modules/tab.ts | 42 +++--- .../src/commands/__tests__/main.test.ts | 11 +- .../src/commands/__tests__/prebuild.test.ts | 124 ++++++++++++++++++ .../src/commands/__tests__/template.test.ts | 91 ++++++------- .../src/commands/__tests__/testing.test.ts | 8 +- lib/buildtools/src/commands/build.ts | 62 ++++----- lib/buildtools/src/commands/index.ts | 4 +- lib/buildtools/src/commands/list.ts | 49 ++++--- lib/buildtools/src/commands/main.ts | 14 +- lib/buildtools/src/commands/prebuild.ts | 90 +++++++------ lib/buildtools/src/commands/template.ts | 2 +- lib/buildtools/src/prebuild/lint.ts | 25 ++-- lib/buildtools/src/prebuild/typecheck.ts | 20 +-- lib/buildtools/src/templates/bundle.ts | 30 ++--- lib/buildtools/src/templates/tab.ts | 37 +++--- lib/buildtools/src/templates/utilities.ts | 6 +- lib/buildtools/src/testing/runner.ts | 4 +- lib/buildtools/src/utils.ts | 38 +++--- lib/buildtools/workspacer.py | 29 ++-- 36 files changed, 555 insertions(+), 441 deletions(-) create mode 100644 lib/buildtools/README.md delete mode 100644 lib/buildtools/build/bundle/test0.js create mode 100644 lib/buildtools/src/__tests__/utils.test.ts create mode 100644 lib/buildtools/src/commands/__tests__/prebuild.test.ts diff --git a/lib/buildtools/README.md b/lib/buildtools/README.md new file mode 100644 index 0000000000..4c9ce5b647 --- /dev/null +++ b/lib/buildtools/README.md @@ -0,0 +1,10 @@ +# Source Academy Buildtools + +## Testing +By default, Jest as a testing framework transforms everything from ESM to CJS before running tests. Unfortunately, the build tools are written in and compiled to ESM, which means +that certain features won't work properly if everything was written in pure ESM. + +`__dirname` is only available in CJS modules, but Node makes this availble in ESM using `import.meta.dirname`. `import.meta` is not available in CJS though, which means that Jest +won't be able to run tests. +Thus, we use `__dirname` in the source code but have `esbuild` replace it with `import.meta.dirname` using its `define` feature during compilation. At the same time +using `__dirname` means that Jest won't complain about using ESM features in CJS. \ No newline at end of file diff --git a/lib/buildtools/bin/templates/tab/index.tsx b/lib/buildtools/bin/templates/tab/index.tsx index 34eb95b091..021dc25e1f 100644 --- a/lib/buildtools/bin/templates/tab/index.tsx +++ b/lib/buildtools/bin/templates/tab/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils' /** * @@ -41,31 +42,12 @@ class Repeat extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ +export default defineTab({ toSpawn: (context: any) => context.result.value === 'test', - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ body: (context: any) => , - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Sample Tab', - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'build', -}; \ No newline at end of file +}); \ No newline at end of file diff --git a/lib/buildtools/build.js b/lib/buildtools/build.js index 34688f50fa..834f12ea96 100644 --- a/lib/buildtools/build.js +++ b/lib/buildtools/build.js @@ -3,8 +3,8 @@ */ // @ts-check -import { Command } from '@commander-js/extra-typings' -import { build } from 'esbuild' +import { Command } from '@commander-js/extra-typings'; +import { build } from 'esbuild'; const command = new Command() .option('--dev', 'If specified, the built output is not minified for easier debugging') @@ -12,6 +12,9 @@ const command = new Command() await build({ entryPoints: ['./src/commands/index.ts'], bundle: true, + define: { + __dirname: 'import.meta.dirname', + }, format: 'esm', minify: !dev, outfile: './bin/index.js', @@ -19,7 +22,7 @@ const command = new Command() platform: 'node', target: 'node20', tsconfig: './tsconfig.json' - }) - }) + }); + }); -await command.parseAsync() +await command.parseAsync(); diff --git a/lib/buildtools/build/bundle/test0.js b/lib/buildtools/build/bundle/test0.js deleted file mode 100644 index bf04a0037e..0000000000 --- a/lib/buildtools/build/bundle/test0.js +++ /dev/null @@ -1,49 +0,0 @@ -export default require => { - var __create = Object.create; - var __defProp = Object.defineProperty; - var __getOwnPropDesc = Object.getOwnPropertyDescriptor; - var __getOwnPropNames = Object.getOwnPropertyNames; - var __getProtoOf = Object.getPrototypeOf; - var __hasOwnProp = Object.prototype.hasOwnProperty; - var __require = (x => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { - get: (a, b) => (typeof require !== "undefined" ? require : a)[b] - }) : x)(function (x) { - if (typeof require !== "undefined") return require.apply(this, arguments); - throw Error('Dynamic require of "' + x + '" is not supported'); - }); - var __export = (target, all) => { - for (var name in all) __defProp(target, name, { - get: all[name], - enumerable: true - }); - }; - var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { - get: () => from[key], - enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable - }); - } - return to; - }; - var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { - value: mod, - enumerable: true - }) : target, mod)); - var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { - value: true - }), mod); - var index_exports = {}; - __export(index_exports, { - test_function: () => test_function, - test_function2: () => test_function2 - }); - var import_context = __toESM(__require("js-slang/context"), 1); - function test_function(_param0) { - return 0; - } - function test_function2() { - return import_context.default.moduleContexts.test0.state.data; - } - return __toCommonJS(index_exports); -}; \ No newline at end of file diff --git a/lib/buildtools/jest.config.js b/lib/buildtools/jest.config.js index 7ee62fb59b..d6613a49a6 100644 --- a/lib/buildtools/jest.config.js +++ b/lib/buildtools/jest.config.js @@ -2,7 +2,7 @@ /** * Buildtools Jest configuration - * @type {import('jest').Config} + * @type {import('ts-jest').JestConfigWithTsJest} */ const jestConfig = { clearMocks: true, @@ -15,7 +15,7 @@ const jestConfig = { }] }, transformIgnorePatterns: [ - '/node_modules/(?!typedoc/)', + 'node_modules/(?!typedoc)', ], setupFilesAfterEnv: ['/jest.setup.ts'], testMatch: [ diff --git a/lib/buildtools/jest.setup.ts b/lib/buildtools/jest.setup.ts index 965f0f5b35..8046fa9ea1 100644 --- a/lib/buildtools/jest.setup.ts +++ b/lib/buildtools/jest.setup.ts @@ -10,8 +10,8 @@ jest.mock('chalk', () => new Proxy({}, { jest.mock('fs/promises', () => ({ copyFile: jest.fn(() => Promise.resolve()), cp: jest.fn(() => Promise.resolve()), - mkdir: jest.fn(() => Promise.resolve()), open: jest.fn(), + mkdir: jest.fn(() => Promise.resolve()), writeFile: jest.fn(() => Promise.resolve()), })); diff --git a/lib/buildtools/package.json b/lib/buildtools/package.json index c2c3db922b..4d02de26cc 100644 --- a/lib/buildtools/package.json +++ b/lib/buildtools/package.json @@ -7,6 +7,7 @@ "@commander-js/extra-typings": "^13.0.0", "@types/estree": "^1.0.0", "@types/jest": "^29.0.0", + "@types/lodash": "^4.14.198", "@types/node": "^20.12.12" }, "exports": { @@ -26,6 +27,7 @@ "eslint": "^9.21.0", "jest": "^29.7.0", "jsonschema": "^1.5.0", + "lodash": "^4.17.21", "ts-jest": "^29.1.2", "typedoc": "^0.28.4", "typescript": "^5.8.2" @@ -33,6 +35,7 @@ "scripts": { "build": "node ./build.js", "tsc": "tsc --project ./tsconfig.json", - "test": "jest" + "test": "jest", + "lint": "eslint" } } diff --git a/lib/buildtools/src/__tests__/utils.test.ts b/lib/buildtools/src/__tests__/utils.test.ts new file mode 100644 index 0000000000..7e24ad4a37 --- /dev/null +++ b/lib/buildtools/src/__tests__/utils.test.ts @@ -0,0 +1,15 @@ +import { findSeverity, type Severity } from '../utils'; + +describe('test findSeverity', () => { + const cases: Severity[][] = [ + ['success', 'success', 'success'], + ['warn', 'success', 'warn'], + ['warn', 'warn', 'warn'], + ['error', 'error', 'warn'], + ['error', 'error', 'success'] + ]; + + test.each(cases)('Expecting %s', (expected, ...args) => { + expect(findSeverity(args, x => x)).toEqual(expected); + }); +}); diff --git a/lib/buildtools/src/build/docs/__tests__/building.test.ts b/lib/buildtools/src/build/docs/__tests__/building.test.ts index 846731b603..d357fc93f7 100644 --- a/lib/buildtools/src/build/docs/__tests__/building.test.ts +++ b/lib/buildtools/src/build/docs/__tests__/building.test.ts @@ -1,12 +1,12 @@ import fs from 'fs/promises'; import type { MockedFunction } from 'jest-mock'; +import type { ProjectReflection } from 'typedoc'; import { initTypedoc, initTypedocForSingleBundle } from '../docsUtils'; import * as json from '../json'; -import type { ProjectReflection } from 'typedoc'; jest.mock('../json', () => ({ buildJson: jest.fn() -})) +})); const mockedWriteFile = fs.writeFile as MockedFunction; @@ -31,7 +31,7 @@ function matchObj(raw: string, expected: T) { expect(JSON.parse(raw)).toMatchObject(expected); } -const testMocksDir = `${__dirname}/../../__test_mocks__` +const testMocksDir = `${__dirname}/../../__test_mocks__`; test('Building the json documentation for a single bundle', async () => { const project = await initTypedocForSingleBundle({ @@ -61,16 +61,16 @@ test('initTypedoc for muiltiple bundles', async () => { directory: `${testMocksDir}/bundles/test1`, entryPoint: `${testMocksDir}/bundles/test1/index.ts`, } - }) + }); - const test0Reflection = project.getChildByName('test0') as ProjectReflection - await json.buildJson('test0', test0Reflection, 'build') - - const test1Reflection = project.getChildByName('test1') as ProjectReflection - await json.buildJson('test1', test1Reflection, 'build') + const test0Reflection = project.getChildByName('test0') as ProjectReflection; + await json.buildJson('test0', test0Reflection, 'build'); + + const test1Reflection = project.getChildByName('test1') as ProjectReflection; + await json.buildJson('test1', test1Reflection, 'build'); expect(json.buildJson).toHaveBeenCalledTimes(2); const [[, test0str], [, test1str]] = mockedWriteFile.mock.calls; matchObj(test0str as string, test0Obj); matchObj(test1str as string, test1Obj); -}) +}); diff --git a/lib/buildtools/src/build/docs/docsUtils.ts b/lib/buildtools/src/build/docs/docsUtils.ts index 75630a5195..d6584d8a26 100644 --- a/lib/buildtools/src/build/docs/docsUtils.ts +++ b/lib/buildtools/src/build/docs/docsUtils.ts @@ -1,7 +1,7 @@ +import pathlib from 'path'; import * as td from 'typedoc'; -import type { ResolvedBundle } from '../manifest'; import { getBundlesDir } from '../../utils'; -import pathlib from 'path'; +import type { ResolvedBundle } from '../manifest'; const commonTypedocOptions: td.Configuration.TypeDocOptions = { categorizeByGroup: true, @@ -9,8 +9,7 @@ const commonTypedocOptions: td.Configuration.TypeDocOptions = { excludeInternal: true, logLevel: 'Error', skipErrorChecking: true, -} - +}; /** * Initialize typedoc for a single bundle. Useful for building the JSON @@ -25,24 +24,24 @@ export async function initTypedocForSingleBundle(bundle: ResolvedBundle) { tsconfig: `${bundle.directory}/tsconfig.json`, }); - const reflection = await app.convert() + const reflection = await app.convert(); if (!reflection) { - throw new Error(`Failed to generate reflection for ${bundle.name}, check that the bundle has no type errors!`) + throw new Error(`Failed to generate reflection for ${bundle.name}, check that the bundle has no type errors!`); } - return reflection + return reflection; } /** * Initialize typedoc for all bundles at once. Useful for building HTML documentation, * but can also be used to build the JSON documentation of every single bundle. - * + * * More efficient than having to initialize typedoc separately each time */ export async function initTypedoc(manifest: Record) { - const entryPoints = Object.values(manifest).map(({ entryPoint }) => entryPoint) - const bundlesDir = await getBundlesDir() - const tsconfigPath = pathlib.resolve(bundlesDir, 'tsconfig.json') + const entryPoints = Object.values(manifest).map(({ entryPoint }) => entryPoint); + const bundlesDir = await getBundlesDir(); + const tsconfigPath = pathlib.resolve(bundlesDir, 'tsconfig.json'); const app = await td.Application.bootstrap({ ...commonTypedocOptions, diff --git a/lib/buildtools/src/build/docs/index.ts b/lib/buildtools/src/build/docs/index.ts index e82560bb2a..bc6fa2f669 100644 --- a/lib/buildtools/src/build/docs/index.ts +++ b/lib/buildtools/src/build/docs/index.ts @@ -1,39 +1,38 @@ -import type { ProjectReflection } from 'typedoc' -import type { ResolvedBundle } from '../manifest' -import { initTypedoc } from './docsUtils' -import { buildJson } from './json' -import chalk from 'chalk' +import chalk from 'chalk'; +import type { ProjectReflection } from 'typedoc'; +import type { ResolvedBundle } from '../manifest'; +import { initTypedoc } from './docsUtils'; +import { buildJson } from './json'; /** * A more efficient version of documentation building to avoid * having to instantiate typedoc multiple times */ export async function buildDocs(manifest: Record, outDir: string) { - const [project, app] = await initTypedoc(manifest) + const [project, app] = await initTypedoc(manifest); - const validModules = new Set(Object.keys(manifest)) + const validModules = new Set(Object.keys(manifest)); - const unknownChildren = project.children.filter(({ name }) => !validModules.has(name)) + const unknownChildren = project.children.filter(({ name }) => !validModules.has(name)); if (unknownChildren.length > 0) { - console.warn(`${chalk.yellow('[warning]')} Unknown modules present in html output`) + console.warn(`${chalk.yellow('[warning]')} Unknown modules present in html output`); } const jsonPromises = Object.keys(manifest) .map(async bundleName => { - const reflection = project.getChildByName(bundleName) + const reflection = project.getChildByName(bundleName); if (!reflection) { - console.warn(`${chalk.yellow('[warning]')} Did not find documentation for ${bundleName}. Did you forget a @module tag?`) + console.warn(`${chalk.yellow('[warning]')} Did not find documentation for ${bundleName}. Did you forget a @module tag?`); return; } - await buildJson(bundleName, reflection as ProjectReflection, outDir) - }) + await buildJson(bundleName, reflection as ProjectReflection, outDir); + }); await Promise.all([ ...jsonPromises, app.generateDocs(project, `${outDir}/documentation`) - ]) + ]); } - -export { buildJson } \ No newline at end of file +export { buildJson }; diff --git a/lib/buildtools/src/build/docs/json.ts b/lib/buildtools/src/build/docs/json.ts index 7d4b2323e4..6a742c7993 100644 --- a/lib/buildtools/src/build/docs/json.ts +++ b/lib/buildtools/src/build/docs/json.ts @@ -8,7 +8,7 @@ const parsers = { [td.ReflectionKind.Function](obj) { // Functions should have only 1 signature if (obj.signatures.length > 1) { - console.warn(`${obj.name} has more than 1 signature; only using the first one`) + console.warn(`${obj.name} has more than 1 signature; only using the first one`); } const [signature] = obj.signatures; diff --git a/lib/buildtools/src/build/manifest.ts b/lib/buildtools/src/build/manifest.ts index a23dd0ab65..b610aad2f5 100644 --- a/lib/buildtools/src/build/manifest.ts +++ b/lib/buildtools/src/build/manifest.ts @@ -1,8 +1,8 @@ import fs from 'fs/promises'; import pathlib from 'path'; import { validate } from 'jsonschema'; -import manifestSchema from './modules/manifest.schema.json' with { type: 'json' }; import { getBundleEntryPoint } from './modules/bundle'; +import manifestSchema from './modules/manifest.schema.json' with { type: 'json' }; export interface BundleManifest { version?: string @@ -11,21 +11,21 @@ export interface BundleManifest { export type ModulesManifest = { [name: string]: BundleManifest -} +}; /** * Checks that the given bundle manifest was correctly specified */ export async function getBundleManifest(manifestFile: string, tabCheck?: boolean): Promise { - let rawManifest: string + let rawManifest: string; try { - rawManifest = await fs.readFile(manifestFile, 'utf-8') + rawManifest = await fs.readFile(manifestFile, 'utf-8'); } catch (error) { if (error.code === 'ENOENT') { - return undefined + return undefined; } - throw error + throw error; } const data = JSON.parse(rawManifest) as BundleManifest; @@ -51,23 +51,23 @@ export async function getBundleManifest(manifestFile: string, tabCheck?: boolean * Get all bundle manifests */ export async function getBundleManifests(bundlesDir: string): Promise { - const subdirs = await fs.readdir(bundlesDir) + const subdirs = await fs.readdir(bundlesDir); const manifests = await Promise.all(subdirs.map(async fileName => { const fullPath = pathlib.join(bundlesDir, fileName); const stats = await fs.stat(fullPath); if (stats.isDirectory()) { const manifest = await getBundleManifest(`${fullPath}/manifest.json`); - if (manifest === undefined) return undefined + if (manifest === undefined) return undefined; return [fileName, manifest] as [string, BundleManifest]; } - return undefined - })) + return undefined; + })); - return manifests.reduce((res, entry) => { + return manifests.reduce((res, entry) => { if (entry === undefined) return res; - const [name, manifest] = entry + const [name, manifest] = entry; return { ...res, @@ -84,46 +84,46 @@ export interface ResolvedBundle { } export async function resolveSingleBundle(bundleDir: string): Promise { - const fullyResolved = pathlib.resolve(bundleDir) + const fullyResolved = pathlib.resolve(bundleDir); - const stats = await fs.stat(fullyResolved) - if (!stats.isDirectory()) return undefined + const stats = await fs.stat(fullyResolved); + if (!stats.isDirectory()) return undefined; - const manifest = await getBundleManifest(`${fullyResolved}/manifest.json`) - if (!manifest) return undefined + const manifest = await getBundleManifest(`${fullyResolved}/manifest.json`); + if (!manifest) return undefined; - const bundleName = pathlib.basename(fullyResolved) - const entryPoint = await getBundleEntryPoint(fullyResolved) + const bundleName = pathlib.basename(fullyResolved); + const entryPoint = await getBundleEntryPoint(fullyResolved); return { name: bundleName, manifest, entryPoint, directory: fullyResolved - } + }; } export async function resolveAllBundles(bundlesDir: string) { - const subdirs = await fs.readdir(bundlesDir) + const subdirs = await fs.readdir(bundlesDir); const manifests = await Promise.all(subdirs.map(subdir => { - const fullPath = pathlib.join(bundlesDir, subdir) - return resolveSingleBundle(fullPath) - })) + const fullPath = pathlib.join(bundlesDir, subdir); + return resolveSingleBundle(fullPath); + })); return manifests.reduce((res, entry) => { - if (entry === undefined) return res + if (entry === undefined) return res; return { ...res, [entry.name]: entry - } - }, {} as Record) + }; + }, {} as Record); } export async function resolvePaths(...paths: string[]) { for (const path of paths) { try { await fs.access(path, fs.constants.R_OK); - return path + return path; } catch (error) { if (error.code !== 'ENOENT') throw error; } diff --git a/lib/buildtools/src/build/modules/__tests__/bundle.test.ts b/lib/buildtools/src/build/modules/__tests__/bundle.test.ts index b26dc00322..63fa9438bb 100644 --- a/lib/buildtools/src/build/modules/__tests__/bundle.test.ts +++ b/lib/buildtools/src/build/modules/__tests__/bundle.test.ts @@ -1,17 +1,17 @@ -import { buildBundle } from "../bundle" import fs from 'fs/promises'; +import { buildBundle } from '../bundle'; -const testMocksDir = `${__dirname}/../../__test_mocks__` +const testMocksDir = `${__dirname}/../../__test_mocks__`; -const written = [] +const written = []; jest.spyOn(fs, 'open').mockResolvedValue({ createWriteStream: () => ({ write: data => { - written.push(data) + written.push(data); } }), close: jest.fn() -} as any) +} as any); test('esbuild transpilation', async () => { await buildBundle({ @@ -19,12 +19,12 @@ test('esbuild transpilation', async () => { name: 'test0', directory: `${testMocksDir}/bundles/test0`, entryPoint: `${testMocksDir}/bundles/test0/index.ts`, - }, 'build') + }, 'build'); - const data = written.join('') + const data = written.join(''); // Trim the export default - const trimmed = (data as string).slice('export default'.length) + const trimmed = (data as string).slice('export default'.length); const provider = jest.fn((p: string) => { if (p === 'js-slang/context') { return { @@ -35,14 +35,14 @@ test('esbuild transpilation', async () => { } } } - } + }; } - throw new Error(`Dynamic require of ${p} is not supported!`) - }) + throw new Error(`Dynamic require of ${p} is not supported!`); + }); - const bundle = eval(trimmed)(provider) + const bundle = eval(trimmed)(provider); - expect(provider).toHaveBeenCalledTimes(1) - expect(bundle.test_function2()).toEqual('foo') -}) \ No newline at end of file + expect(provider).toHaveBeenCalledTimes(1); + expect(bundle.test_function2()).toEqual('foo'); +}); diff --git a/lib/buildtools/src/build/modules/bundle.ts b/lib/buildtools/src/build/modules/bundle.ts index e660fb1c63..29ac47f556 100644 --- a/lib/buildtools/src/build/modules/bundle.ts +++ b/lib/buildtools/src/build/modules/bundle.ts @@ -1,18 +1,18 @@ import fs from 'fs/promises'; import { build as esbuild } from 'esbuild'; -import { commonEsbuildOptions, outputBundleOrTab } from './commons'; import type { ResolvedBundle } from '../manifest'; +import { commonEsbuildOptions, outputBundleOrTab } from './commons'; export async function getBundleEntryPoint(bundleDir: string) { - let bundlePath = `${bundleDir}/src/index.ts` + let bundlePath = `${bundleDir}/src/index.ts`; try { - await fs.access(bundlePath, fs.constants.R_OK) - return bundlePath - } catch (error) { - bundlePath = `${bundleDir}/index.ts` - await fs.access(bundlePath, fs.constants.R_OK) - return bundlePath + await fs.access(bundlePath, fs.constants.R_OK); + return bundlePath; + } catch { + bundlePath = `${bundleDir}/index.ts`; + await fs.access(bundlePath, fs.constants.R_OK); + return bundlePath; } } diff --git a/lib/buildtools/src/build/modules/commons.ts b/lib/buildtools/src/build/modules/commons.ts index 2ad41fcf7e..db6412e3ce 100644 --- a/lib/buildtools/src/build/modules/commons.ts +++ b/lib/buildtools/src/build/modules/commons.ts @@ -55,7 +55,7 @@ export async function outputBundleOrTab({ text }: OutputFile, name: string, type } }; - await fs.mkdir(`${outDir}/${type}`, { recursive: true }) + await fs.mkdir(`${outDir}/${type}`, { recursive: true }); const file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); const writeStream = file.createWriteStream(); generate(output, { output: writeStream }); diff --git a/lib/buildtools/src/build/modules/index.ts b/lib/buildtools/src/build/modules/index.ts index 475182586a..590765f560 100644 --- a/lib/buildtools/src/build/modules/index.ts +++ b/lib/buildtools/src/build/modules/index.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises'; -import { buildBundle } from './bundle'; import type { ResolvedBundle } from '../manifest'; +import { buildBundle } from './bundle'; /** * Writes the module manifest to the output directory @@ -9,8 +9,8 @@ export async function writeManifest(manifests: Record, o const toWrite = Object.entries(manifests).reduce((res, [key, { manifest }]) => ({ ...res, [key]: manifest - }), {}) - await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(toWrite, null, 2)) + }), {}); + await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(toWrite, null, 2)); } /** @@ -20,8 +20,8 @@ export async function writeManifest(manifests: Record, o export async function buildBundles(manifests: Record, outDir: string) { await Promise.all(Object.values(manifests).map(async bundle => { await buildBundle(bundle, outDir); - })) + })); } export { buildBundle }; -export { buildTab, buildTabs } from './tab'; \ No newline at end of file +export { buildTab, buildTabs } from './tab'; diff --git a/lib/buildtools/src/build/modules/tab.ts b/lib/buildtools/src/build/modules/tab.ts index 367b08e900..8c1b55275a 100644 --- a/lib/buildtools/src/build/modules/tab.ts +++ b/lib/buildtools/src/build/modules/tab.ts @@ -1,7 +1,7 @@ -import pathlib from 'path' +import pathlib from 'path'; import { build as esbuild, type Plugin as ESBuildPlugin } from 'esbuild'; -import { commonEsbuildOptions, outputBundleOrTab } from './commons'; import { resolveAllBundles, resolvePaths, type ResolvedBundle } from '../manifest'; +import { commonEsbuildOptions, outputBundleOrTab } from './commons'; const tabContextPlugin: ESBuildPlugin = { name: 'Tab Context', @@ -21,52 +21,52 @@ interface ResolvedTab { } export async function resolveSingleTab(tabDir: string): Promise { - const fullyResolved = pathlib.resolve(tabDir) + const fullyResolved = pathlib.resolve(tabDir); const tabPath = await resolvePaths( `${fullyResolved}/src/index.tsx`, `${fullyResolved}/index.tsx` - ) + ); if (tabPath === undefined) { - throw new Error(`No tab found at ${fullyResolved}!`) + throw new Error(`No tab found at ${fullyResolved}!`); } return { directory: fullyResolved, entryPoint: tabPath, name: pathlib.basename(fullyResolved) - } + }; } export async function resolveAllTabs(bundlesDir: string, tabsDir: string) { - const bundlesManifest = await resolveAllBundles(bundlesDir) + const bundlesManifest = await resolveAllBundles(bundlesDir); const rawTabs = await Promise.all( Object.values(bundlesManifest).map(async ({ manifest }) => { - if (manifest.tabs) { - return Promise.all(manifest.tabs.map(async tabName => { - const resolvedTab = await resolveSingleTab(pathlib.join(tabsDir, tabName)) - return resolvedTab - })) - } + if (manifest.tabs) { + return Promise.all(manifest.tabs.map(async tabName => { + const resolvedTab = await resolveSingleTab(pathlib.join(tabsDir, tabName)); + return resolvedTab; + })); + } - return [] - })) + return []; + })); return rawTabs.reduce((res, tabs) => { return tabs.reduce((res, tab) => ({ ...res, [tab.name]: tab - }), res) - }, {} as Record) + }), res); + }, {} as Record); } /** * Build a tab at the given directory */ export async function buildTab(tabDir: string, outDir: string) { - const tab = await resolveSingleTab(tabDir) + const tab = await resolveSingleTab(tabDir); const { outputFiles: [result]} = await esbuild({ ...commonEsbuildOptions, @@ -89,8 +89,8 @@ export async function buildTabs(manifest: Record, outDir await Promise.all(Object.values(manifest).map(async ({ manifest: { tabs } }) => { if (tabs) { await Promise.all(tabs.map(async tabName => { - await buildTab(tabName, outDir) - })) + await buildTab(tabName, outDir); + })); } - })) + })); } diff --git a/lib/buildtools/src/commands/__tests__/main.test.ts b/lib/buildtools/src/commands/__tests__/main.test.ts index 4410daaa05..bfb7dc9156 100644 --- a/lib/buildtools/src/commands/__tests__/main.test.ts +++ b/lib/buildtools/src/commands/__tests__/main.test.ts @@ -1,9 +1,10 @@ -import { getMainCommand } from "../main" +import { getMainCommand } from '../main'; describe('Make sure that all subcommands can execute', () => { - const mainCommand = getMainCommand() + const mainCommand = getMainCommand(); + // eslint-disable-next-line jest/valid-title mainCommand.commands.map(command => test(command.name(), () => { return expect(command.parseAsync(['--help'], { from: 'user' })) .rejects - .toThrow() - })) -}) \ No newline at end of file + .toThrow(); + })); +}); diff --git a/lib/buildtools/src/commands/__tests__/prebuild.test.ts b/lib/buildtools/src/commands/__tests__/prebuild.test.ts new file mode 100644 index 0000000000..fbfa14aa99 --- /dev/null +++ b/lib/buildtools/src/commands/__tests__/prebuild.test.ts @@ -0,0 +1,124 @@ +import type { MockedFunction } from 'jest-mock'; +import * as tc from '../../prebuild/typecheck'; +import { getLintCommand, getTscCommand } from '../prebuild'; + +const lintFilesMock = jest.fn(); + +jest.mock('eslint', () => ({ + ...jest.requireActual('eslint'), + ESLint: class { + fix: boolean; + + constructor({ fix }) { + this.fix = fix; + } + + lintFiles = lintFilesMock; + loadFormatter = () => Promise.resolve({ + format: () => Promise.resolve('') + }); + static outputFixes = () => Promise.resolve(); + } +})); + +jest.spyOn(tc, 'getTsconfig'); + +describe('Test Lint Command', () => { + async function runCommand(...args: string[]) { + const command = getLintCommand(); + await command.parseAsync(args, { from: 'user' }); + } + + test('Fatal errors without --fix cause errors', async () => { + lintFilesMock.mockResolvedValueOnce([{ + fatalErrorCount: 1 + }]); + + await expect(runCommand()).rejects.toThrow('process.exit called with 1'); + }); + + test('Fatal errors with --fix cause errors', async () => { + lintFilesMock.mockResolvedValueOnce([{ + fatalErrorCount: 1 + }]); + + await expect(runCommand('--fix')).rejects.toThrow('process.exit called with 1'); + }); + + test('Non fatal errors without --fix cause errors', async () => { + lintFilesMock.mockResolvedValueOnce([{ + errorCount: 1 + }]); + + await expect(runCommand()).rejects.toThrow('process.exit called with 1'); + }); + + test('Non fatal errors with --fix does not call process.exit', async () => { + lintFilesMock.mockResolvedValueOnce([{ + errorCount: 1 + }]); + + await expect(runCommand('--fix')).resolves.not.toThrow(); + }); + + test('Warnings outside of ci mode should not error out', async () => { + lintFilesMock.mockResolvedValueOnce([{ + warningCount: 1, + fixableWarningCount: 0 + }]); + + await expect(runCommand()).resolves.not.toThrow(); + }); + + test('Warnings in ci mode should error out', async () => { + lintFilesMock.mockResolvedValueOnce([{ + warningCount: 1, + fixableWarningCount: 0 + }]); + + await expect(runCommand('--ci')).rejects.toThrow('process.exit called with 1'); + }); + + test('Fixable Warnings outside of ci mode should not error out', async () => { + lintFilesMock.mockResolvedValueOnce([{ + warningCount: 1, + fixableWarningCount: 1 + }]); + + await expect(runCommand()).resolves.not.toThrow(); + }); + + test('Fixable Warnings with --fix do not errors out', async () => { + lintFilesMock.mockResolvedValueOnce([{ + warningCount: 1, + fixableWarningCount: 1 + }]); + + await expect(runCommand('--fix')).resolves.not.toThrow(); + }); + + test('Fixable Warnings with --fix and --ci do not errors out', async () => { + lintFilesMock.mockResolvedValueOnce([{ + warningCount: 1, + fixableWarningCount: 1 + }]); + + await expect(runCommand('--fix', '--ci')).resolves.not.toThrow(); + }); +}); + +describe('Test Typecheck Command', () => { + const mockedGetTsconfig = tc.getTsconfig as MockedFunction; + + async function runCommand(...args: string[]) { + const command = getTscCommand(); + await command.parseAsync(args, { from: 'user' }); + + expect(tc.getTsconfig).toHaveBeenCalledTimes(1); + } + + test('tsc errors should error out', async () => { + mockedGetTsconfig.mockResolvedValueOnce({ severity: 'error' }); + await expect(runCommand()).rejects.toThrow('process.exit called with 1'); + }); +}); diff --git a/lib/buildtools/src/commands/__tests__/template.test.ts b/lib/buildtools/src/commands/__tests__/template.test.ts index 97f73b5b04..d1a4deac90 100644 --- a/lib/buildtools/src/commands/__tests__/template.test.ts +++ b/lib/buildtools/src/commands/__tests__/template.test.ts @@ -1,20 +1,15 @@ import fs from 'fs/promises'; -import type pathlib from 'path' import type { MockedFunction } from 'jest-mock'; -import getTemplateCommand from '../template'; +import * as manifest from '../../build/manifest'; import { askQuestion } from '../../templates/print'; -import * as manifest from '../../build/manifest' - -jest.mock('path', () => { - const actualPath: typeof pathlib = jest.requireActual('path') - return { - ...actualPath, - resolve: (...args: string[]) => { - return actualPath.resolve('/', ...args) - } - } -}) +import * as utils from '../../utils'; +import getTemplateCommand from '../template'; + +jest.spyOn(utils, 'getGitRoot').mockResolvedValue('/'); + +// Silence all the console logging +jest.spyOn(console, 'log').mockImplementation(() => {}); jest.mock('../../templates/print', () => ({ ...jest.requireActual('../../templates/print'), @@ -35,7 +30,7 @@ jest.mock('../../templates/print', () => ({ jest.spyOn(manifest, 'getBundleManifests').mockResolvedValue({ test0: { tabs: ['tab0'] }, test1: {}, -}) +}); const asMock = any>(func: T) => func as MockedFunction; @@ -92,27 +87,33 @@ describe('Test adding new module', () => { expectCall( fs.mkdir, - ['src/bundles/new_module', { recursive: true }] + ['/src/bundles', { recursive: true }] ); expectCall( fs.cp, [ - './src/', - 'src/bundles/new_module/index.ts' + expect.any(String), + '/src/bundles/new_module' ] ); - const oldManifest = await retrieveManifest('modules.json'); - const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; - expect(manifestPath) - .toEqual('modules.json'); + expect(fs.writeFile).toHaveBeenCalledTimes(2); + + const [ + [packagePath, rawPackage], + [manifestPath, rawManifest] + ] = asMock(fs.writeFile).mock.calls; + + expect(packagePath).toEqual('/src/bundles/new_module/package.json'); + const packageJson = JSON.parse(rawPackage as string); + expect(packageJson.name).toEqual('@sourceacademy/bundle-new_module'); - expect(JSON.parse(newManifest as string)) - .toMatchObject({ - ...oldManifest, - new_module: { tabs: [] } - }); + expect(manifestPath).toEqual('/src/bundles/new_module/manifest.json'); + const manifest = JSON.parse(rawManifest as string); + expect(manifest).toMatchObject({ + tabs: [] + }); }); }); @@ -146,27 +147,27 @@ describe('Test adding new tab', () => { expectCall( fs.mkdir, - ['src/tabs/TabNew', { recursive: true }] + ['/src/tabs', { recursive: true }] ); - expectCall( - fs.copyFile, - [ - './scripts/src/templates/templates/__tab__.tsx', - 'src/tabs/TabNew/index.tsx' - ] - ); + // Expect the entire template directory to be copied over + expectCall(fs.cp, [expect.any(String), '/src/tabs/TabNew']); + + const [ + [packagePath, packageJsonRaw], + [bundleManifestPath, manifestRaw] + ] = asMock(fs.writeFile).mock.calls; + + // Expect that a package json was created + expect(packagePath) + .toEqual('/src/tabs/TabNew/package.json'); + + const packageJson = JSON.parse(packageJsonRaw as string); + expect(packageJson.name).toEqual('@sourceacademy/tab-TabNew'); - const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; - expect(manifestPath) - .toEqual('modules.json'); - - expect(JSON.parse(newManifest as string)) - .toMatchObject({ - ...oldManifest, - test0: { - tabs: ['tab0', 'TabNew'] - } - }); + // Check that the corresponding bundle manifest was updated + expect(bundleManifestPath).toEqual('/src/bundles/test0/manifest.json'); + const manifest = JSON.parse(manifestRaw as string); + expect(manifest.tabs).toContain('TabNew'); }); }); diff --git a/lib/buildtools/src/commands/__tests__/testing.test.ts b/lib/buildtools/src/commands/__tests__/testing.test.ts index c2c8a8f2a8..9e22ef1dcb 100644 --- a/lib/buildtools/src/commands/__tests__/testing.test.ts +++ b/lib/buildtools/src/commands/__tests__/testing.test.ts @@ -1,10 +1,10 @@ import type { MockedFunction } from 'jest-mock'; -import getTestCommand from '../testing'; import { runJest } from '../../testing/runner'; +import getTestCommand from '../testing'; jest.mock('../../testing/runner', () => ({ runJest: jest.fn() -})) +})); const runCommand = (...args: string[]) => getTestCommand() .parseAsync(args, { from: 'user' }); @@ -13,7 +13,7 @@ const mockRunJest = runJest as MockedFunction; test('Check that the test command properly passes options to jest', async () => { await runCommand('-u', '0', '-w', './src/folder'); - expect(runJest).toHaveBeenCalledTimes(1) + expect(runJest).toHaveBeenCalledTimes(1); const [[args, patterns]] = mockRunJest.mock.calls; expect(args) @@ -24,7 +24,7 @@ test('Check that the test command properly passes options to jest', async () => test('Check that the test command handles windows paths as posix paths', async () => { await runCommand('.\\src\\folder'); - expect(runJest).toHaveBeenCalledTimes(1) + expect(runJest).toHaveBeenCalledTimes(1); const [call] = mockRunJest.mock.calls; expect(call[0]) diff --git a/lib/buildtools/src/commands/build.ts b/lib/buildtools/src/commands/build.ts index 23c7ce312b..52c388f8e7 100644 --- a/lib/buildtools/src/commands/build.ts +++ b/lib/buildtools/src/commands/build.ts @@ -1,63 +1,63 @@ -import fs from 'fs/promises' -import { Command } from "@commander-js/extra-typings"; -import { buildBundle, buildBundles, buildTab, buildTabs, writeManifest } from "../build/modules"; -import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; -import { initTypedocForSingleBundle } from '../build/docs/docsUtils'; +import fs from 'fs/promises'; +import { Command } from '@commander-js/extra-typings'; import { buildDocs, buildJson } from '../build/docs'; +import { initTypedocForSingleBundle } from '../build/docs/docsUtils'; +import { resolveAllBundles, resolveSingleBundle } from '../build/manifest'; +import { buildBundle, buildBundles, buildTab, buildTabs, writeManifest } from '../build/modules'; import { getBundlesDir, getOutDir } from '../utils'; -const outDir = await getOutDir() -const bundlesDir = await getBundlesDir() +const outDir = await getOutDir(); +const bundlesDir = await getBundlesDir(); export const getBuildBundleCommand = () => new Command('bundle') .argument('', 'Directory in which the bundle\'s source files are located') .action(async bundleDir => { - const bundle = await resolveSingleBundle(bundleDir) + const bundle = await resolveSingleBundle(bundleDir); if (!bundle) { - throw new Error(`No bundle found at ${bundleDir}!`) + throw new Error(`No bundle found at ${bundleDir}!`); } - await buildBundle(bundle, outDir) - }) + await buildBundle(bundle, outDir); + }); export const getBuildTabCommand = () => new Command('tab') .argument('', 'Directory in which the tab\'s source files are located') - .action(tabDir => buildTab(tabDir, outDir)) + .action(tabDir => buildTab(tabDir, outDir)); export const getBuildJsonCommand = () => new Command('json') .argument('', 'Directory in which the bundle\'s source files are located') .action(async bundleDir => { - const bundle = await resolveSingleBundle(bundleDir) - const reflection = await initTypedocForSingleBundle(bundle) - await fs.mkdir(`${outDir}/json`, { recursive: true }) - await buildJson(bundle.name, reflection, outDir) - }) + const bundle = await resolveSingleBundle(bundleDir); + const reflection = await initTypedocForSingleBundle(bundle); + await fs.mkdir(`${outDir}/json`, { recursive: true }); + await buildJson(bundle.name, reflection, outDir); + }); export const getBuildDocsCommand = () => new Command('docs') - .action(async () => { - const manifest = await resolveAllBundles(bundlesDir) - await buildDocs(manifest, outDir) - }) + .action(async () => { + const manifest = await resolveAllBundles(bundlesDir); + await buildDocs(manifest, outDir); + }); export const getBuildManifestCommand = () => new Command('manifest') .action(async () => { - const manifest = await resolveAllBundles(bundlesDir) - await fs.mkdir(outDir, { recursive: true }) - await writeManifest(manifest, outDir) - }) + const manifest = await resolveAllBundles(bundlesDir); + await fs.mkdir(outDir, { recursive: true }); + await writeManifest(manifest, outDir); + }); export const getBuildAllCommand = () => new Command('all') -.description('Build all bundles, tabs and documentation') + .description('Build all bundles, tabs and documentation') .action(async () => { - const manifest = await resolveAllBundles(bundlesDir) - await fs.mkdir(outDir, { recursive: true }) + const manifest = await resolveAllBundles(bundlesDir); + await fs.mkdir(outDir, { recursive: true }); await Promise.all([ writeManifest(manifest, outDir), buildDocs(manifest, outDir), buildBundles(manifest, outDir), buildTabs(manifest, outDir) - ]) - }) + ]); + }); export const getBuildCommand = () => new Command('build') .addCommand(getBuildAllCommand()) @@ -65,4 +65,4 @@ export const getBuildCommand = () => new Command('build') .addCommand(getBuildTabCommand()) .addCommand(getBuildJsonCommand()) .addCommand(getBuildDocsCommand()) - .addCommand(getBuildManifestCommand()) + .addCommand(getBuildManifestCommand()); diff --git a/lib/buildtools/src/commands/index.ts b/lib/buildtools/src/commands/index.ts index c0b3b96360..debe344ceb 100644 --- a/lib/buildtools/src/commands/index.ts +++ b/lib/buildtools/src/commands/index.ts @@ -1,3 +1,3 @@ -import { getMainCommand } from "./main"; +import { getMainCommand } from './main'; -await getMainCommand().parseAsync() \ No newline at end of file +await getMainCommand().parseAsync(); diff --git a/lib/buildtools/src/commands/list.ts b/lib/buildtools/src/commands/list.ts index 1308745537..b0b647642d 100644 --- a/lib/buildtools/src/commands/list.ts +++ b/lib/buildtools/src/commands/list.ts @@ -1,29 +1,29 @@ -import { Command } from "@commander-js/extra-typings"; -import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; -import { getBundlesDir, getTabsDir } from "../utils"; -import chalk from "chalk"; -import { resolveAllTabs, resolveSingleTab } from "../build/modules/tab"; +import { Command } from '@commander-js/extra-typings'; +import chalk from 'chalk'; +import { resolveAllBundles, resolveSingleBundle } from '../build/manifest'; +import { resolveAllTabs, resolveSingleTab } from '../build/modules/tab'; +import { getBundlesDir, getTabsDir } from '../utils'; export const getListBundlesCommand = () => new Command('bundle') .description('Lists all the bundles present or the information for a specific bundle in a given directory') .argument('[directory]') .action(async directory => { if (directory === undefined) { - const bundlesDir = await getBundlesDir() - const manifest = await resolveAllBundles(bundlesDir) - const bundleNames = Object.keys(manifest) + const bundlesDir = await getBundlesDir(); + const manifest = await resolveAllBundles(bundlesDir); + const bundleNames = Object.keys(manifest); if (bundleNames.length > 0 ) { - const bundlesStr = bundleNames.map((each, i) => `${i+1}. ${each}`).join('\n') - console.log(`${chalk.magentaBright(`Detected ${bundleNames.length} bundles in ${bundlesDir}:`)}\n${bundlesStr}`) + const bundlesStr = bundleNames.map((each, i) => `${i+1}. ${each}`).join('\n'); + console.log(`${chalk.magentaBright(`Detected ${bundleNames.length} bundles in ${bundlesDir}:`)}\n${bundlesStr}`); } else { - console.log(chalk.redBright(`No bundles in ${bundlesDir}`)) + console.log(chalk.redBright(`No bundles in ${bundlesDir}`)); } } else { - const manifest = await resolveSingleBundle(directory) - console.log(chalk.magentaBright(`Bundle '${manifest.name}' found in ${directory}`)) + const manifest = await resolveSingleBundle(directory); + console.log(chalk.magentaBright(`Bundle '${manifest.name}' found in ${directory}`)); } - }) + }); export const getListTabsCommand = () => new Command('tabs') .description('Lists all the tabs present or the information for a specific tab in a given directory') @@ -33,24 +33,23 @@ export const getListTabsCommand = () => new Command('tabs') const [bundlesDir, tabsDir] = await Promise.all([ getBundlesDir(), getTabsDir() - ]) + ]); - const tabsManifest = await resolveAllTabs(bundlesDir, tabsDir) - const tabNames = Object.keys(tabsManifest) + const tabsManifest = await resolveAllTabs(bundlesDir, tabsDir); + const tabNames = Object.keys(tabsManifest); if (tabNames.length > 0) { - const tabsStr = tabNames.map((each, i) => `${i+1}. ${each}`).join('\n') - console.log(`${chalk.magentaBright(`Detected ${tabNames.length} bundles in ${tabsDir}:`)}\n${tabsStr}`) + const tabsStr = tabNames.map((each, i) => `${i+1}. ${each}`).join('\n'); + console.log(`${chalk.magentaBright(`Detected ${tabNames.length} bundles in ${tabsDir}:`)}\n${tabsStr}`); } else { - console.log(chalk.redBright(`No tabs in ${tabsDir}`)) + console.log(chalk.redBright(`No tabs in ${tabsDir}`)); } } else { - const resolvedTab = await resolveSingleTab(directory) - console.log(chalk.magentaBright(`Tab '${resolvedTab.name}' found in ${directory}`)) + const resolvedTab = await resolveSingleTab(directory); + console.log(chalk.magentaBright(`Tab '${resolvedTab.name}' found in ${directory}`)); } - }) - + }); export const getListCommand = () => new Command('list') .addCommand(getListBundlesCommand()) - .addCommand(getListTabsCommand()) \ No newline at end of file + .addCommand(getListTabsCommand()); diff --git a/lib/buildtools/src/commands/main.ts b/lib/buildtools/src/commands/main.ts index d554ec2439..226dd2f959 100644 --- a/lib/buildtools/src/commands/main.ts +++ b/lib/buildtools/src/commands/main.ts @@ -1,9 +1,9 @@ -import { Command } from "@commander-js/extra-typings"; -import { getBuildCommand } from "./build"; -import getTemplateCommand from "./template"; -import getTestCommand from "./testing"; -import { getLintCommand, getTscCommand } from "./prebuild"; -import { getListCommand } from "./list"; +import { Command } from '@commander-js/extra-typings'; +import { getBuildCommand } from './build'; +import { getListCommand } from './list'; +import { getLintCommand, getTscCommand } from './prebuild'; +import getTemplateCommand from './template'; +import getTestCommand from './testing'; export const getMainCommand = () => new Command() .addCommand(getBuildCommand()) @@ -11,4 +11,4 @@ export const getMainCommand = () => new Command() .addCommand(getListCommand()) .addCommand(getTemplateCommand()) .addCommand(getTestCommand()) - .addCommand(getTscCommand()) \ No newline at end of file + .addCommand(getTscCommand()); diff --git a/lib/buildtools/src/commands/prebuild.ts b/lib/buildtools/src/commands/prebuild.ts index 79c6773f5a..b2dcc7b7e6 100644 --- a/lib/buildtools/src/commands/prebuild.ts +++ b/lib/buildtools/src/commands/prebuild.ts @@ -1,49 +1,57 @@ -import { Command } from "@commander-js/extra-typings"; -import { runEslint } from "../prebuild/lint"; -import type { AwaitedReturn } from "../utils"; -import { runTsc } from "../prebuild/typecheck"; import pathlib from 'path'; -import chalk from "chalk"; -import ts from 'typescript' +import { Command } from '@commander-js/extra-typings'; +import chalk from 'chalk'; +import ts from 'typescript'; +import { runEslint } from '../prebuild/lint'; +import { runTsc } from '../prebuild/typecheck'; +import { getGitRoot } from '../utils'; export const getLintCommand = () => new Command('lint') - .argument('') + .description('Run ESLint for the given directory, or the entire repository if no directory is specified') + .argument('[directory]') .option('--fix') - .action(async (directory, { fix }) => { - const results = await runEslint(directory, fix) - console.log(eslintResultsLogger(results)) - }) + .option('--ci') + .action(async (directory, { fix, ci }) => { + const fullyResolved = directory === undefined ? await getGitRoot() : pathlib.resolve(directory); + const { severity, formatted } = await runEslint(fullyResolved, fix); + let errStr: string; + + if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); + else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); + else errStr = chalk.greenBright('successfully'); + + console.log(`${chalk.cyanBright('Linting completed ')}${errStr}:\n${formatted}`); + + switch (severity) { + case 'warn': { + if (!ci) return; + } + case 'error': { + process.exit(1); + } + } + }); export const getTscCommand = () => new Command('typecheck') - .argument('') + .description('Run tsc for the given directory, or the entire repository if no directory is specified') + .argument('[directory]') .action(async directory => { - const results = await runTsc(directory) - console.log(tscResultsLogger(results)) - }) - -function tscResultsLogger(tscResult: AwaitedReturn) { - if (tscResult.severity === 'error' && tscResult.error) { - return `${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}: ${tscResult.error}`)}`; - } - - const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { - getNewLine: () => '\n', - getCurrentDirectory: () => process.cwd(), - getCanonicalFileName: name => pathlib.basename(name) + const fullyResolved = directory === undefined ? await getGitRoot() : pathlib.resolve(directory); + const tscResult = await runTsc(fullyResolved); + if (tscResult.severity === 'error' && tscResult.error) { + console.log(`${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}: ${tscResult.error}`)}`); + process.exit(1); + } + + const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { + getNewLine: () => '\n', + getCurrentDirectory: () => process.cwd(), + getCanonicalFileName: name => pathlib.basename(name) + }); + + if (tscResult.severity === 'error') { + console.log(`${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}}`)}`); + process.exit(1); + } + console.log(`${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')}`)}`); }); - - if (tscResult.severity === 'error') { - return `${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}}`)}`; - } - return `${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')}`)}`; -} - -function eslintResultsLogger({ formatted, severity }: AwaitedReturn) { - let errStr: string; - - if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); - else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); - else errStr = chalk.greenBright('successfully'); - - return `${chalk.cyanBright(`Linting completed:`)}\n${formatted}`; -} \ No newline at end of file diff --git a/lib/buildtools/src/commands/template.ts b/lib/buildtools/src/commands/template.ts index 77709b4c07..7fff5d7b4f 100644 --- a/lib/buildtools/src/commands/template.ts +++ b/lib/buildtools/src/commands/template.ts @@ -24,7 +24,7 @@ export default function getTemplateCommand() { const [bundlesDir, tabsDir] = await Promise.all([ getBundlesDir(), getTabsDir() - ]) + ]); const rl = getRl(); try { diff --git a/lib/buildtools/src/prebuild/lint.ts b/lib/buildtools/src/prebuild/lint.ts index 540857d341..c79bd218c4 100644 --- a/lib/buildtools/src/prebuild/lint.ts +++ b/lib/buildtools/src/prebuild/lint.ts @@ -1,5 +1,5 @@ -import { ESLint } from 'eslint' -import { findSeverity, type Severity } from '../utils'; +import { ESLint } from 'eslint'; +import { findSeverity, getGitRoot, type Severity } from '../utils'; interface LintResults { formatted: string @@ -7,7 +7,10 @@ interface LintResults { } export async function runEslint(directory: string, fix: boolean): Promise { - const linter = new ESLint({ fix }); + const linter = new ESLint({ + fix, + cwd: await getGitRoot() + }); try { const linterResults = await linter.lintFiles(directory); @@ -17,12 +20,18 @@ export async function runEslint(directory: string, fix: boolean): Promise { - if (fatalErrorCount > 0) return 'error' - if (!fix && errorCount > 0) return 'error' + const severity = findSeverity(linterResults, ({ warningCount, errorCount, fatalErrorCount, fixableWarningCount }) => { - if (warningCount > 0) return 'warn'; + if (fatalErrorCount > 0) return 'error'; + if (!fix && errorCount > 0) return 'error'; + + if (warningCount > 0) { + if (fix && fixableWarningCount === warningCount) { + return 'success'; + } + return 'warn'; + } return 'success'; }); @@ -36,4 +45,4 @@ export async function runEslint(directory: string, fix: boolean): Promise { +export async function getTsconfig(srcDir: string): Promise { // Step 1: Read the text from tsconfig.json const tsconfigLocation = pathlib.join(srcDir, 'tsconfig.json'); @@ -37,7 +38,7 @@ async function getTsconfig(srcDir: string): Promise { } // Step 3: Parse the json object into a config object for use by tsc - const { errors: parseErrors, options: tsconfig } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); + const { errors: parseErrors, options: tsconfig, fileNames } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); if (parseErrors.length > 0) { return { severity: 'error', @@ -47,7 +48,8 @@ async function getTsconfig(srcDir: string): Promise { return { severity: 'success', - results: tsconfig + compilerOptions: tsconfig, + fileNames }; } catch (error) { return { @@ -63,8 +65,10 @@ export async function runTsc(srcDir: string): Promise { return tsconfigRes; } + const { compilerOptions, fileNames } = tsconfigRes; + try { - const tsc = ts.createProgram([], tsconfigRes.results); + const tsc = ts.createProgram(fileNames, compilerOptions); const results = tsc.emit(); const diagnostics = ts.getPreEmitDiagnostics(tsc) .concat(results.diagnostics); diff --git a/lib/buildtools/src/templates/bundle.ts b/lib/buildtools/src/templates/bundle.ts index 41abe2d500..d4c225412c 100644 --- a/lib/buildtools/src/templates/bundle.ts +++ b/lib/buildtools/src/templates/bundle.ts @@ -1,9 +1,9 @@ import fs from 'fs/promises'; import type { Interface } from 'readline/promises'; +import _package from '../../../../package.json' with { type: 'json' }; +import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; import { askQuestion, success, warn } from './print'; import { check, isSnakeCase } from './utilities'; -import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; -import _package from '../../../../package.json' with { type: 'json' } async function askModuleName(manifest: ModulesManifest, rl: Interface) { while (true) { @@ -19,42 +19,42 @@ async function askModuleName(manifest: ModulesManifest, rl: Interface) { } export async function addNew(bundlesDir: string, rl: Interface) { - const manifest = await getBundleManifests(bundlesDir) + const manifest = await getBundleManifests(bundlesDir); const moduleName = await askModuleName(manifest, rl); const bundleDestination = `${bundlesDir}/${moduleName}`; - await fs.cp(`${__dirname}/templates/bundle`, bundleDestination) + await fs.mkdir(bundlesDir, { recursive: true }); + await fs.cp(`${__dirname}/templates/bundle`, bundleDestination); - const typescriptVersion = _package.devDependencies.typescript + const typescriptVersion = _package.devDependencies.typescript; const packageJson = { name: `@sourceacademy/bundle-${moduleName}`, private: true, - version: "1.0.0", + version: '1.0.0', devDependencies: { - "@sourceacademy/modules-buildtools": "workspace:^", + '@sourceacademy/modules-buildtools': 'workspace:^', typescript: typescriptVersion, }, - type: "module", + type: 'module', scripts: { - tsc: "tsc --project ./tsconfig.json", + tsc: 'tsc --project ./tsconfig.json', build: 'buildtools build bundle .' }, exports: { - ".": "./dist/index.js", - "./*": "./dist/*.js", - "./*.js": "./dist/*.js" + '.': './dist/index.js', + './*': './dist/*.js' } - } + }; const bundleManifest: BundleManifest = { tabs: [] - } + }; await Promise.all([ fs.writeFile(`${bundleDestination}/package.json`, JSON.stringify(packageJson, null, 2)), fs.writeFile(`${bundleDestination}/manifest.json`, JSON.stringify(bundleManifest, null, 2)) - ]) + ]); success(`Bundle for module ${moduleName} created at ${bundleDestination}.`); } diff --git a/lib/buildtools/src/templates/tab.ts b/lib/buildtools/src/templates/tab.ts index 9c04124d6c..465208b880 100644 --- a/lib/buildtools/src/templates/tab.ts +++ b/lib/buildtools/src/templates/tab.ts @@ -1,9 +1,9 @@ import fs from 'fs/promises'; import type { Interface } from 'readline/promises'; +import _package from '../../../../package.json' with { type: 'json' }; +import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; import { askQuestion, success, warn } from './print'; import { check, isPascalCase } from './utilities'; -import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; -import _package from '../../../../package.json' with { type: 'json' } async function askModuleName(manifest: ModulesManifest, rl: Interface) { while (true) { @@ -17,7 +17,7 @@ async function askModuleName(manifest: ModulesManifest, rl: Interface) { } function checkTabExists(manifest: ModulesManifest, name: string) { - return Object.values(manifest).flatMap(x => x.tabs).includes(name) + return Object.values(manifest).flatMap(x => x.tabs).includes(name); } async function askTabName(manifest: ModulesManifest, rl: Interface) { @@ -34,36 +34,34 @@ async function askTabName(manifest: ModulesManifest, rl: Interface) { } export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) { - const manifest = await getBundleManifests(bundlesDir) + const manifest = await getBundleManifests(bundlesDir); const moduleName = await askModuleName(manifest, rl); const tabName = await askTabName(manifest, rl); - // Copy module tab template into correct destination and show success message - const tabDestination = `${tabsDir}/${tabName}`; - await fs.mkdir(tabDestination, { recursive: true }); + await fs.mkdir(tabsDir, { recursive: true }); - const reactVersion = _package.dependencies.react + const reactVersion = _package.dependencies.react; const { - "@types/react": reactTypesVersion, + '@types/react': reactTypesVersion, typescript: typescriptVersion - } = _package.devDependencies + } = _package.devDependencies; const packageJson = { name: `@sourceacademy/tab-${tabName}`, private: true, - version: "1.0.0", + version: '1.0.0', devDependencies: { - "@sourceacademy/modules-buildtools": "workspace:^", - "@types/react": reactTypesVersion, - "typescript": typescriptVersion, + '@sourceacademy/modules-buildtools': 'workspace:^', + '@types/react': reactTypesVersion, + 'typescript': typescriptVersion, }, dependencies: { react: reactVersion, }, scripts: { - "build": "buildtools build tab ." + 'build': 'buildtools build tab .' } - } + }; const newManifest: BundleManifest = { ...manifest[moduleName], @@ -71,13 +69,14 @@ export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) ...manifest[moduleName].tabs, tabName ] - } + }; - await fs.cp(`${__dirname}/templates/tabs`, tabDestination) + const tabDestination = `${tabsDir}/${tabName}`; + await fs.cp(`${__dirname}/templates/tabs`, tabDestination); await Promise.all([ fs.writeFile(`${tabDestination}/package.json`, JSON.stringify(packageJson, null, 2)), fs.writeFile(`${bundlesDir}/${moduleName}/manifest.json`, JSON.stringify(newManifest, null, 2)) - ]) + ]); success( `Tab ${tabName} for module ${moduleName} created at ${tabDestination}.` diff --git a/lib/buildtools/src/templates/utilities.ts b/lib/buildtools/src/templates/utilities.ts index 274ddc1ea7..ee52c8c345 100644 --- a/lib/buildtools/src/templates/utilities.ts +++ b/lib/buildtools/src/templates/utilities.ts @@ -1,7 +1,7 @@ // Snake case regex has been changed from `/\b[a-z]+(?:_[a-z]+)*\b/u` to `/\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u` // to be consistent with the naming of the `arcade_2d` and `physics_2d` modules. -import type { ModulesManifest } from "../build/manifest"; +import type { ModulesManifest } from '../build/manifest'; // This change should not affect other modules, since the set of possible names is only expanded. const snakeCaseRegex = /\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u; @@ -16,5 +16,5 @@ export function isPascalCase(string: string) { } export function check(manifest: ModulesManifest, name: string) { - return Object.keys(manifest).includes(name) -} \ No newline at end of file + return Object.keys(manifest).includes(name); +} diff --git a/lib/buildtools/src/testing/runner.ts b/lib/buildtools/src/testing/runner.ts index 9736bc204b..908c3d67e7 100644 --- a/lib/buildtools/src/testing/runner.ts +++ b/lib/buildtools/src/testing/runner.ts @@ -1,8 +1,8 @@ -import pathlib from 'path' +import pathlib from 'path'; import jest from 'jest'; export function runJest(jestArgs: string[], patterns: string[]) { - const filePatterns = patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)) + const filePatterns = patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)); return jest.run([ ...jestArgs, ...filePatterns diff --git a/lib/buildtools/src/utils.ts b/lib/buildtools/src/utils.ts index c82e6b32f2..60c9c67978 100644 --- a/lib/buildtools/src/utils.ts +++ b/lib/buildtools/src/utils.ts @@ -1,43 +1,43 @@ -import { execFile } from "child_process" -import _ from "lodash" -import pathlib from "path" +import { execFile } from 'child_process'; +import pathlib from 'path'; +import _ from 'lodash'; function rawGetGitRoot() { return new Promise((resolve, reject) => { execFile('git', ['rev-parse', '--show-toplevel'], (err, stdout, stderr) => { - const possibleError = err || stderr + const possibleError = err || stderr; if (possibleError) { - reject(possibleError) + reject(possibleError); } - resolve(stdout.trim()) - }) - }) + resolve(stdout.trim()); + }); + }); } /** * Get the path to the root of the git repository */ -export const getGitRoot = _.memoize(rawGetGitRoot) -export const getBundlesDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'bundles')) -export const getTabsDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'tabs')) -export const getOutDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'build')) +export const getGitRoot = _.memoize(rawGetGitRoot); +export const getBundlesDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'bundles')); +export const getTabsDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'tabs')); +export const getOutDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'build')); -export type AwaitedReturn Promise> = Awaited> +export type AwaitedReturn Promise> = Awaited>; -export type Severity = 'success' | 'warn' | 'error' +export type Severity = 'error' | 'success' | 'warn'; export function findSeverity(items: T[], mapper: (each: T) => Severity): Severity { - let output: Severity = 'success' + let output: Severity = 'success'; for (const item of items) { - const severity = mapper(item) - if (severity === 'error') return 'error' + const severity = mapper(item); + if (severity === 'error') return 'error'; if (severity === 'warn') { - output = 'warn' + output = 'warn'; } } return output; } -export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFixed(2); \ No newline at end of file +export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFixed(2); diff --git a/lib/buildtools/workspacer.py b/lib/buildtools/workspacer.py index 9e53c95ae4..08dcf3fe7e 100644 --- a/lib/buildtools/workspacer.py +++ b/lib/buildtools/workspacer.py @@ -4,14 +4,21 @@ (say if a new script needed to be added) """ +import asyncio as aio import os import json -def get_tabs(): - for name in os.listdir('./src/tabs'): +async def get_git_root(): + proc = await aio.create_subprocess_exec('git', 'rev-parse', '--show-toplevel', stdout=aio.subprocess.PIPE) + stdout, _ = await proc.communicate() + return stdout.decode() + +def get_tabs(git_root: str): + for name in os.listdir(f'{git_root}/src/tabs'): + full_path = os.path.join(git_root, 'src', 'tabs', name) if not os.path.isdir(f'./src/tabs/{name}'): continue - yield name + yield name, full_path def get_bundles(): for name in os.listdir('./src/bundles'): @@ -22,14 +29,11 @@ def get_bundles(): continue yield name -def update_tab_packages(): - for name in get_tabs(): +def update_tab_packages(git_root: str): + for name, full_path in get_tabs(git_root): with open(f'./src/tabs/{name}/package.json') as file: original = json.load(file) - - if '@sourceacademy/module-buildtools' in original['devDependencies']: - del original['devDependencies']['@sourceacademy/module-buildtools'] - original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" + original['dependencies']['@sourceacademy/modules-lib'] = 'workspace:^' with open(f'./src/tabs/{name}/package.json', 'w') as file: json.dump(original, file, indent=2) @@ -88,10 +92,13 @@ def update_bundle_packages(): with open(f'./src/bundles/{name}/package.json', 'w') as file: json.dump(original, file, indent=2) +async def main(): + git_root = await get_git_root() + if __name__ == '__main__': # create_bundle_manifest() - update_bundle_packages() + # update_bundle_packages() # update_bundle_tsconfigs() - # update_tab_packages() + aio.run(main()) # update_tab_tsconfigs() \ No newline at end of file From 5c6db3d9813b1bf9bbdbdc63871beebf241222bd Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 22 May 2025 12:28:18 +0800 Subject: [PATCH 019/112] Use defineTab --- src/{modules-lib => }/.gitignore | 0 src/__mocks__/context.ts | 5 - src/__mocks__/emptyModule.js | 1 - src/buildtools/.gitignore | 1 - src/buildtools/bin/docsreadme.md | 18 -- .../bin/templates/bundle/src/index.ts | 25 --- .../bin/templates/bundle/tsconfig.json | 4 - src/buildtools/bin/templates/tab/index.tsx | 71 ------- .../bin/templates/tab/tsconfig.json | 4 - src/buildtools/build.js | 25 --- src/buildtools/jest.config.js | 16 -- src/buildtools/jest.setup.ts | 18 -- src/buildtools/package.json | 37 ---- .../src/build/docs/__mocks__/docsUtils.ts | 16 -- .../src/build/docs/__tests__/building.test.ts | 52 ----- .../src/build/docs/__tests__/docs.test.ts | 49 ----- .../src/build/docs/__tests__/json.test.ts | 42 ---- .../test_mocks/bundles/test0/index.ts | 8 - .../test_mocks/bundles/test1/index.ts | 4 - .../docs/__tests__/test_mocks/tsconfig.json | 45 ----- src/buildtools/src/build/docs/docsUtils.ts | 51 ----- src/buildtools/src/build/docs/drawdown.ts | 190 ------------------ src/buildtools/src/build/docs/index.ts | 39 ---- src/buildtools/src/build/docs/json.ts | 69 ------- src/buildtools/src/build/manifest.ts | 133 ------------ src/buildtools/src/build/modules/bundle.ts | 31 --- src/buildtools/src/build/modules/commons.ts | 62 ------ src/buildtools/src/build/modules/index.ts | 27 --- .../src/build/modules/manifest.schema.json | 23 --- src/buildtools/src/build/modules/tab.ts | 86 -------- .../src/commands/__tests__/template.test.ts | 155 -------------- .../src/commands/__tests__/testing.test.ts | 28 --- src/buildtools/src/commands/build.ts | 68 ------- src/buildtools/src/commands/index.ts | 3 - src/buildtools/src/commands/list.ts | 28 --- src/buildtools/src/commands/main.ts | 14 -- src/buildtools/src/commands/prebuild.ts | 49 ----- src/buildtools/src/commands/template.ts | 41 ---- src/buildtools/src/commands/testing.ts | 16 -- src/buildtools/src/commands/utils.ts | 71 ------- src/buildtools/src/prebuild/lint.ts | 39 ---- src/buildtools/src/prebuild/typecheck.ts | 82 -------- .../src/templates/__tests__/names.test.ts | 22 -- src/buildtools/src/templates/bundle.ts | 57 ------ src/buildtools/src/templates/print.ts | 27 --- src/buildtools/src/templates/tab.ts | 82 -------- src/buildtools/src/templates/utilities.ts | 20 -- src/buildtools/src/testing/runner.ts | 10 - src/buildtools/src/utils.ts | 43 ---- src/buildtools/tsconfig.json | 16 -- src/buildtools/workspacer.py | 97 --------- src/bundles/__mocks__/context.ts | 2 +- src/bundles/__mocks__/emptyModule.ts | 2 +- src/bundles/csg/package.json | 3 +- src/bundles/jest.config.js | 2 +- src/bundles/jest.polyfills.js | 3 +- src/bundles/physics_2d/src/types.ts | 2 +- .../engine/__tests__/Render/MeshFactory.ts | 2 +- src/bundles/rune/src/display.ts | 2 +- src/bundles/rune/src/functions.ts | 2 +- src/bundles/rune/src/rune.ts | 2 +- src/commons/__tests__/hextocolor.ts | 17 -- src/commons/js-slang/context.d.ts | 4 - src/commons/specialErrors.ts | 8 - src/commons/type_map/index.ts | 69 ------- src/commons/types.ts | 46 ----- src/commons/utilities.ts | 16 -- src/jest.config.js | 35 ---- src/jest.polyfills.js | 8 - src/lintplugin/package.json | 38 ---- src/lintplugin/src/configs.ts | 78 ------- src/lintplugin/src/index.ts | 18 -- .../src/rules/__tests__/modulestag.test.ts | 54 ----- .../src/rules/__tests__/typeimports.test.ts | 35 ---- src/lintplugin/src/rules/moduleTag.ts | 56 ------ src/lintplugin/src/rules/tabType.ts | 78 ------- src/lintplugin/src/rules/typeimports.ts | 55 ----- src/lintplugin/tsconfig.json | 12 -- src/modules-lib/package.json | 20 +- src/modules-lib/src/tabs/utils.ts | 29 +++ .../src/types/js-slang/context.d.ts | 2 +- src/modules-lib/src/utilities.ts | 12 ++ src/modules-lib/tsconfig.json | 6 +- .../tsconfig.prod.json | 5 +- src/tabs/ArcadeTwod/index.tsx | 34 +--- src/tabs/ArcadeTwod/package.json | 6 +- src/tabs/AugmentedReality/package.json | 8 +- .../AugmentedReality/src/AugmentedContent.tsx | 10 +- .../AugmentedReality/src/AugmentedLayer.tsx | 2 +- src/tabs/AugmentedReality/src/index.tsx | 34 +--- src/tabs/CopyGc/package.json | 6 +- src/tabs/CopyGc/src/index.tsx | 7 +- src/tabs/Csg/package.json | 5 +- src/tabs/Csg/src/index.tsx | 7 +- src/tabs/Curve/package.json | 8 +- src/tabs/Curve/src/__tests__/Curve.tsx | 2 +- .../Curve/src/animation_canvas_3d_curve.tsx | 2 +- src/tabs/Curve/src/canvas_3d_curve.tsx | 4 +- src/tabs/Curve/src/index.tsx | 11 +- src/tabs/Game/package.json | 6 +- src/tabs/Game/src/index.tsx | 5 +- src/tabs/MarkSweep/package.json | 6 +- src/tabs/MarkSweep/src/index.tsx | 5 +- src/tabs/Nbody/index.tsx | 31 +-- src/tabs/Nbody/package.json | 6 +- src/tabs/Painter/index.tsx | 10 +- src/tabs/Painter/package.json | 5 +- src/tabs/Painter/tsconfig.json | 4 + src/tabs/Physics2D/package.json | 6 +- src/tabs/Physics2D/src/index.tsx | 30 +-- src/tabs/Pixnflix/index.tsx | 5 +- src/tabs/Pixnflix/package.json | 6 +- src/tabs/Plotly/index.tsx | 10 +- src/tabs/Plotly/package.json | 5 +- src/tabs/Repeat/index.tsx | 5 +- src/tabs/Repeat/package.json | 6 +- src/tabs/Repl/index.tsx | 32 +-- src/tabs/Repl/package.json | 5 +- src/tabs/RobotSimulation/package.json | 6 +- src/tabs/RobotSimulation/src/index.tsx | 34 +--- src/tabs/Rune/package.json | 5 +- src/tabs/Rune/src/__tests__/Rune.tsx | 9 +- src/tabs/Rune/src/index.tsx | 32 +-- src/tabs/Sound/index.tsx | 26 +-- src/tabs/Sound/package.json | 5 +- src/tabs/SoundMatrix/index.tsx | 31 +-- src/tabs/SoundMatrix/package.json | 7 +- src/tabs/StereoSound/index.tsx | 32 +-- src/tabs/StereoSound/package.json | 5 +- src/tabs/Unittest/index.tsx | 33 +-- src/tabs/Unittest/package.json | 5 +- src/tabs/UnityAcademy/index.tsx | 31 +-- src/tabs/UnityAcademy/package.json | 6 +- src/tabs/package.json | 2 +- src/tabs/tsconfig.json | 4 +- src/tsconfig.json | 18 +- 136 files changed, 272 insertions(+), 3196 deletions(-) rename src/{modules-lib => }/.gitignore (100%) delete mode 100644 src/__mocks__/context.ts delete mode 100644 src/__mocks__/emptyModule.js delete mode 100644 src/buildtools/.gitignore delete mode 100644 src/buildtools/bin/docsreadme.md delete mode 100644 src/buildtools/bin/templates/bundle/src/index.ts delete mode 100644 src/buildtools/bin/templates/bundle/tsconfig.json delete mode 100644 src/buildtools/bin/templates/tab/index.tsx delete mode 100644 src/buildtools/bin/templates/tab/tsconfig.json delete mode 100644 src/buildtools/build.js delete mode 100644 src/buildtools/jest.config.js delete mode 100644 src/buildtools/jest.setup.ts delete mode 100644 src/buildtools/package.json delete mode 100644 src/buildtools/src/build/docs/__mocks__/docsUtils.ts delete mode 100644 src/buildtools/src/build/docs/__tests__/building.test.ts delete mode 100644 src/buildtools/src/build/docs/__tests__/docs.test.ts delete mode 100644 src/buildtools/src/build/docs/__tests__/json.test.ts delete mode 100644 src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts delete mode 100644 src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts delete mode 100644 src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json delete mode 100644 src/buildtools/src/build/docs/docsUtils.ts delete mode 100644 src/buildtools/src/build/docs/drawdown.ts delete mode 100644 src/buildtools/src/build/docs/index.ts delete mode 100644 src/buildtools/src/build/docs/json.ts delete mode 100644 src/buildtools/src/build/manifest.ts delete mode 100644 src/buildtools/src/build/modules/bundle.ts delete mode 100644 src/buildtools/src/build/modules/commons.ts delete mode 100644 src/buildtools/src/build/modules/index.ts delete mode 100644 src/buildtools/src/build/modules/manifest.schema.json delete mode 100644 src/buildtools/src/build/modules/tab.ts delete mode 100644 src/buildtools/src/commands/__tests__/template.test.ts delete mode 100644 src/buildtools/src/commands/__tests__/testing.test.ts delete mode 100644 src/buildtools/src/commands/build.ts delete mode 100644 src/buildtools/src/commands/index.ts delete mode 100644 src/buildtools/src/commands/list.ts delete mode 100644 src/buildtools/src/commands/main.ts delete mode 100644 src/buildtools/src/commands/prebuild.ts delete mode 100644 src/buildtools/src/commands/template.ts delete mode 100644 src/buildtools/src/commands/testing.ts delete mode 100644 src/buildtools/src/commands/utils.ts delete mode 100644 src/buildtools/src/prebuild/lint.ts delete mode 100644 src/buildtools/src/prebuild/typecheck.ts delete mode 100644 src/buildtools/src/templates/__tests__/names.test.ts delete mode 100644 src/buildtools/src/templates/bundle.ts delete mode 100644 src/buildtools/src/templates/print.ts delete mode 100644 src/buildtools/src/templates/tab.ts delete mode 100644 src/buildtools/src/templates/utilities.ts delete mode 100644 src/buildtools/src/testing/runner.ts delete mode 100644 src/buildtools/src/utils.ts delete mode 100644 src/buildtools/tsconfig.json delete mode 100644 src/buildtools/workspacer.py delete mode 100644 src/commons/__tests__/hextocolor.ts delete mode 100644 src/commons/js-slang/context.d.ts delete mode 100644 src/commons/specialErrors.ts delete mode 100644 src/commons/type_map/index.ts delete mode 100644 src/commons/types.ts delete mode 100644 src/commons/utilities.ts delete mode 100644 src/jest.config.js delete mode 100644 src/jest.polyfills.js delete mode 100644 src/lintplugin/package.json delete mode 100644 src/lintplugin/src/configs.ts delete mode 100644 src/lintplugin/src/index.ts delete mode 100644 src/lintplugin/src/rules/__tests__/modulestag.test.ts delete mode 100644 src/lintplugin/src/rules/__tests__/typeimports.test.ts delete mode 100644 src/lintplugin/src/rules/moduleTag.ts delete mode 100644 src/lintplugin/src/rules/tabType.ts delete mode 100644 src/lintplugin/src/rules/typeimports.ts delete mode 100644 src/lintplugin/tsconfig.json rename src/{lintplugin => modules-lib}/tsconfig.prod.json (62%) diff --git a/src/modules-lib/.gitignore b/src/.gitignore similarity index 100% rename from src/modules-lib/.gitignore rename to src/.gitignore diff --git a/src/__mocks__/context.ts b/src/__mocks__/context.ts deleted file mode 100644 index 762e1bd1d6..0000000000 --- a/src/__mocks__/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - moduleContexts: new Proxy({}, { - get: () => ({ state: {} }) - }) -}; diff --git a/src/__mocks__/emptyModule.js b/src/__mocks__/emptyModule.js deleted file mode 100644 index f053ebf797..0000000000 --- a/src/__mocks__/emptyModule.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/src/buildtools/.gitignore b/src/buildtools/.gitignore deleted file mode 100644 index 55a319dd92..0000000000 --- a/src/buildtools/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin/index.js \ No newline at end of file diff --git a/src/buildtools/bin/docsreadme.md b/src/buildtools/bin/docsreadme.md deleted file mode 100644 index 0adf8f5beb..0000000000 --- a/src/buildtools/bin/docsreadme.md +++ /dev/null @@ -1,18 +0,0 @@ -# Overview - -The Source Academy allows programmers to import functions and constants from a module, using JavaScript's `import` directive. For example, the programmer may decide to import the function `thrice` from the module `repeat` by starting the program with -``` -import { thrice } from "repeat"; -``` - -When evaluating such a directive, the Source Academy looks for a module with the matching name, here `repeat`, in a preconfigured modules site. The Source Academy at https://sourceacademy.org uses the default modules site (located at https://source-academy.github.io/modules). - -After importing functions or constants from a module, they can be used as usual. -``` -thrice(display)(8); // displays 8 three times -``` -if `thrice` is declared in the module `repeat` as follows: -``` -const thrice = f => x => f(f(f(x))); -``` -[List of modules](modules.html) available at the default modules site. \ No newline at end of file diff --git a/src/buildtools/bin/templates/bundle/src/index.ts b/src/buildtools/bin/templates/bundle/src/index.ts deleted file mode 100644 index 297cca4d76..0000000000 --- a/src/buildtools/bin/templates/bundle/src/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * A single sentence summarising the module (this sentence is displayed larger). - * - * Sentences describing the module. More sentences about the module. - * - * @module module_name - * @author Author Name - * @author Author Name - */ - -/* - To access things like the context or module state you can just import the context - using the import below - */ -import context from 'js-slang/context'; - -/** - * Sample function. Increments a number by 1. - * - * @param x The number to be incremented. - * @returns The incremented value of the number. - */ -export function sample_function(x: number): number { - return ++x; -} // Then any functions or variables you want to expose to the user is exported from the bundle's index.ts file \ No newline at end of file diff --git a/src/buildtools/bin/templates/bundle/tsconfig.json b/src/buildtools/bin/templates/bundle/tsconfig.json deleted file mode 100644 index 2536da7baf..0000000000 --- a/src/buildtools/bin/templates/bundle/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends" : "../tsconfig.json", - "include": ["./src"] -} \ No newline at end of file diff --git a/src/buildtools/bin/templates/tab/index.tsx b/src/buildtools/bin/templates/tab/index.tsx deleted file mode 100644 index 34eb95b091..0000000000 --- a/src/buildtools/bin/templates/tab/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; - -/** - * - * @author - * @author - */ - -/** - * React Component props for the Tab. - */ -type Props = { - children?: never; - className?: never; - context?: any; -}; - -/** - * React Component state for the Tab. - */ -type State = { - counter: number; -}; - -/** - * The main React Component of the Tab. - */ -class Repeat extends React.Component { - constructor(props) { - super(props); - this.state = { - counter: 0, - }; - } - - public render() { - const { counter } = this.state; - return ( -
    This is spawned from the repeat package. Counter is {counter}
    - ); - } -} - -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn: (context: any) => context.result.value === 'test', - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: any) => , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ - label: 'Sample Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ - iconName: 'build', -}; \ No newline at end of file diff --git a/src/buildtools/bin/templates/tab/tsconfig.json b/src/buildtools/bin/templates/tab/tsconfig.json deleted file mode 100644 index 8e4645929c..0000000000 --- a/src/buildtools/bin/templates/tab/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": ["./index.tsx" ] -} \ No newline at end of file diff --git a/src/buildtools/build.js b/src/buildtools/build.js deleted file mode 100644 index 34688f50fa..0000000000 --- a/src/buildtools/build.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Script for building buildtools - */ - -// @ts-check -import { Command } from '@commander-js/extra-typings' -import { build } from 'esbuild' - -const command = new Command() - .option('--dev', 'If specified, the built output is not minified for easier debugging') - .action(async ({ dev }) => { - await build({ - entryPoints: ['./src/commands/index.ts'], - bundle: true, - format: 'esm', - minify: !dev, - outfile: './bin/index.js', - packages: 'external', - platform: 'node', - target: 'node20', - tsconfig: './tsconfig.json' - }) - }) - -await command.parseAsync() diff --git a/src/buildtools/jest.config.js b/src/buildtools/jest.config.js deleted file mode 100644 index 8e4647ed8f..0000000000 --- a/src/buildtools/jest.config.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @type {import('jest').Config} - */ -const jestConfig = { - clearMocks: true, - displayName: 'Build Tools', - extensionsToTreatAsEsm: ['.ts'], - testEnvironment: 'node', - preset: 'ts-jest/presets/default-esm', - setupFilesAfterEnv: ['/jest.setup.ts'], - testMatch: [ - '/src/**/__tests__/**/*.test.ts', - ], -}; - -export default jestConfig; diff --git a/src/buildtools/jest.setup.ts b/src/buildtools/jest.setup.ts deleted file mode 100644 index 412b8c6459..0000000000 --- a/src/buildtools/jest.setup.ts +++ /dev/null @@ -1,18 +0,0 @@ -const chalkFunction = new Proxy((x: string) => x, { - get: () => chalkFunction -}); - -jest.mock('chalk', () => new Proxy({}, { - get: () => chalkFunction -})); - -jest.mock('fs/promises', () => ({ - copyFile: jest.fn(() => Promise.resolve()), - mkdir: jest.fn(() => Promise.resolve()), - open: jest.fn(), - writeFile: jest.fn(() => Promise.resolve()) -})); - -global.process.exit = jest.fn(code => { - throw new Error(`process.exit called with ${code}`); -}); diff --git a/src/buildtools/package.json b/src/buildtools/package.json deleted file mode 100644 index 1005dc0f42..0000000000 --- a/src/buildtools/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@sourceacademy/modules-buildtools", - "private": true, - "description": "Tools for building Source Academy bundles and tabs", - "version": "1.0.0", - "devDependencies": { - "@commander-js/extra-typings": "^13.0.0", - "@types/estree": "^1.0.0", - "@types/jest": "^29.0.0", - "@types/node": "^20.12.12" - }, - "exports": { - "*": null - }, - "bin": { - "buildtools": "./bin/index.js" - }, - "type": "module", - "dependencies": { - "acorn": "^8.8.1", - "astring": "^1.8.6", - "chalk": "^5.0.1", - "commander": "^13.0.0", - "console-table-printer": "^2.11.1", - "esbuild": "^0.25.0", - "eslint": "^9.21.0", - "jest": "^29.7.0", - "jsonschema": "^1.5.0", - "ts-jest": "^29.1.2", - "typedoc": "^0.28.4", - "typescript": "^5.8.2" - }, - "scripts": { - "build": "node ./build.js", - "tsc": "tsc --project ./tsconfig.json" - } -} diff --git a/src/buildtools/src/build/docs/__mocks__/docsUtils.ts b/src/buildtools/src/build/docs/__mocks__/docsUtils.ts deleted file mode 100644 index 8532cf4da5..0000000000 --- a/src/buildtools/src/build/docs/__mocks__/docsUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const initTypedoc = jest.fn(() => { - const proj = { - getChildByName: () => ({ - children: [] - }), - path: '' - } as any; - - const app = { - convert: jest.fn() - .mockReturnValue(proj), - generateDocs: jest.fn(() => Promise.resolve()) - }; - - return Promise.resolve([proj, app]); -}); diff --git a/src/buildtools/src/build/docs/__tests__/building.test.ts b/src/buildtools/src/build/docs/__tests__/building.test.ts deleted file mode 100644 index 786787b57f..0000000000 --- a/src/buildtools/src/build/docs/__tests__/building.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; -import { initTypedoc } from '../docsUtils'; -import * as json from '../json'; - -jest.spyOn(json, 'buildJson'); - -const mockedWriteFile = fs.writeFile as MockedFunction; - -const test0Obj = { - test_function: { - kind: 'function', - params: [['_param0', 'string']], - description: '

    This is just some test function

    ', - retType: 'number' - } -}; - -const test1Obj = { - test_variable: { - kind: 'variable', - type: 'number', - description: '

    Test variable

    ' - } -}; - -const workingDir = __dirname + '/test_mocks'; - -function matchObj(raw: string, expected: T) { - expect(JSON.parse(raw)).toMatchObject(expected); -} - -describe('Check that buildJsons can handle building different numbers of bundles', () => { - test('Building the json documentation for a single bundle', async () => { - const [project,] = await initTypedoc(['test0'], workingDir, false, false); - await json.buildJsons({ bundles: ['test0'] }, workingDir, project); - - expect(json.buildJson).toHaveBeenCalledTimes(1); - const [[, test0str]] = mockedWriteFile.mock.calls; - matchObj(test0str as string, test0Obj); - }); - - test('Building the json documentation for multiple bundles', async () => { - const [project,] = await initTypedoc(['test0', 'test1'], workingDir, false, false); - await json.buildJsons({ bundles: ['test0', 'test1'] }, workingDir, project); - - expect(json.buildJson).toHaveBeenCalledTimes(2); - const [[, test0Str], [, test1Str]] = mockedWriteFile.mock.calls; - matchObj(test0Str as string, test0Obj); - matchObj(test1Str as string, test1Obj); - }); -}); diff --git a/src/buildtools/src/build/docs/__tests__/docs.test.ts b/src/buildtools/src/build/docs/__tests__/docs.test.ts deleted file mode 100644 index 5877d52010..0000000000 --- a/src/buildtools/src/build/docs/__tests__/docs.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import { testBuildCommand } from '@src/build/__tests__/testingUtils'; -import { getBuildDocsCommand } from '..'; -import * as html from '../html'; -import * as json from '../json'; - -jest.mock('../docsUtils'); - -jest.spyOn(json, 'buildJsons'); -jest.spyOn(html, 'buildHtml'); - -const asMock = any>(func: T) => func as MockedFunction; -const mockBuildJson = asMock(json.buildJsons); - -const runCommand = (...args: string[]) => getBuildDocsCommand() - .parseAsync(args, { from: 'user' }); - -describe('test the docs command', () => { - testBuildCommand( - 'buildDocs', - getBuildDocsCommand, - [json.buildJsons, html.buildHtml] - ); - - it('should only build the documentation for specified modules', async () => { - await runCommand('-b', 'test0', 'test1'); - - expect(json.buildJsons) - .toHaveBeenCalledTimes(1); - - const buildJsonCall = mockBuildJson.mock.calls[0]; - expect(buildJsonCall[0]) - .toEqual({ - bundles: ['test0', 'test1'], - modulesSpecified: true - }); - - expect(html.buildHtml) - .toHaveBeenCalledTimes(1); - - expect(html.buildHtml) - .toReturnWith(Promise.resolve({ - elapsed: 0, - result: { - severity: 'warn' - } - })); - }); -}); diff --git a/src/buildtools/src/build/docs/__tests__/json.test.ts b/src/buildtools/src/build/docs/__tests__/json.test.ts deleted file mode 100644 index 86581ec019..0000000000 --- a/src/buildtools/src/build/docs/__tests__/json.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; -import { testBuildCommand } from '@src/build/__tests__/testingUtils'; -import * as json from '../json'; - -jest.spyOn(json, 'buildJsons'); -jest.mock('../docsUtils'); - -const mockBuildJson = json.buildJsons as MockedFunction; -const runCommand = (...args: string[]) => json.getBuildJsonsCommand() - .parseAsync(args, { from: 'user' }); - -describe('test json command', () => { - testBuildCommand( - 'buildJsons', - json.getBuildJsonsCommand, - [json.buildJsons] - ); - - test('normal function', async () => { - await runCommand(); - - expect(fs.mkdir) - .toBeCalledWith('build/jsons', { recursive: true }); - - expect(json.buildJsons) - .toHaveBeenCalledTimes(1); - }); - - it('should only build the jsons for specified modules', async () => { - await runCommand('-b', 'test0', 'test1'); - - expect(json.buildJsons) - .toHaveBeenCalledTimes(1); - - const buildJsonCall = mockBuildJson.mock.calls[0]; - expect(buildJsonCall[0]) - .toMatchObject({ - bundles: ['test0', 'test1'] - }); - }); -}); diff --git a/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts b/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts deleted file mode 100644 index e7dee10395..0000000000 --- a/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This is just some test function - * @param _param0 Test parameter - * @returns Zero - */ -export function test_function(_param0: string) { - return 0; -} diff --git a/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts b/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts deleted file mode 100644 index 9f313a9cbd..0000000000 --- a/src/buildtools/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Test variable - */ -export const test_variable: number = 0; diff --git a/src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json b/src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json deleted file mode 100644 index d9a93d7cd9..0000000000 --- a/src/buildtools/src/build/docs/__tests__/test_mocks/tsconfig.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "compilerOptions": { - /* Allow JavaScript files to be imported inside your project, instead of just .ts and .tsx files. */ - "allowJs": false, - /* When set to true, allowSyntheticDefaultImports allows you to write an import like "import React from "react";" */ - "allowSyntheticDefaultImports": true, - /* See https://www.typescriptlang.org/tsconfig#esModuleInterop */ - "esModuleInterop": true, - /* Controls how JSX constructs are emitted in JavaScript files. This only affects output of JS files that started in .tsx files. */ - "jsx": "react-jsx", - /* See https://www.typescriptlang.org/tsconfig#lib */ - "lib": ["es6", "dom", "es2016", "ESNext", "scripthost"], - /* Sets the module system for the program. See the Modules reference page for more information. */ - "module": "esnext", - /* Specify the module resolution strategy: 'node' (Node.js) or 'classic' (used in TypeScript before the release of 1.6). */ - "moduleResolution": "node", - /* Do not emit compiler output files like JavaScript source code, source-maps or declarations. */ - "noEmit": true, - /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ - "resolveJsonModule": true, - /* The longest common path of all non-declaration input files. */ - "rootDir": "./", - /* Enables the generation of sourcemap files. These files allow debuggers and other tools to display the original TypeScript source code when actually working with the emitted JavaScript files. */ - "sourceMap": false, - /* Skip running typescript on declaration files. This option is needed due to a known bug in react-ace */ - "skipLibCheck": true, - /* The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. */ - "strict": true, - "forceConsistentCasingInFileNames": true, - /* The target setting changes which JS features are downleveled and which are left intact. */ - "target": "es6", - /* In some cases where no type annotations are present, TypeScript will fall back to a type of any for a variable when it cannot infer the type. */ - /* *** TEMPORARILY ADDED UNTIL ALL MODULES HAVE BEEN REFACTORED!!!!!!!!!!! *** */ - "noImplicitAny": false, - "verbatimModuleSyntax": true, - "paths": { - "js-slang/context": ["./typings/js-slang/context.d.ts"] - }, - "ignoreDeprecations": "5.0" - }, - /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ - "include": ["."], - /* Specifies an array of filenames or patterns that should be skipped when resolving include. */ - "exclude": ["jest.config.js"] -} diff --git a/src/buildtools/src/build/docs/docsUtils.ts b/src/buildtools/src/build/docs/docsUtils.ts deleted file mode 100644 index e78d821331..0000000000 --- a/src/buildtools/src/build/docs/docsUtils.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as td from 'typedoc'; -import type { ResolvedBundle } from '../manifest'; -import { getGitRoot } from '../../utils'; -import pathlib from 'path'; - -const commonTypedocOptions: td.Configuration.TypeDocOptions = { - categorizeByGroup: true, - disableSources: true, - excludeInternal: true, - logLevel: 'Error', - skipErrorChecking: true, -} - -export async function initTypedocForSingleBundle(bundle: ResolvedBundle) { - const app = await td.Application.bootstrap({ - ...commonTypedocOptions, - name: bundle.name, - entryPoints: [bundle.entryPoint], - tsconfig: `${bundle.directory}/tsconfig.json`, - }); - - const reflection = await app.convert() - if (!reflection) { - throw new Error(`Failed to generate reflection for ${bundle.name}, check that the bundle has no type errors!`) - } - - return reflection -} - -export async function initTypedoc(manifest: Record) { - const entryPoints = Object.values(manifest).map(({ entryPoint }) => entryPoint) - const gitRoot = await getGitRoot() - const tsconfigPath = pathlib.resolve(gitRoot, 'src', 'bundles', 'tsconfig.json') - - const app = await td.Application.bootstrap({ - ...commonTypedocOptions, - alwaysCreateEntryPointModule: true, - entryPoints, - name: 'Source Academy Modules', - readme: `${import.meta.dirname}/docsreadme.md`, - tsconfig: tsconfigPath, - }); - - const project = await app.convert(); - if (!project) { - throw new Error('Failed to initialize typedoc - Make sure to check that the source files have no compilation errors!'); - } - return [project, app] as [td.ProjectReflection, td.Application]; -} - -export type TypedocInitResult = Awaited>; diff --git a/src/buildtools/src/build/docs/drawdown.ts b/src/buildtools/src/build/docs/drawdown.ts deleted file mode 100644 index 8fe5d5aaa8..0000000000 --- a/src/buildtools/src/build/docs/drawdown.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* eslint-disable*/ -/** - * Module to convert from markdown into HTML - * drawdown.js - * (c) Adam Leggett - */ - -export default (src: string): string => { - var rx_lt = //g; - var rx_space = /\t|\r|\uf8ff/g; - var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g; - var rx_hr = /^([*\-=_] *){3,}$/gm; - var rx_blockquote = /\n *> *([^]*?)(?=(\n|$){2})/g; - var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g; - var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g; - var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+)|`)(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g; - var rx_code = /\n((```|~~~).*\n?([^]*?)\n?\2|(( {4}.*?\n)+))/g; - var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g; - var rx_table = /\n(( *\|.*?\| *\n)+)/g; - var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/; - var rx_row = /.*\n/g; - var rx_cell = /\||(.*?[^\\])\|/g; - var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g; - var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g; - var rx_stash = /-\d+\uf8ff/g; - - function replace(rex, fn) { - src = src.replace(rex, fn); - } - - function element(tag, content) { - return '<' + tag + '>' + content + ''; - } - - function blockquote(src) { - return src.replace(rx_blockquote, function (all, content) { - return element( - 'blockquote', - blockquote(highlight(content.replace(/^ *> */gm, ''))) - ); - }); - } - - function list(src) { - return src.replace(rx_list, function (all, ind, ol, num, low, content) { - var entry = element( - 'li', - highlight( - content - .split( - RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g') - ) - .map(list) - .join('
  • ') - ) - ); - - return ( - '\n' + - (ol - ? '
      ' - : parseInt(ol, 36) - - 9 + - '" style="list-style-type:' + - (low ? 'low' : 'upp') + - 'er-alpha">') + - entry + - '
    ' - : element('ul', entry)) - ); - }); - } - - function highlight(src) { - return src.replace( - rx_highlight, - function (all, _, p1, emp, sub, sup, small, big, p2, content) { - return ( - _ + - element( - emp - ? p2 - ? 'strong' - : 'em' - : sub - ? p2 - ? 's' - : 'sub' - : sup - ? 'sup' - : small - ? 'small' - : big - ? 'big' - : 'code', - highlight(content) - ) - ); - } - ); - } - - function unesc(str) { - return str.replace(rx_escape, '$1'); - } - - var stash = []; - var si = 0; - - src = '\n' + src + '\n'; - - replace(rx_lt, '<'); - replace(rx_gt, '>'); - replace(rx_space, ' '); - - // blockquote - src = blockquote(src); - - // horizontal rule - replace(rx_hr, '
    '); - - // list - src = list(src); - replace(rx_listjoin, ''); - - // code - replace(rx_code, function (all, p1, p2, p3, p4) { - stash[--si] = element( - 'pre', - element('code', p3 || p4.replace(/^ {4}/gm, '')) - ); - return si + '\uf8ff'; - }); - - // link or image - replace(rx_link, function (all, p1, p2, p3, p4, p5, p6) { - stash[--si] = p4 - ? p2 - ? '' + p3 + '' - : '' + unesc(highlight(p3)) + '' - : p6; - return si + '\uf8ff'; - }); - - // table - replace(rx_table, function (all, table) { - var sep = table.match(rx_thead)[1]; - return ( - '\n' + - element( - 'table', - table.replace(rx_row, function (row, ri) { - return row == sep - ? '' - : element( - 'tr', - row.replace(rx_cell, function (all, cell, ci) { - return ci - ? element( - sep && !ri ? 'th' : 'td', - unesc(highlight(cell || '')) - ) - : ''; - }) - ); - }) - ) - ); - }); - - // heading - replace(rx_heading, function (all, _, p1, p2) { - return _ + element('h' + p1.length, unesc(highlight(p2))); - }); - - // paragraph - replace(rx_para, function (all, content) { - return element('p', unesc(highlight(content))); - }); - - // stash - replace(rx_stash, function (all) { - return stash[parseInt(all)]; - }); - - return src.trim(); -}; \ No newline at end of file diff --git a/src/buildtools/src/build/docs/index.ts b/src/buildtools/src/build/docs/index.ts deleted file mode 100644 index e82560bb2a..0000000000 --- a/src/buildtools/src/build/docs/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ProjectReflection } from 'typedoc' -import type { ResolvedBundle } from '../manifest' -import { initTypedoc } from './docsUtils' -import { buildJson } from './json' -import chalk from 'chalk' - -/** - * A more efficient version of documentation building to avoid - * having to instantiate typedoc multiple times - */ -export async function buildDocs(manifest: Record, outDir: string) { - const [project, app] = await initTypedoc(manifest) - - const validModules = new Set(Object.keys(manifest)) - - const unknownChildren = project.children.filter(({ name }) => !validModules.has(name)) - if (unknownChildren.length > 0) { - console.warn(`${chalk.yellow('[warning]')} Unknown modules present in html output`) - } - - const jsonPromises = Object.keys(manifest) - .map(async bundleName => { - const reflection = project.getChildByName(bundleName) - if (!reflection) { - console.warn(`${chalk.yellow('[warning]')} Did not find documentation for ${bundleName}. Did you forget a @module tag?`) - return; - } - - await buildJson(bundleName, reflection as ProjectReflection, outDir) - }) - - await Promise.all([ - ...jsonPromises, - app.generateDocs(project, `${outDir}/documentation`) - ]) -} - - -export { buildJson } \ No newline at end of file diff --git a/src/buildtools/src/build/docs/json.ts b/src/buildtools/src/build/docs/json.ts deleted file mode 100644 index e99505f383..0000000000 --- a/src/buildtools/src/build/docs/json.ts +++ /dev/null @@ -1,69 +0,0 @@ -import fs from 'fs/promises'; -import * as td from 'typedoc'; -import drawdown from './drawdown'; - -const typeToName = (type?: td.SomeType) => type.stringify(td.TypeContext.none); - -const parsers = { - [td.ReflectionKind.Function](obj) { - // Functions should have only 1 signature - if (obj.signatures.length > 1) { - console.warn(`${obj.name} has more than 1 signature; only using the first one`) - } - - const [signature] = obj.signatures; - - let description: string; - if (signature.comment) { - description = drawdown(signature.comment.summary.map(({ text }) => text) - .join('')); - } else { - description = 'No description available'; - } - - const params = signature.parameters.map(({ type, name }) => [name, typeToName(type)] as [string, string]); - - return { - kind: 'function', - name: obj.name, - description, - params, - retType: typeToName(signature.type) - }; - }, - [td.ReflectionKind.Variable](obj) { - let description: string; - if (obj.comment) { - description = drawdown(obj.comment.summary.map(({ text }) => text) - .join('')); - } else { - description = 'No description available'; - } - - return { - kind: 'variable', - name: obj.name, - description, - type: typeToName(obj.type) - }; - } -} satisfies Partial any>>; - -export async function buildJson(bundleName: string, reflection: td.ProjectReflection, outDir: string) { - const jsonData = reflection.children.reduce((res, element) => { - // Ignore 'type_map' exports if they are present - if (element.name === 'type_map') { - return res; - } - - const parser = parsers[element.kind]; - return { - ...res, - [element.name]: parser - ? parser(element) - : { kind: 'unknown' } - }; - }, {}); - - await fs.writeFile(`${outDir}/json/${bundleName}.json`, JSON.stringify(jsonData, null, 2)); -} diff --git a/src/buildtools/src/build/manifest.ts b/src/buildtools/src/build/manifest.ts deleted file mode 100644 index a23dd0ab65..0000000000 --- a/src/buildtools/src/build/manifest.ts +++ /dev/null @@ -1,133 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path'; -import { validate } from 'jsonschema'; -import manifestSchema from './modules/manifest.schema.json' with { type: 'json' }; -import { getBundleEntryPoint } from './modules/bundle'; - -export interface BundleManifest { - version?: string - tabs?: string[] -} - -export type ModulesManifest = { - [name: string]: BundleManifest -} - -/** - * Checks that the given bundle manifest was correctly specified - */ -export async function getBundleManifest(manifestFile: string, tabCheck?: boolean): Promise { - let rawManifest: string - - try { - rawManifest = await fs.readFile(manifestFile, 'utf-8') - } catch (error) { - if (error.code === 'ENOENT') { - return undefined - } - throw error - } - - const data = JSON.parse(rawManifest) as BundleManifest; - const { valid, errors } = validate(data, manifestSchema); - - if (!valid) throw errors; - - // Make sure that all the tabs specified exist - if (tabCheck && data.tabs) { - await Promise.all(data.tabs.map(async tabName => { - try { - await fs.access(`./src/tabs/${tabName}`, fs.constants.R_OK); - } catch { - throw new Error(`Failed to find tab with name '${tabName}'!`); - } - })); - } - - return data; -} - -/** - * Get all bundle manifests - */ -export async function getBundleManifests(bundlesDir: string): Promise { - const subdirs = await fs.readdir(bundlesDir) - const manifests = await Promise.all(subdirs.map(async fileName => { - const fullPath = pathlib.join(bundlesDir, fileName); - const stats = await fs.stat(fullPath); - if (stats.isDirectory()) { - const manifest = await getBundleManifest(`${fullPath}/manifest.json`); - if (manifest === undefined) return undefined - return [fileName, manifest] as [string, BundleManifest]; - } - - return undefined - })) - - return manifests.reduce((res, entry) => { - if (entry === undefined) return res; - - const [name, manifest] = entry - - return { - ...res, - [name]: manifest - }; - }, {} as Record); -} - -export interface ResolvedBundle { - name: string - manifest: BundleManifest - entryPoint: string - directory: string -} - -export async function resolveSingleBundle(bundleDir: string): Promise { - const fullyResolved = pathlib.resolve(bundleDir) - - const stats = await fs.stat(fullyResolved) - if (!stats.isDirectory()) return undefined - - const manifest = await getBundleManifest(`${fullyResolved}/manifest.json`) - if (!manifest) return undefined - - const bundleName = pathlib.basename(fullyResolved) - const entryPoint = await getBundleEntryPoint(fullyResolved) - - return { - name: bundleName, - manifest, - entryPoint, - directory: fullyResolved - } -} - -export async function resolveAllBundles(bundlesDir: string) { - const subdirs = await fs.readdir(bundlesDir) - const manifests = await Promise.all(subdirs.map(subdir => { - const fullPath = pathlib.join(bundlesDir, subdir) - return resolveSingleBundle(fullPath) - })) - - return manifests.reduce((res, entry) => { - if (entry === undefined) return res - - return { - ...res, - [entry.name]: entry - } - }, {} as Record) -} -export async function resolvePaths(...paths: string[]) { - for (const path of paths) { - try { - await fs.access(path, fs.constants.R_OK); - return path - } catch (error) { - if (error.code !== 'ENOENT') throw error; - } - } - - return undefined; -} diff --git a/src/buildtools/src/build/modules/bundle.ts b/src/buildtools/src/build/modules/bundle.ts deleted file mode 100644 index e660fb1c63..0000000000 --- a/src/buildtools/src/build/modules/bundle.ts +++ /dev/null @@ -1,31 +0,0 @@ -import fs from 'fs/promises'; -import { build as esbuild } from 'esbuild'; -import { commonEsbuildOptions, outputBundleOrTab } from './commons'; -import type { ResolvedBundle } from '../manifest'; - -export async function getBundleEntryPoint(bundleDir: string) { - let bundlePath = `${bundleDir}/src/index.ts` - - try { - await fs.access(bundlePath, fs.constants.R_OK) - return bundlePath - } catch (error) { - bundlePath = `${bundleDir}/index.ts` - await fs.access(bundlePath, fs.constants.R_OK) - return bundlePath - } -} - -/** - * Build the given resolved bundle - */ -export async function buildBundle(bundle: ResolvedBundle, outDir: string) { - const { outputFiles: [result] } = await esbuild({ - ...commonEsbuildOptions, - entryPoints: [bundle.entryPoint], - tsconfig: `${bundle.directory}/tsconfig.json`, - outfile: `/bundle/${bundle.name}`, - }); - - await outputBundleOrTab(result, bundle.name, 'bundle', outDir); -} diff --git a/src/buildtools/src/build/modules/commons.ts b/src/buildtools/src/build/modules/commons.ts deleted file mode 100644 index 2ad41fcf7e..0000000000 --- a/src/buildtools/src/build/modules/commons.ts +++ /dev/null @@ -1,62 +0,0 @@ -import fs from 'fs/promises'; -import { parse } from 'acorn'; -import { generate } from 'astring'; -import type { BuildOptions as ESBuildOptions, OutputFile } from 'esbuild'; -import type es from 'estree'; - -export const commonEsbuildOptions: ESBuildOptions = { - bundle: true, - format: 'iife', - define: { - process: JSON.stringify({ - env: { - NODE_ENV: 'production' - }, - }), - global: 'globalThis' - }, - external: ['js-slang*'], - globalName: 'module', - platform: 'browser', - target: 'es6', - write: false -}; - -export async function outputBundleOrTab({ text }: OutputFile, name: string, type: 'bundle' | 'tab', outDir: string) { - const parsed = parse(text, { ecmaVersion: 6 }) as es.Program; - - // Account for 'use strict'; directives - let declStatement: es.VariableDeclaration; - if (parsed.body[0].type === 'VariableDeclaration') { - declStatement = parsed.body[0]; - } else { - declStatement = parsed.body[1] as es.VariableDeclaration; - } - - const { init: callExpression } = declStatement.declarations[0]; - if (callExpression.type !== 'CallExpression') { - throw new Error(`Expected a CallExpression, got ${callExpression.type}`); - } - - const moduleCode = callExpression.callee; - - if (moduleCode.type !== 'FunctionExpression' && moduleCode.type !== 'ArrowFunctionExpression') { - throw new Error(`Expected a function, got ${moduleCode.type}`); - } - - const output: es.ExportDefaultDeclaration = { - type: 'ExportDefaultDeclaration', - declaration: { - ...moduleCode, - params: [{ - type: 'Identifier', - name: 'require' - }] - } - }; - - await fs.mkdir(`${outDir}/${type}`, { recursive: true }) - const file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); - const writeStream = file.createWriteStream(); - generate(output, { output: writeStream }); -} diff --git a/src/buildtools/src/build/modules/index.ts b/src/buildtools/src/build/modules/index.ts deleted file mode 100644 index 475182586a..0000000000 --- a/src/buildtools/src/build/modules/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import fs from 'fs/promises'; -import { buildBundle } from './bundle'; -import type { ResolvedBundle } from '../manifest'; - -/** - * Writes the module manifest to the output directory - */ -export async function writeManifest(manifests: Record, outDir: string) { - const toWrite = Object.entries(manifests).reduce((res, [key, { manifest }]) => ({ - ...res, - [key]: manifest - }), {}) - await fs.writeFile(`${outDir}/modules.json`, JSON.stringify(toWrite, null, 2)) -} - -/** - * Search the given directory for valid bundles, then build and write - * them to the given output directory, - */ -export async function buildBundles(manifests: Record, outDir: string) { - await Promise.all(Object.values(manifests).map(async bundle => { - await buildBundle(bundle, outDir); - })) -} - -export { buildBundle }; -export { buildTab, buildTabs } from './tab'; \ No newline at end of file diff --git a/src/buildtools/src/build/modules/manifest.schema.json b/src/buildtools/src/build/modules/manifest.schema.json deleted file mode 100644 index 8a3394d479..0000000000 --- a/src/buildtools/src/build/modules/manifest.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "schema": { - "type": "object", - "properties": { - "tabs": { - "description": "Tabs that will be loaded with this bundle", - "type": "array", - "items": { - "type": "string" - } - }, - "version": { - "type": "string", - "description": "Version of your bundle" - }, - "requires": { - "enum": [1, 2, 3, 4], - "description": "Minimum Source version required to run this bundle" - }, - "additionalProperties": false - } - } -} \ No newline at end of file diff --git a/src/buildtools/src/build/modules/tab.ts b/src/buildtools/src/build/modules/tab.ts deleted file mode 100644 index 3859073b93..0000000000 --- a/src/buildtools/src/build/modules/tab.ts +++ /dev/null @@ -1,86 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path' -import { build as esbuild, type Plugin as ESBuildPlugin } from 'esbuild'; -import { commonEsbuildOptions, outputBundleOrTab } from './commons'; -import { resolvePaths, type ResolvedBundle } from '../manifest'; - -const tabContextPlugin: ESBuildPlugin = { - name: 'Tab Context', - setup(build) { - build.onResolve({ filter: /^js-slang\/context/ }, () => ({ - errors: [{ - text: 'If you see this message, it means that your tab code is importing js-slang/context directly or indirectly. Do not do this' - }] - })); - } -}; - -export async function getTabEntryPoint(tabDir: string) { - let tabPath = `${tabDir}/src/index.tsx` - try { - await fs.access(tabPath, fs.constants.R_OK) - return tabPath - } catch { - tabPath = `${tabPath}/index.tsx` - await fs.access(tabPath, fs.constants.R_OK) - return tabPath - } -} - -interface ResolvedTab { - directory: string - entryPoint: string - name: string -} - -export async function resolveSingleTab(tabDir: string): Promise { - const fullyResolved = pathlib.resolve(tabDir) - - const tabPath = await resolvePaths( - `${fullyResolved}/src/index.tsx`, - `${fullyResolved}/index.tsx` - ) - - if (tabPath === undefined) { - throw new Error(`No tab found at ${fullyResolved}!`) - } - - return { - directory: fullyResolved, - entryPoint: tabPath, - name: pathlib.basename(fullyResolved) - } -} - -/** - * Build a tab at the given directory - */ -export async function buildTab(tabDir: string, outDir: string) { - const tab = await resolveSingleTab(tabDir) - - const { outputFiles: [result]} = await esbuild({ - ...commonEsbuildOptions, - entryPoints: [tab.entryPoint], - external: [ - ...commonEsbuildOptions.external, - 'react', - 'react-ace', - 'react-dom', - 'react/jsx-runtime', - '@blueprintjs/*' - ], - tsconfig: `${tab.directory}/tsconfig.json`, - plugins: [tabContextPlugin], - }); - await outputBundleOrTab(result, tab.name, 'tab', outDir); -} - -export async function buildTabs(manifest: Record, outDir: string) { - await Promise.all(Object.values(manifest).map(async ({ manifest: { tabs } }) => { - if (tabs) { - await Promise.all(tabs.map(async tabName => { - await buildTab(tabName, outDir) - })) - } - })) -} diff --git a/src/buildtools/src/commands/__tests__/template.test.ts b/src/buildtools/src/commands/__tests__/template.test.ts deleted file mode 100644 index 7907424e61..0000000000 --- a/src/buildtools/src/commands/__tests__/template.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; - -import getTemplateCommand from '../template'; -import { askQuestion } from '../../templates/print'; - -jest.mock('../print', () => ({ - ...jest.requireActual('../print'), - askQuestion: jest.fn(), - error(x: string) { - // The command has a catch-all for errors, - // so we rethrow the error to observe the value - throw new Error(x); - }, - // Because the functions run in perpetual while loops - // We need to throw an error to observe what value warn - // was called with - warn(x: string) { - throw new Error(x); - } -})); - -const asMock = any>(func: T) => func as MockedFunction; - -const mockedAskQuestion = asMock(askQuestion); - -function runCommand(...args: string[]) { - return getTemplateCommand() - .parseAsync(args, { from: 'user' }); -} - -function expectCall any>( - func: T, - ...expected: Parameters[]) { - const mocked = asMock(func); - - expect(func) - .toHaveBeenCalledTimes(expected.length); - - mocked.mock.calls.forEach((actual, i) => { - expect(actual) - .toEqual(expected[i]); - }); -} - -async function expectCommandFailure(snapshot: string) { - await expect(runCommand()) - .rejects - // eslint-disable-next-line jest/no-interpolation-in-snapshots - .toMatchInlineSnapshot(`[Error: ERROR: ${snapshot}]`); - - expect(fs.writeFile).not.toHaveBeenCalled(); - expect(fs.copyFile).not.toHaveBeenCalled(); - expect(fs.mkdir).not.toHaveBeenCalled(); -} - -describe('Test adding new module', () => { - beforeEach(() => { - mockedAskQuestion.mockResolvedValueOnce('module'); - }); - - it('should require camel case for module names', async () => { - mockedAskQuestion.mockResolvedValueOnce('camelCase'); - await expectCommandFailure('Module names must be in snake case. (eg. binary_tree)'); - }); - - it('should check for existing modules', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - await expectCommandFailure('A module with the same name already exists.'); - }); - - test('successfully adding a new module', async () => { - mockedAskQuestion.mockResolvedValueOnce('new_module'); - await runCommand(); - - expectCall( - fs.mkdir, - ['src/bundles/new_module', { recursive: true }] - ); - - expectCall( - fs.copyFile, - [ - './scripts/src/templates/templates/__bundle__.ts', - 'src/bundles/new_module/index.ts' - ] - ); - - const oldManifest = await retrieveManifest('modules.json'); - const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; - expect(manifestPath) - .toEqual('modules.json'); - - expect(JSON.parse(newManifest as string)) - .toMatchObject({ - ...oldManifest, - new_module: { tabs: [] } - }); - }); -}); - -describe('Test adding new tab', () => { - beforeEach(() => { - mockedAskQuestion.mockResolvedValueOnce('tab'); - }); - - it('should require pascal case for tab names', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - mockedAskQuestion.mockResolvedValueOnce('unknown_tab'); - await expectCommandFailure('Tab names must be in pascal case. (eg. BinaryTree)'); - }); - - it('should check if the given tab already exists', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - mockedAskQuestion.mockResolvedValueOnce('tab0'); - await expectCommandFailure('A tab with the same name already exists.'); - }); - - it('should check if the given module exists', async () => { - mockedAskQuestion.mockResolvedValueOnce('unknown_module'); - await expectCommandFailure('Module unknown_module does not exist.'); - }); - - test('Successfully adding new tab', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - mockedAskQuestion.mockResolvedValueOnce('TabNew'); - - await runCommand(); - - expectCall( - fs.mkdir, - ['src/tabs/TabNew', { recursive: true }] - ); - - expectCall( - fs.copyFile, - [ - './scripts/src/templates/templates/__tab__.tsx', - 'src/tabs/TabNew/index.tsx' - ] - ); - - const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; - expect(manifestPath) - .toEqual('modules.json'); - - expect(JSON.parse(newManifest as string)) - .toMatchObject({ - ...oldManifest, - test0: { - tabs: ['tab0', 'TabNew'] - } - }); - }); -}); diff --git a/src/buildtools/src/commands/__tests__/testing.test.ts b/src/buildtools/src/commands/__tests__/testing.test.ts deleted file mode 100644 index 5c7e59efd6..0000000000 --- a/src/buildtools/src/commands/__tests__/testing.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import getTestCommand from '../testing'; -import * as runner from '../../testing/runner'; - -jest.spyOn(runner, 'runJest') - .mockImplementation(jest.fn()); - -const runCommand = (...args: string[]) => getTestCommand() - .parseAsync(args, { from: 'user' }); -const mockRunJest = runner.runJest as MockedFunction; - -test('Check that the test command properly passes options to jest', async () => { - await runCommand('-u', '-w', './src/folder'); - - const [call] = mockRunJest.mock.calls; - expect(call[0]) - .toEqual(['-u', '-w', './src/folder']); - expect(call[1]) - .toEqual('gg'); -}); - -test('Check that the test command handles windows paths as posix paths', async () => { - await runCommand('.\\src\\folder'); - - const [call] = mockRunJest.mock.calls; - expect(call[0]) - .toEqual(['./src/folder']); -}); diff --git a/src/buildtools/src/commands/build.ts b/src/buildtools/src/commands/build.ts deleted file mode 100644 index 23c7ce312b..0000000000 --- a/src/buildtools/src/commands/build.ts +++ /dev/null @@ -1,68 +0,0 @@ -import fs from 'fs/promises' -import { Command } from "@commander-js/extra-typings"; -import { buildBundle, buildBundles, buildTab, buildTabs, writeManifest } from "../build/modules"; -import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; -import { initTypedocForSingleBundle } from '../build/docs/docsUtils'; -import { buildDocs, buildJson } from '../build/docs'; -import { getBundlesDir, getOutDir } from '../utils'; - -const outDir = await getOutDir() -const bundlesDir = await getBundlesDir() - -export const getBuildBundleCommand = () => new Command('bundle') - .argument('', 'Directory in which the bundle\'s source files are located') - .action(async bundleDir => { - const bundle = await resolveSingleBundle(bundleDir) - if (!bundle) { - throw new Error(`No bundle found at ${bundleDir}!`) - } - await buildBundle(bundle, outDir) - }) - -export const getBuildTabCommand = () => new Command('tab') - .argument('', 'Directory in which the tab\'s source files are located') - .action(tabDir => buildTab(tabDir, outDir)) - -export const getBuildJsonCommand = () => new Command('json') - .argument('', 'Directory in which the bundle\'s source files are located') - .action(async bundleDir => { - const bundle = await resolveSingleBundle(bundleDir) - const reflection = await initTypedocForSingleBundle(bundle) - await fs.mkdir(`${outDir}/json`, { recursive: true }) - await buildJson(bundle.name, reflection, outDir) - }) - -export const getBuildDocsCommand = () => new Command('docs') - .action(async () => { - const manifest = await resolveAllBundles(bundlesDir) - await buildDocs(manifest, outDir) - }) - -export const getBuildManifestCommand = () => new Command('manifest') - .action(async () => { - const manifest = await resolveAllBundles(bundlesDir) - await fs.mkdir(outDir, { recursive: true }) - await writeManifest(manifest, outDir) - }) - -export const getBuildAllCommand = () => new Command('all') -.description('Build all bundles, tabs and documentation') - .action(async () => { - const manifest = await resolveAllBundles(bundlesDir) - await fs.mkdir(outDir, { recursive: true }) - - await Promise.all([ - writeManifest(manifest, outDir), - buildDocs(manifest, outDir), - buildBundles(manifest, outDir), - buildTabs(manifest, outDir) - ]) - }) - -export const getBuildCommand = () => new Command('build') - .addCommand(getBuildAllCommand()) - .addCommand(getBuildBundleCommand()) - .addCommand(getBuildTabCommand()) - .addCommand(getBuildJsonCommand()) - .addCommand(getBuildDocsCommand()) - .addCommand(getBuildManifestCommand()) diff --git a/src/buildtools/src/commands/index.ts b/src/buildtools/src/commands/index.ts deleted file mode 100644 index c0b3b96360..0000000000 --- a/src/buildtools/src/commands/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { getMainCommand } from "./main"; - -await getMainCommand().parseAsync() \ No newline at end of file diff --git a/src/buildtools/src/commands/list.ts b/src/buildtools/src/commands/list.ts deleted file mode 100644 index f24a7ebf5e..0000000000 --- a/src/buildtools/src/commands/list.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Command } from "@commander-js/extra-typings"; -import { resolveAllBundles, resolveSingleBundle } from "../build/manifest"; -import { getBundlesDir } from "../utils"; -import chalk from "chalk"; - -export const getListBundlesCommand = () => new Command('bundle') - .description('Lists all the bundles present or the information for a specific bundle in a given directory') - .argument('[directory]') - .action(async directory => { - if (directory === undefined) { - const bundlesDir = await getBundlesDir() - const manifest = await resolveAllBundles(bundlesDir) - const bundleNames = Object.keys(manifest) - - if (bundleNames.length > 0 ) { - const bundlesStr = bundleNames.map((each, i) => `${i+1}. ${each}`).join('\n') - console.log(`${chalk.magentaBright(`Detected ${bundleNames.length} bundles in ${directory}:`)}\n${bundlesStr}`) - } else { - console.log(chalk.redBright(`No bundles in ${directory}`)) - } - } else { - const manifest = await resolveSingleBundle(directory) - console.log(chalk.magentaBright(`Bundle '${manifest.name}' found in ${directory}`)) - } - }) - -export const getListCommand = () => new Command('list') - .addCommand(getListBundlesCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/main.ts b/src/buildtools/src/commands/main.ts deleted file mode 100644 index d554ec2439..0000000000 --- a/src/buildtools/src/commands/main.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Command } from "@commander-js/extra-typings"; -import { getBuildCommand } from "./build"; -import getTemplateCommand from "./template"; -import getTestCommand from "./testing"; -import { getLintCommand, getTscCommand } from "./prebuild"; -import { getListCommand } from "./list"; - -export const getMainCommand = () => new Command() - .addCommand(getBuildCommand()) - .addCommand(getLintCommand()) - .addCommand(getListCommand()) - .addCommand(getTemplateCommand()) - .addCommand(getTestCommand()) - .addCommand(getTscCommand()) \ No newline at end of file diff --git a/src/buildtools/src/commands/prebuild.ts b/src/buildtools/src/commands/prebuild.ts deleted file mode 100644 index 79c6773f5a..0000000000 --- a/src/buildtools/src/commands/prebuild.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Command } from "@commander-js/extra-typings"; -import { runEslint } from "../prebuild/lint"; -import type { AwaitedReturn } from "../utils"; -import { runTsc } from "../prebuild/typecheck"; -import pathlib from 'path'; -import chalk from "chalk"; -import ts from 'typescript' - -export const getLintCommand = () => new Command('lint') - .argument('') - .option('--fix') - .action(async (directory, { fix }) => { - const results = await runEslint(directory, fix) - console.log(eslintResultsLogger(results)) - }) - -export const getTscCommand = () => new Command('typecheck') - .argument('') - .action(async directory => { - const results = await runTsc(directory) - console.log(tscResultsLogger(results)) - }) - -function tscResultsLogger(tscResult: AwaitedReturn) { - if (tscResult.severity === 'error' && tscResult.error) { - return `${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}: ${tscResult.error}`)}`; - } - - const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { - getNewLine: () => '\n', - getCurrentDirectory: () => process.cwd(), - getCanonicalFileName: name => pathlib.basename(name) - }); - - if (tscResult.severity === 'error') { - return `${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}}`)}`; - } - return `${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')}`)}`; -} - -function eslintResultsLogger({ formatted, severity }: AwaitedReturn) { - let errStr: string; - - if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); - else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); - else errStr = chalk.greenBright('successfully'); - - return `${chalk.cyanBright(`Linting completed:`)}\n${formatted}`; -} \ No newline at end of file diff --git a/src/buildtools/src/commands/template.ts b/src/buildtools/src/commands/template.ts deleted file mode 100644 index 77709b4c07..0000000000 --- a/src/buildtools/src/commands/template.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Interface } from 'readline/promises'; -import { Command } from '@commander-js/extra-typings'; - -import { addNew as addNewModule } from '../templates/bundle'; -import { error as _error, askQuestion, getRl, info, warn } from '../templates/print'; -import { addNew as addNewTab } from '../templates/tab'; -import { getBundlesDir, getTabsDir } from '../utils'; - -async function askMode(rl: Interface) { - while (true) { - const mode = await askQuestion('What would you like to create? (module/tab)', rl); - if (mode !== 'module' && mode !== 'tab') { - warn("Please answer with only 'module' or 'tab'."); - } else { - return mode; - } - } -} - -export default function getTemplateCommand() { - return new Command('template') - .description('Interactively create a new module or tab') - .action(async () => { - const [bundlesDir, tabsDir] = await Promise.all([ - getBundlesDir(), - getTabsDir() - ]) - - const rl = getRl(); - try { - const mode = await askMode(rl); - if (mode === 'module') await addNewModule(bundlesDir, rl); - else if (mode === 'tab') await addNewTab(bundlesDir, tabsDir, rl); - } catch (error) { - _error(`ERROR: ${error.message}`); - info('Terminating module app...'); - } finally { - rl.close(); - } - }); -} diff --git a/src/buildtools/src/commands/testing.ts b/src/buildtools/src/commands/testing.ts deleted file mode 100644 index b06672f031..0000000000 --- a/src/buildtools/src/commands/testing.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Command } from '@commander-js/extra-typings'; - -import { runJest } from '../testing/runner'; - -const getTestCommand = () => new Command('test') - .description('Run jest') - .allowUnknownOption() - .argument('[patterns...]') - .action((patterns, args: Record) => { - return runJest( - Object.entries(args).flat(), - patterns - ); - }); - -export default getTestCommand; diff --git a/src/buildtools/src/commands/utils.ts b/src/buildtools/src/commands/utils.ts deleted file mode 100644 index b460d63170..0000000000 --- a/src/buildtools/src/commands/utils.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Option } from '@commander-js/extra-typings'; - -class OptionNew< - UsageT extends string = '', - PresetT = undefined, - DefaultT = undefined, - CoerceT = undefined, - Mandatory extends boolean = false, - ChoicesT = undefined -> - extends Option { - default(value: T, description?: string): Option { - return super.default(value, description); - } -} - -export const srcDirOption = new OptionNew('--srcDir ', 'Location of the source files') - .default('src'); - -export const outDirOption = new OptionNew('--outDir ', 'Location of output directory') - .default('build'); - -export const manifestOption = new OptionNew('--manifest ', 'Location of manifest') - .default('modules.json'); - -export const lintOption = new OptionNew('--lint', 'Run ESLint'); - -export const lintFixOption = new OptionNew('--fix', 'Fix automatically fixable linting errors') - .implies({ lint: true }); - -export const bundlesOption = new OptionNew('-b, --bundles ', 'Manually specify which bundles') - .default(null); - -export const tabsOption = new OptionNew('-t, --tabs ', 'Manually specify which tabs') - .default(null); - -export function promiseAll[]>(...args: T): Promise<{ [K in keyof T]: Awaited }> { - return Promise.all(args); -} - -export interface TimedResult { - result: T - elapsed: number -} - -// export function wrapWithTimer Promise>(func: T) { -// return async (...args: Parameters): Promise>> => { -// const startTime = performance.now(); -// const result = await func(...args); -// return { -// result, -// elapsed: performance.now() - startTime -// }; -// }; -// } - -type ValuesOfRecord = T extends Record ? U : never; - -export type EntriesOfRecord> = ValuesOfRecord<{ - [K in keyof T]: [K, T[K]] -}>; - -export function objectEntries>(obj: T) { - return Object.entries(obj) as EntriesOfRecord[]; -} - -export interface BuildInputs { - bundles?: string[] | null; - tabs?: string[] | null; - modulesSpecified?: boolean; -} diff --git a/src/buildtools/src/prebuild/lint.ts b/src/buildtools/src/prebuild/lint.ts deleted file mode 100644 index 540857d341..0000000000 --- a/src/buildtools/src/prebuild/lint.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ESLint } from 'eslint' -import { findSeverity, type Severity } from '../utils'; - -interface LintResults { - formatted: string - severity: Severity -} - -export async function runEslint(directory: string, fix: boolean): Promise { - const linter = new ESLint({ fix }); - - try { - const linterResults = await linter.lintFiles(directory); - if (fix) { - await ESLint.outputFixes(linterResults); - } - - const outputFormatter = await linter.loadFormatter('stylish'); - const formatted = await outputFormatter.format(linterResults); - const severity = findSeverity(linterResults, ({ warningCount, errorCount, fatalErrorCount }) => { - - if (fatalErrorCount > 0) return 'error' - if (!fix && errorCount > 0) return 'error' - - if (warningCount > 0) return 'warn'; - return 'success'; - }); - - return { - formatted, - severity - }; - } catch (error) { - return { - severity: 'error', - formatted: error.toString() - }; - } -} \ No newline at end of file diff --git a/src/buildtools/src/prebuild/typecheck.ts b/src/buildtools/src/prebuild/typecheck.ts deleted file mode 100644 index a1d3d2f675..0000000000 --- a/src/buildtools/src/prebuild/typecheck.ts +++ /dev/null @@ -1,82 +0,0 @@ -import fs from 'fs/promises' -import pathlib from 'path' -import ts from 'typescript' - -type TsconfigResult = { - severity: 'error', - results?: ts.Diagnostic[] - error?: any -} | { - severity: 'success', - results: ts.CompilerOptions -}; - -type TscResult = { - severity: 'error' - results?: ts.Diagnostic[] - error?: any -} | { - severity: 'success', - results: ts.Diagnostic[] -}; - -async function getTsconfig(srcDir: string): Promise { - // Step 1: Read the text from tsconfig.json - const tsconfigLocation = pathlib.join(srcDir, 'tsconfig.json'); - - try { - const configText = await fs.readFile(tsconfigLocation, 'utf-8'); - - // Step 2: Parse the raw text into a json object - const { error: configJsonError, config: configJson } = ts.parseConfigFileTextToJson(tsconfigLocation, configText); - if (configJsonError) { - return { - severity: 'error', - results: [configJsonError] - }; - } - - // Step 3: Parse the json object into a config object for use by tsc - const { errors: parseErrors, options: tsconfig } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); - if (parseErrors.length > 0) { - return { - severity: 'error', - results: parseErrors - }; - } - - return { - severity: 'success', - results: tsconfig - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -} - -export async function runTsc(srcDir: string): Promise { - const tsconfigRes = await getTsconfig(srcDir); - if (tsconfigRes.severity === 'error') { - return tsconfigRes; - } - - try { - const tsc = ts.createProgram([], tsconfigRes.results); - const results = tsc.emit(); - const diagnostics = ts.getPreEmitDiagnostics(tsc) - .concat(results.diagnostics); - - return { - severity: diagnostics.length > 0 ? 'error' : 'success', - results: diagnostics - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -} diff --git a/src/buildtools/src/templates/__tests__/names.test.ts b/src/buildtools/src/templates/__tests__/names.test.ts deleted file mode 100644 index 633cca5014..0000000000 --- a/src/buildtools/src/templates/__tests__/names.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { isPascalCase, isSnakeCase } from '../utilities'; - -function testFunction( - func: (value: string) => boolean, - tcs: [string, boolean][] -) { - describe(`Testing ${func.name}`, () => test.each(tcs)('%#: %s', (value, valid) => expect(func(value)) - .toEqual(valid))); -} - -testFunction(isPascalCase, [ - ['PascalCase', true], - ['snake_case', false], - ['Snake_Case', false] -]); - -testFunction(isSnakeCase, [ - ['snake_case', true], - ['arcade_2d', true], - ['PascalCase', false], - ['camelCase', false] -]); diff --git a/src/buildtools/src/templates/bundle.ts b/src/buildtools/src/templates/bundle.ts deleted file mode 100644 index 0cf783020e..0000000000 --- a/src/buildtools/src/templates/bundle.ts +++ /dev/null @@ -1,57 +0,0 @@ -import fs from 'fs/promises'; -import type { Interface } from 'readline/promises'; -import { askQuestion, success, warn } from './print'; -import { check, isSnakeCase } from './utilities'; -import { getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; - -async function askModuleName(manifest: ModulesManifest, rl: Interface) { - while (true) { - const name = await askQuestion('What is the name of your new module? (eg. binary_tree)', rl); - if (!isSnakeCase(name)) { - warn('Module names must be in snake case. (eg. binary_tree)'); - } else if (check(manifest, name)) { - warn('A module with the same name already exists.'); - } else { - return name; - } - } -} - -export async function addNew(bundlesDir: string, rl: Interface) { - const manifest = await getBundleManifests(bundlesDir) - const moduleName = await askModuleName(manifest, rl); - const bundleDestination = `${bundlesDir}/${moduleName}`; - - await fs.cp(`${import.meta.dirname}/templates/bundle`, bundleDestination) - - const packageJson = { - name: `@sourceacademy/bundle-${moduleName}`, - private: true, - version: "1.0.0", - devDependencies: { - "@sourceacademy/modules-buildtools": "workspace:^", - "typescript": "^5.8.2" - }, - type: "module", - scripts: { - tsc: "tsc --project ./tsconfig.json", - build: 'buildtools build bundle .' - }, - exports: { - ".": "./dist/index.js", - "./*": "./dist/*.js", - "./*.js": "./dist/*.js" - } - } - - const bundleManifest: BundleManifest = { - tabs: [] - } - - await Promise.all([ - fs.writeFile(`${bundleDestination}/package.json`, JSON.stringify(packageJson, null, 2)), - fs.writeFile(`${bundleDestination}/manifest.json`, JSON.stringify(bundleManifest, null, 2)) - ]) - - success(`Bundle for module ${moduleName} created at ${bundleDestination}.`); -} diff --git a/src/buildtools/src/templates/print.ts b/src/buildtools/src/templates/print.ts deleted file mode 100644 index f358a2fc66..0000000000 --- a/src/buildtools/src/templates/print.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type Interface, createInterface } from 'readline/promises'; -import chalk from 'chalk'; - -export const getRl = () => createInterface({ - input: process.stdin, - output: process.stdout -}); - -export function info(...args: any[]) { - return console.log(...args.map(string => chalk.grey(string))); -} - -export function error(...args) { - return console.log(...args.map(string => chalk.red(string))); -} - -export function warn(...args) { - return console.log(...args.map(string => chalk.yellow(string))); -} - -export function success(...args) { - return console.log(...args.map(string => chalk.green(string))); -} - -export function askQuestion(question: string, rl: Interface) { - return rl.question(chalk.blueBright(`${question}\n`)); -} diff --git a/src/buildtools/src/templates/tab.ts b/src/buildtools/src/templates/tab.ts deleted file mode 100644 index 6624c3117a..0000000000 --- a/src/buildtools/src/templates/tab.ts +++ /dev/null @@ -1,82 +0,0 @@ -import pathlib from 'path' -import fs from 'fs/promises'; -import type { Interface } from 'readline/promises'; -import { askQuestion, success, warn } from './print'; -import { check, isPascalCase } from './utilities'; -import { getBundleManifest, getBundleManifests, type BundleManifest, type ModulesManifest } from '../build/manifest'; - -async function askModuleName(manifest: ModulesManifest, rl: Interface) { - while (true) { - const name = await askQuestion('Add a new tab to which module?', rl); - if (!check(manifest, name)) { - warn(`Module ${name} does not exist.`); - } else { - return name; - } - } -} - -function checkTabExists(manifest: ModulesManifest, name: string) { - return Object.values(manifest).flatMap(x => x.tabs).includes(name) -} - -async function askTabName(manifest: ModulesManifest, rl: Interface) { - while (true) { - const name = await askQuestion('What is the name of your new tab? (eg. BinaryTree)', rl); - if (checkTabExists(manifest, name)) { - warn('A tab with the same name already exists.'); - } else if (!isPascalCase(name)) { - warn('Tab names must be in pascal case. (eg. BinaryTree)'); - } else { - return name; - } - } -} - -export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) { - const manifest = await getBundleManifests(bundlesDir) - const moduleName = await askModuleName(manifest, rl); - const tabName = await askTabName(manifest, rl); - - // Copy module tab template into correct destination and show success message - const tabDestination = `${tabsDir}/${tabName}`; - await fs.mkdir(tabDestination, { recursive: true }); - - const packageJson = { - name: `@sourceacademy/tab-${tabName}`, - private: true, - version: "1.0.0", - devDependencies: { - "@sourceacademy/modules-buildtools": "workspace:^", - "@types/react": "^18.3.1", - "typescript": "^5.8.2" - }, - dependencies: { - react: "^18.3.1" - }, - scripts: { - "build": "buildtools build tab ." - } - } - - await fs.cp(`${import.meta.dirname}/templates/tabs`, tabDestination) - await Promise.all([ - fs.writeFile(`${tabDestination}/package.json`, JSON.stringify(packageJson, null, 2)), - getBundleManifest(`${bundlesDir}/${moduleName}`) - .then(async bundleManifest => { - const newManifest: BundleManifest = { - ...bundleManifest, - tabs: [ - ...bundleManifest.tabs, - tabName - ] - } - - await fs.writeFile(`${bundlesDir}/${moduleName}/manifest.json`, JSON.stringify(newManifest, null, 2)) - }) - ]) - - success( - `Tab ${tabName} for module ${moduleName} created at ${tabDestination}.` - ); -} diff --git a/src/buildtools/src/templates/utilities.ts b/src/buildtools/src/templates/utilities.ts deleted file mode 100644 index 274ddc1ea7..0000000000 --- a/src/buildtools/src/templates/utilities.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Snake case regex has been changed from `/\b[a-z]+(?:_[a-z]+)*\b/u` to `/\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u` -// to be consistent with the naming of the `arcade_2d` and `physics_2d` modules. - -import type { ModulesManifest } from "../build/manifest"; - -// This change should not affect other modules, since the set of possible names is only expanded. -const snakeCaseRegex = /\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u; -const pascalCaseRegex = /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/u; - -export function isSnakeCase(string: string) { - return snakeCaseRegex.test(string); -} - -export function isPascalCase(string: string) { - return pascalCaseRegex.test(string); -} - -export function check(manifest: ModulesManifest, name: string) { - return Object.keys(manifest).includes(name) -} \ No newline at end of file diff --git a/src/buildtools/src/testing/runner.ts b/src/buildtools/src/testing/runner.ts deleted file mode 100644 index 9736bc204b..0000000000 --- a/src/buildtools/src/testing/runner.ts +++ /dev/null @@ -1,10 +0,0 @@ -import pathlib from 'path' -import jest from 'jest'; - -export function runJest(jestArgs: string[], patterns: string[]) { - const filePatterns = patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)) - return jest.run([ - ...jestArgs, - ...filePatterns - ]); -} diff --git a/src/buildtools/src/utils.ts b/src/buildtools/src/utils.ts deleted file mode 100644 index c82e6b32f2..0000000000 --- a/src/buildtools/src/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { execFile } from "child_process" -import _ from "lodash" -import pathlib from "path" - -function rawGetGitRoot() { - return new Promise((resolve, reject) => { - execFile('git', ['rev-parse', '--show-toplevel'], (err, stdout, stderr) => { - const possibleError = err || stderr - if (possibleError) { - reject(possibleError) - } - - resolve(stdout.trim()) - }) - }) -} - -/** - * Get the path to the root of the git repository - */ -export const getGitRoot = _.memoize(rawGetGitRoot) -export const getBundlesDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'bundles')) -export const getTabsDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'src', 'tabs')) -export const getOutDir = _.memoize(async () => pathlib.join(await getGitRoot(), 'build')) - -export type AwaitedReturn Promise> = Awaited> - -export type Severity = 'success' | 'warn' | 'error' - -export function findSeverity(items: T[], mapper: (each: T) => Severity): Severity { - let output: Severity = 'success' - for (const item of items) { - const severity = mapper(item) - if (severity === 'error') return 'error' - if (severity === 'warn') { - output = 'warn' - } - } - - return output; -} - -export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFixed(2); \ No newline at end of file diff --git a/src/buildtools/tsconfig.json b/src/buildtools/tsconfig.json deleted file mode 100644 index 1e9c99fbe6..0000000000 --- a/src/buildtools/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -// buildtools tsconfig -{ - "compilerOptions": { - "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "noEmit": true, - "target": "ESNext", - "verbatimModuleSyntax": true - }, - "include": ["./src", "jest.setup.ts"], - "exclude": [ - "./bin", - "./src/build/docs/__tests__/test_mocks" - ] -} diff --git a/src/buildtools/workspacer.py b/src/buildtools/workspacer.py deleted file mode 100644 index 9e53c95ae4..0000000000 --- a/src/buildtools/workspacer.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -This file contains a bunch of python scripts that can be used to update all -the tsconfigs or package.jsons of all tabs and bundles at once -(say if a new script needed to be added) -""" - -import os -import json - -def get_tabs(): - for name in os.listdir('./src/tabs'): - if not os.path.isdir(f'./src/tabs/{name}'): - continue - yield name - -def get_bundles(): - for name in os.listdir('./src/bundles'): - if name == '__mocks__': - continue - - if not os.path.isdir(f'./src/bundles/{name}'): - continue - yield name - -def update_tab_packages(): - for name in get_tabs(): - with open(f'./src/tabs/{name}/package.json') as file: - original = json.load(file) - - if '@sourceacademy/module-buildtools' in original['devDependencies']: - del original['devDependencies']['@sourceacademy/module-buildtools'] - original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" - - with open(f'./src/tabs/{name}/package.json', 'w') as file: - json.dump(original, file, indent=2) - -def create_bundle_manifest(): - with open('modules.json') as file: - current_manifest = json.load(file) - - for moduleName in current_manifest.keys(): - with open(f'./src/bundles/{moduleName}/manifest.json', 'w') as manifest_file: - manifest = {} - - if 'tabs' in current_manifest[moduleName]: - manifest['tabs'] = current_manifest[moduleName]['tabs'] - - json.dump(manifest, manifest_file, indent=2) - -def update_tab_tsconfigs(): - for name in get_tabs(): - tsconfigPath = f'./src/tabs/{name}/tsconfig.json' - with open(tsconfigPath) as file: - original = json.load(file) - - with open(f'./src/tabs/{name}/tsconfig.json', 'w') as file: - compilerOptions = original.setdefault('compilerOptions', dict()) - compilerOptions['outDir'] = './dist' - json.dump(original, file, indent=2) - -def update_bundle_tsconfigs(): - for name in os.listdir('./src/bundles'): - if not os.path.isdir(f'./src/bundles/{name}'): - continue - - with open(f'./src/bundles/{name}/tsconfig.json') as file: - original = json.load(file) - - if 'compilerOptions' in original: - original['compilerOptions']['outDir'] = './dist' - else: - original['compilerOptions'] = { - "outDir": './dist' - } - - with open(f'./src/bundles/{name}/tsconfig.json', 'w') as file: - json.dump(original, file, indent=2) - -def update_bundle_packages(): - for name in get_bundles(): - with open(f'./src/bundles/{name}/package.json') as file: - original = json.load(file) - - if '@sourceacademy/module-buildtools' in original['devDependencies']: - del original['devDependencies']['@sourceacademy/module-buildtools'] - original['devDependencies']['@sourceacademy/modules-buildtools'] = "workspace:^" - - with open(f'./src/bundles/{name}/package.json', 'w') as file: - json.dump(original, file, indent=2) - -if __name__ == '__main__': - # create_bundle_manifest() - update_bundle_packages() - # update_bundle_tsconfigs() - # update_tab_packages() - # update_tab_tsconfigs() - \ No newline at end of file diff --git a/src/bundles/__mocks__/context.ts b/src/bundles/__mocks__/context.ts index 1b55789787..bcb9f4c75c 100644 --- a/src/bundles/__mocks__/context.ts +++ b/src/bundles/__mocks__/context.ts @@ -2,4 +2,4 @@ export default { moduleContexts: new Proxy({}, { get: () => ({}) }) -} \ No newline at end of file +}; diff --git a/src/bundles/__mocks__/emptyModule.ts b/src/bundles/__mocks__/emptyModule.ts index 693da49fc4..cb0ff5c3b5 100644 --- a/src/bundles/__mocks__/emptyModule.ts +++ b/src/bundles/__mocks__/emptyModule.ts @@ -1 +1 @@ -export {} \ No newline at end of file +export {}; diff --git a/src/bundles/csg/package.json b/src/bundles/csg/package.json index 68fbbd5003..be20498aa4 100644 --- a/src/bundles/csg/package.json +++ b/src/bundles/csg/package.json @@ -5,7 +5,8 @@ "dependencies": { "@jscad/modeling": "2.9.6", "@jscad/regl-renderer": "^2.6.1", - "@jscad/stl-serializer": "2.1.11" + "@jscad/stl-serializer": "2.1.11", + "save-file": "^2.3.1" }, "exports": { ".": "./dist/index.js", diff --git a/src/bundles/jest.config.js b/src/bundles/jest.config.js index 8d868a8011..34b1dced2c 100644 --- a/src/bundles/jest.config.js +++ b/src/bundles/jest.config.js @@ -14,6 +14,6 @@ export default { './jest.polyfills.js' ], testMatch: [ - "/**/__tests__/**/*.ts" + '/**/__tests__/**/*.ts' ] }; diff --git a/src/bundles/jest.polyfills.js b/src/bundles/jest.polyfills.js index 9018b43bb4..dbc1cb8efd 100644 --- a/src/bundles/jest.polyfills.js +++ b/src/bundles/jest.polyfills.js @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { TextDecoder, TextEncoder } = require('node:util'); +import { TextDecoder, TextEncoder } from 'node:util'; // According to the Jest docs, https://mswjs.io/docs/migrations/1.x-to-2.x#environment Object.defineProperties(globalThis, { diff --git a/src/bundles/physics_2d/src/types.ts b/src/bundles/physics_2d/src/types.ts index 062fc84168..f33988a894 100644 --- a/src/bundles/physics_2d/src/types.ts +++ b/src/bundles/physics_2d/src/types.ts @@ -1,5 +1,5 @@ import { b2Vec2 } from '@box2d/core'; -import type { ReplResult } from '../../typings/type_helpers'; +import type { ReplResult } from '@sourceacademy/modules-lib/types'; export const ACCURACY = 2; export class Vector2 extends b2Vec2 implements ReplResult { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts index 250dbcefc5..3944de81f5 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts @@ -49,7 +49,7 @@ jest.mock('three', () => { ...originalModule, BoxGeometry: jest.fn(), MeshPhysicalMaterial: jest.fn(), - Mesh: jest.fn().mockImplementation(function (geometry, material) { + Mesh: jest.fn().mockImplementation(function (this: any, geometry, material) { this.geometry = geometry; this.material = material; this.position = new Vector3(); diff --git a/src/bundles/rune/src/display.ts b/src/bundles/rune/src/display.ts index bc6593a9b4..87f1da80d5 100644 --- a/src/bundles/rune/src/display.ts +++ b/src/bundles/rune/src/display.ts @@ -1,5 +1,5 @@ -import context from 'js-slang/context'; import { functionDeclaration } from '@sourceacademy/modules-lib/type_map'; +import context from 'js-slang/context'; import { AnaglyphRune, HollusionRune } from './functions'; import { AnimatedRune, NormalRune, type DrawnRune, type Rune, type RuneAnimation } from './rune'; import { throwIfNotRune } from './runes_ops'; diff --git a/src/bundles/rune/src/functions.ts b/src/bundles/rune/src/functions.ts index 2e031c6842..c85beccba0 100644 --- a/src/bundles/rune/src/functions.ts +++ b/src/bundles/rune/src/functions.ts @@ -1,8 +1,8 @@ -import { mat4, vec3 } from 'gl-matrix'; import { functionDeclaration, variableDeclaration, } from '@sourceacademy/modules-lib/type_map'; +import { mat4, vec3 } from 'gl-matrix'; import { Rune, DrawnRune, diff --git a/src/bundles/rune/src/rune.ts b/src/bundles/rune/src/rune.ts index ba6db5f1c5..13914e8c70 100644 --- a/src/bundles/rune/src/rune.ts +++ b/src/bundles/rune/src/rune.ts @@ -1,6 +1,6 @@ +import { classDeclaration } from '@sourceacademy/modules-lib/type_map'; import { type AnimFrame, type ReplResult, glAnimation } from '@sourceacademy/modules-lib/types'; import { mat4 } from 'gl-matrix'; -import { classDeclaration } from '@sourceacademy/modules-lib/type_map'; import { getWebGlFromCanvas, initShaderProgram } from './runes_webgl'; const normalVertexShader = ` diff --git a/src/commons/__tests__/hextocolor.ts b/src/commons/__tests__/hextocolor.ts deleted file mode 100644 index 835095350a..0000000000 --- a/src/commons/__tests__/hextocolor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { hexToColor } from '../utilities'; - -describe('Test hexToColor', () => { - test.each([ - ['#FFFFFF', [1, 1, 1]], - ['ffffff', [1, 1, 1]], - ['0088ff', [0, 0.53, 1]], - ['#000000', [0, 0, 0]], - ['#GGGGGG', [0, 0, 0]], - ['888888', [0.53, 0.53, 0.53]] - ])('Testing %s', (c, expected) => { - const result = hexToColor(c); - for (let i = 0; i < expected.length; i++) { - expect(result[i]).toBeCloseTo(expected[i]); - } - }); -}); diff --git a/src/commons/js-slang/context.d.ts b/src/commons/js-slang/context.d.ts deleted file mode 100644 index 841849bbc1..0000000000 --- a/src/commons/js-slang/context.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { Context } from 'js-slang'; - -const ctx: Context; -export default ctx; diff --git a/src/commons/specialErrors.ts b/src/commons/specialErrors.ts deleted file mode 100644 index 54aa1fbbfa..0000000000 --- a/src/commons/specialErrors.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This function is used to interrupt the frontend execution. - * When called, the frontend will notify that the program has ended successfully - * and display a message that the program is stopped by a module. - */ -export function interrupt() { - throw 'source_academy_interrupt'; -} diff --git a/src/commons/type_map/index.ts b/src/commons/type_map/index.ts deleted file mode 100644 index 014138baa9..0000000000 --- a/src/commons/type_map/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -export const type_map : Record = {}; - -const registerType = (name: string, declaration: string) => { - if (name == 'prelude') { - type_map['prelude'] = type_map['prelude'] != undefined ? type_map['prelude'] + '\n' + declaration : declaration; - } else { - type_map[name] = declaration; - } -}; - -export const classDeclaration = (name: string) => { - return (_target: any) => { - registerType('prelude', `class ${name} {}`); - }; -}; - -export const typeDeclaration = (type: string, declaration = null) => { - return (_target: any) => { - const typeAlias = `type ${_target.name} = ${type}`; - let variableDeclaration = `const ${_target.name} = ${declaration === null ? type : declaration}`; - - switch (type) { - case 'number': - variableDeclaration = `const ${_target.name} = 0`; - break; - case 'string': - variableDeclaration = `const ${_target.name} = ''`; - break; - case 'boolean': - variableDeclaration = `const ${_target.name} = false`; - break; - case 'void': - variableDeclaration = ''; - break; - } - - registerType('prelude', `${typeAlias};\n${variableDeclaration};`); - }; -}; - -export const functionDeclaration = (paramTypes: string, returnType: string) => { - return (_target: any, propertyKey: string, _descriptor: PropertyDescriptor) => { - let returnValue = ''; - switch (returnType) { - case 'number': - returnValue = 'return 0'; - break; - case 'string': - returnValue = "return ''"; - break; - case 'boolean': - returnValue = 'return false'; - break; - case 'void': - returnValue = ''; - break; - default: - returnValue = `return ${returnType}`; - break; - } - registerType(propertyKey, `function ${propertyKey} (${paramTypes}) : ${returnType} { ${returnValue} }`); - }; -}; - -export const variableDeclaration = (type: string) => { - return (_target: any, propertyKey: string) => { - registerType(propertyKey, `const ${propertyKey}: ${type} = ${type}`); - }; -}; diff --git a/src/commons/types.ts b/src/commons/types.ts deleted file mode 100644 index d032ecfcf9..0000000000 --- a/src/commons/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Context } from 'js-slang'; -import type { FC } from 'react'; - -/** - * Represents an animation drawn using WebGL - * @field duration Duration of the animation in secondss - * @field fps Framerate in frames per second - */ -export abstract class glAnimation { - constructor(public readonly duration: number, public readonly fps: number) { } - - public abstract getFrame(timestamp: number): AnimFrame; - - public static isAnimation = (obj: any): obj is glAnimation => obj.fps !== undefined; -} -export interface AnimFrame { - draw: (canvas: HTMLCanvasElement) => void; -} -export type DeepPartial = T extends object ? { - [P in keyof T]?: DeepPartial; -} : T; - -/** - * DebuggerContext type used by frontend to assist typing information - */ -export type DebuggerContext = { - result: any; - lastDebuggerResult: any; - code: string; - context: Context; - workspaceLocation?: any; -}; - -export type ModuleContexts = Context['moduleContexts']; - -/** - * Interface to represent objects that require a string representation in the - * REPL - */ -export interface ReplResult { - toReplString: () => string; -} - -export type ModuleTab = FC<{ context: DebuggerContext }>; - -export const getModuleState = ({ context: { moduleContexts } }: DebuggerContext, moduleName: string) => moduleContexts[moduleName].state as T; diff --git a/src/commons/utilities.ts b/src/commons/utilities.ts deleted file mode 100644 index f25780ffdd..0000000000 --- a/src/commons/utilities.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* [Exports] */ -export function degreesToRadians(degrees: number): number { - return (degrees / 360) * (2 * Math.PI); -} - -export function hexToColor(hex: string): [number, number, number] { - const regex = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/igu; - const groups = regex.exec(hex); - - if (groups == undefined) return [0, 0, 0]; - return [ - parseInt(groups[1], 16) / 0xff, - parseInt(groups[2], 16) / 0xff, - parseInt(groups[3], 16) / 0xff - ]; -} diff --git a/src/jest.config.js b/src/jest.config.js deleted file mode 100644 index c678980546..0000000000 --- a/src/jest.config.js +++ /dev/null @@ -1,35 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -export default { - displayName: 'Modules', - // preset: 'ts-jest', - testEnvironment: 'jsdom', - modulePaths: [ - '' - ], - transform: { - '.ts': ['ts-jest', { - useESM: true, - /** - * ts-jest preset currently has an issue with the 'verbatimModuleSyntax' typescript option: - * This whole transform bit should be removed once this is resolved: - * https://github.com/kulshekhar/ts-jest/issues/4081 - */ - isolatedModules: true - }] - }, - moduleNameMapper: { - // Module Name settings required to make chalk work with jest - '#(.*)': '/node_modules/$1', - '^js-slang/context': '/__mocks__/context.ts', - '^three.+.js': '/__mocks__/emptyModule.js', - // 'lowdb': '/node_modules/lowdb/lib', - // 'steno': '/node_modules/steno/lib', - }, - transformIgnorePatterns: [ - 'node_modules/(?!=chalk)/', - '.+\\.js' - ], - setupFiles: [ - './jest.polyfills.js' - ] -}; diff --git a/src/jest.polyfills.js b/src/jest.polyfills.js deleted file mode 100644 index 9018b43bb4..0000000000 --- a/src/jest.polyfills.js +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { TextDecoder, TextEncoder } = require('node:util'); - -// According to the Jest docs, https://mswjs.io/docs/migrations/1.x-to-2.x#environment -Object.defineProperties(globalThis, { - TextDecoder: { value: TextDecoder }, - TextEncoder: { value: TextEncoder }, -}); diff --git a/src/lintplugin/package.json b/src/lintplugin/package.json deleted file mode 100644 index 4aa4c420f9..0000000000 --- a/src/lintplugin/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@sourceacademy/lint-plugin", - "version": "1.0.0", - "type": "module", - "exports": { - ".": "./dist/index.js" - }, - "dependencies": { - "@es-joy/jsdoccomment": "^0.50.1", - "@typescript-eslint/utils": "^8.32.1", - "eslint-plugin-import": "^2.31.0", - "typescript-eslint": "^8.24.1" - }, - "peerDependencies": { - "eslint": "^9.21.0" - }, - "devDependencies": { - "@typescript-eslint/rule-tester": "^8.32.1", - "jest": "^29.7.0", - "ts-jest": "^29.1.2", - "typescript": "^5.8.2" - }, - "scripts": { - "build": "tsc --project ./tsconfig.prod.json", - "tsc": "tsc --project ./tsconfig.json", - "test": "jest" - }, - "jest": { - "displayName": "Lint Plugin", - "extensionsToTreatAsEsm": [ - ".ts" - ], - "preset": "ts-jest/presets/default-esm", - "testMatch": [ - "/src/**/__tests__/**/*.test.ts" - ] - } -} diff --git a/src/lintplugin/src/configs.ts b/src/lintplugin/src/configs.ts deleted file mode 100644 index 95d7bedc49..0000000000 --- a/src/lintplugin/src/configs.ts +++ /dev/null @@ -1,78 +0,0 @@ -import stylePlugin from '@stylistic/eslint-plugin'; -import type { Linter } from 'eslint' -import * as importPlugin from 'eslint-plugin-import'; -import tseslint from 'typescript-eslint'; -import globals from 'globals' -import saLintPlugin from '.' - -const todoTreeKeywordsWarning = ['TODO', 'TODOS', 'TODO WIP', 'FIXME', 'WIP']; -const todoTreeKeywordsAll = [...todoTreeKeywordsWarning, 'NOTE', 'NOTES', 'LIST']; - -export const jsConfig = { - // Global JS Rules - languageOptions: { - globals: { - ...globals.node, - ...globals.es2022 - } - }, - plugins: { - import: importPlugin, - '@stylistic': stylePlugin, - }, - rules: { - 'import/no-duplicates': ['warn', { 'prefer-inline': false }], - 'import/order': [ - 'warn', - { - groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], - alphabetize: { - order: 'asc', - orderImportKind: 'asc' - }, - } - ], - - '@stylistic/brace-style': ['warn', '1tbs', { allowSingleLine: true }], - '@stylistic/eol-last': 'warn', - '@stylistic/indent': ['warn', 2, { SwitchCase: 1 }], - '@stylistic/no-mixed-spaces-and-tabs': 'warn', - '@stylistic/no-multi-spaces': 'warn', - '@stylistic/no-multiple-empty-lines': ['warn', { max: 1, maxEOF: 0 }], - '@stylistic/no-trailing-spaces': 'warn', - '@stylistic/quotes': ['warn', 'single', { avoidEscape: true }], - '@stylistic/semi': ['warn', 'always'], - '@stylistic/spaced-comment': [ - 'warn', - 'always', - { markers: todoTreeKeywordsAll } - ], - } -} satisfies Linter.Config - -export const tsConfig = { - // Global typescript rules - files: ['**/*.ts*'], - languageOptions: { - // @ts-expect-error typescript eslint's type definitions are different - parser: tseslint.parser - }, - plugins: { - // @ts-expect-error typescript eslint's type definitions are different - '@typescript-eslint': tseslint.plugin, - '@sourceacademy': saLintPlugin - }, - rules: { - 'no-unused-vars': 'off', // Use the typescript eslint rule instead - - '@typescript-eslint/ban-types': 'off', // Was 'error' - '@typescript-eslint/no-duplicate-type-constituents': 'off', // Was 'error' - '@typescript-eslint/no-explicit-any': 'off', // Was 'error' - '@typescript-eslint/no-redundant-type-constituents': 'off', // Was 'error' - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], // Was 'error' - '@typescript-eslint/prefer-ts-expect-error': 'warn', - '@typescript-eslint/sort-type-constituents': 'warn', - - '@sourceacademy/collate-type-imports': 'warn' - } -} satisfies Linter.Config \ No newline at end of file diff --git a/src/lintplugin/src/index.ts b/src/lintplugin/src/index.ts deleted file mode 100644 index 37b03e1569..0000000000 --- a/src/lintplugin/src/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ESLint } from 'eslint'; -import collateTypeImports from './rules/typeimports.js'; -import { jsConfig, tsConfig } from './configs.js'; -import moduleTagPresent from './rules/moduleTag.js'; - -const plugin: ESLint.Plugin = { - name: 'Source Academy Lint Plugin', - rules: { - 'collate-type-imports': collateTypeImports, - 'module-tag-present': moduleTagPresent - }, - configs: { - js: jsConfig, - ts: tsConfig - } -} - -export default plugin; diff --git a/src/lintplugin/src/rules/__tests__/modulestag.test.ts b/src/lintplugin/src/rules/__tests__/modulestag.test.ts deleted file mode 100644 index 4c275ed9c5..0000000000 --- a/src/lintplugin/src/rules/__tests__/modulestag.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { RuleTester } from 'eslint'; -import moduleTagPresent from '../moduleTag' - -describe('Test moduleTagPresent', () => { - const tester = new RuleTester({ - 'languageOptions': { - // eslint-disable-next-line @typescript-eslint/no-require-imports - parser: require('@typescript-eslint/parser'), - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } - }, - }); - - tester.run( - 'module-tag-present', - moduleTagPresent, - { - valid: [` - /** - * @module hi - */ - `], - invalid: [ - // { - // code: '', - // errors: 1, - // output: '/**\n * @module module_name\n */\n' - // }, { - // code: '// A comment', - // errors: 1, - // output: '/**\n * @module module_name\n */\n// A comment' - // }, { - // code: '/* A comment */', - // errors: 1, - // output: '/**\n * @module module_name\n */\n/* A comment */' - // }, { - // code: '/**\n *\n */', - // errors: 1, - // output: '/**\n * @module module_name\n */\n/**\n *\n */' - // }, - { - code: ` - /** - * @author yo - */ - `, - errors: 1, - output: '/**\n * @author yo\n * @module module_name\n */' - }] - } - ) -}) \ No newline at end of file diff --git a/src/lintplugin/src/rules/__tests__/typeimports.test.ts b/src/lintplugin/src/rules/__tests__/typeimports.test.ts deleted file mode 100644 index 32c465e99b..0000000000 --- a/src/lintplugin/src/rules/__tests__/typeimports.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; -import collateTypeImports from '../typeimports'; - -describe('Test collateTypeImports', () => { - const tester = new RuleTester(); - tester.run( - 'collate-type-imports', - collateTypeImports, - { - valid: [ - 'import type { a, b } from "wherever"', - '', - 'import { type a, b } from "wherever"', - 'import { a, b } from "wherever"', - 'import a, { type b } from "wherever"', - 'import type { a as b } from "wherever"', - 'import { type a as b, c } from "wherever"', - 'import "wherever"', - ], - invalid: [{ - code: 'import { type a, type b } from "wherever"', - errors: [{ messageId: 'msg' }], - output: 'import type { a, b } from \'wherever\'' - }, { - code: 'import { type a } from "wherever"', - errors: [{ messageId: 'msg' }], - output: 'import type { a } from \'wherever\'' - }, { - code: 'import { type a as b } from "wherever"', - errors: [{ messageId: 'msg' }], - output: "import type { a as b } from 'wherever'" - }] - } - ); -}); diff --git a/src/lintplugin/src/rules/moduleTag.ts b/src/lintplugin/src/rules/moduleTag.ts deleted file mode 100644 index 46a6771495..0000000000 --- a/src/lintplugin/src/rules/moduleTag.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { parseComment, getJSDocComment } from '@es-joy/jsdoccomment' -import type { Rule } from "eslint"; - -const moduleTagPresent = { - meta: { - type: 'suggestion', - hasSuggestions: true, - fixable: 'code' - }, - create: ({ sourceCode, report }) => ({ - Program(node) { - let maxLines: number - - if (node.body.length === 0) { - maxLines = 1 - } else { - const firstNode = node.body[0] - maxLines = firstNode.loc.start.line - } - - const comment = getJSDocComment(sourceCode, node as any, { - maxLines, - minLines: 1 - }) - - if (comment === null) { - report({ - node, - message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', - fix: fixer => [ - fixer.insertTextBeforeRange([0, 0], '/**\n * @module module_name\n */\n') - ] - }) - return; - } - - const parsed = parseComment(comment) - const tag = parsed.tags.find(tag => tag.tag === 'module') - - if (!tag) { - console.log(comment.loc.end) - const commentEndLine = (comment as any).loc.end.line - report({ - node, - message: 'Bundle requires a JSDOC comment at its top with a @module tag specified', - fix: fixer => [ - fixer.insertTextAfterRange([commentEndLine, commentEndLine], '\n * @module module_name\n') - ] - }) - return - } - } - }) -} satisfies Rule.RuleModule - -export default moduleTagPresent \ No newline at end of file diff --git a/src/lintplugin/src/rules/tabType.ts b/src/lintplugin/src/rules/tabType.ts deleted file mode 100644 index 7d54d85d54..0000000000 --- a/src/lintplugin/src/rules/tabType.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { ESLintUtils, type TSESTree as es } from "@typescript-eslint/utils"; - -const createRule = ESLintUtils.RuleCreator.withoutDocs - -const tabType = createRule({ - meta: { - type: 'problem', - schema: [{ - type: 'string', - description: 'Import path' - }], - messages: { - noExport: 'Your tab should export an object using the defineTab helper', - useHelper: 'Use the defineTab helper from {{ source }}' - } - }, - defaultOptions: [ - '@sourceacademy/modules-lib/tabs/utils' - ], - create: (context, options) => ({ - Program(node) { - const exportNode = node.body.find((stmt): stmt is es.ExportDefaultDeclaration => stmt.type === 'ExportDefaultDeclaration') - if (!exportNode) { - context.report({ - messageId: 'noExport', - node - }) - return - } - - const { declaration: exportDeclaration } = exportNode - if (exportDeclaration.type !== 'CallExpression') { - context.report({ - messageId: 'useHelper', - data: { - source: options[0] - }, - node: exportDeclaration - }) - return - } - - const importDeclarations = node.body.filter((stmt): stmt is es.ImportDeclaration => { - return stmt.type === 'ImportDeclaration' && stmt.source.value === options[0] - }) - - if (importDeclarations.length === 0) { - context.report({ - messageId: 'useHelper', - data: { - source: options[0] - }, - node - }) - return - } - - const specifiers = importDeclarations.flatMap(({ specifiers }) => specifiers) - .filter(spec => spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier' && spec.imported.name === 'defineTab') - - const defineNames = specifiers.map(spec => spec.local.name) - - const { callee } = exportDeclaration - if (callee.type !== 'Identifier' || defineNames.includes(callee.name)) { - context.report({ - messageId: 'useHelper', - data: { - source: options[0] - }, - node: callee - }) - return - } - } - }) -}) - -export default tabType \ No newline at end of file diff --git a/src/lintplugin/src/rules/typeimports.ts b/src/lintplugin/src/rules/typeimports.ts deleted file mode 100644 index ef98741c59..0000000000 --- a/src/lintplugin/src/rules/typeimports.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ESLintUtils, type TSESTree as es } from '@typescript-eslint/utils'; - -const createRule = ESLintUtils.RuleCreator.withoutDocs - -function isImportSpecifier(spec: es.ImportDeclaration['specifiers'][number]): spec is es.ImportSpecifier { - return spec.type === 'ImportSpecifier'; -} - -function specToString(spec: es.ImportSpecifier) { - if (spec.imported.type === 'Identifier') { - if (spec.imported.name === spec.local.name) { - return spec.imported.name; - } - return `${spec.imported.name} as ${spec.local.name}`; - } - return ''; -} - -const collateTypeImports = createRule({ - meta: { - type: 'suggestion', - messages: { - msg: 'Use a single type specifier', - }, - schema: [], - hasSuggestions: true, - fixable: 'code' - }, - defaultOptions: [], - create: context => ({ - ImportDeclaration(node) { - if (node.importKind === 'type' || node.specifiers.length === 0) return; - - if (node.specifiers.some(spec => !isImportSpecifier(spec) || spec.importKind !== 'type')) return; - - context.report({ - node, - messageId: 'msg', - fix(fixer) { - const regularSpecs = (node.specifiers as es.ImportSpecifier[]).map(specToString); - - return [ - fixer.remove(node), - fixer.insertTextAfter( - node, - `import type { ${regularSpecs.join(', ')} } from '${node.source.value}'` - ) - ]; - } - }); - } - }) -}) - -export default collateTypeImports; diff --git a/src/lintplugin/tsconfig.json b/src/lintplugin/tsconfig.json deleted file mode 100644 index b887a15b86..0000000000 --- a/src/lintplugin/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -// lintplugin with tests -{ - "compilerOptions": { - "esModuleInterop": true, - "isolatedModules": true, - "module": "ESNext", - "moduleResolution": "bundler", - "noEmit": true, - "target": "ESNext" - }, - "include": ["./src"], -} \ No newline at end of file diff --git a/src/modules-lib/package.json b/src/modules-lib/package.json index 03151c246c..94862461f5 100644 --- a/src/modules-lib/package.json +++ b/src/modules-lib/package.json @@ -1,22 +1,18 @@ { "name": "@sourceacademy/modules-lib", - "description": "Common library used by Source Acaddemy bundles and tabs", + "description": "Common library used by Source Academy bundles and tabs", "private": true, + "type": "module", "devDependencies": { - "@types/react": "^18.3.2", - "@types/react-dom": "^18.3.0", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", "jest": "^29.7.0", "ts-jest": "^29.1.2", "typescript": "^5.8.2" }, "exports": { - "./types": "./dist/types/index.js", - "./tabs/*": "./dist/tabs/*.js", - "./utilities": "./dist/utilities.js", - "./utilities.js": "./dist/utilities.js", - "./specialErrors": "./dist/specialErrors.js", - "./specialErrors.js": "./dist/specialErrors.js", - "./type_map": "./dist/type_map.js" + "./*": "./dist/*.js", + "./types": "./dist/types/index.js" }, "dependencies": { "@blueprintjs/core": "^5.10.2", @@ -26,7 +22,9 @@ "react-dom": "^18.3.1" }, "scripts": { - "build": "tsc --project ./tsconfig.json" + "build": "tsc --project ./tsconfig.prod.json", + "tsc": "tsc --project ./tsconfig.json", + "test": "jest" }, "jest": { "displayName": "Modules Library", diff --git a/src/modules-lib/src/tabs/utils.ts b/src/modules-lib/src/tabs/utils.ts index 7e46833827..c9c9429b1d 100644 --- a/src/modules-lib/src/tabs/utils.ts +++ b/src/modules-lib/src/tabs/utils.ts @@ -1,5 +1,34 @@ +import type { IconName } from '@blueprintjs/icons'; import type { DebuggerContext } from '../types'; export function getModuleState({ context }: DebuggerContext, name: string): T { return context.moduleContexts[name].state; } + +/** + * Helper for typing tabs + */ +export function defineTab(tab: { + /** + * BlueprintJS IconName element's name, used to render the icon which will be + * displayed in the side contents panel. + * @see https://blueprintjs.com/docs/#icons + */ + iconName: IconName + /** + * The Tab's icon tooltip in the side contents on Source Academy frontend. + */ + label: string + /** + * This function will be called to determine if the component will be + * rendered + */ + toSpawn?: (context: DebuggerContext) => boolean + /** + * This function will be called to render the module tab in the side contents + * on Source Academy frontend. + */ + body: (context: DebuggerContext) => JSX.Element +}) { + return tab; +} diff --git a/src/modules-lib/src/types/js-slang/context.d.ts b/src/modules-lib/src/types/js-slang/context.d.ts index 841849bbc1..55b36bda11 100644 --- a/src/modules-lib/src/types/js-slang/context.d.ts +++ b/src/modules-lib/src/types/js-slang/context.d.ts @@ -1,4 +1,4 @@ import type { Context } from 'js-slang'; -const ctx: Context; +declare const ctx: Context; export default ctx; diff --git a/src/modules-lib/src/utilities.ts b/src/modules-lib/src/utilities.ts index f25780ffdd..8e4077a6ab 100644 --- a/src/modules-lib/src/utilities.ts +++ b/src/modules-lib/src/utilities.ts @@ -1,3 +1,5 @@ +import type { DebuggerContext } from './types'; + /* [Exports] */ export function degreesToRadians(degrees: number): number { return (degrees / 360) * (2 * Math.PI); @@ -14,3 +16,13 @@ export function hexToColor(hex: string): [number, number, number] { parseInt(groups[3], 16) / 0xff ]; } + +export function mockDebuggerContext(state: T, module: string) { + return { + context: { + [module]: { + state + } + } + } as unknown as DebuggerContext; +} diff --git a/src/modules-lib/tsconfig.json b/src/modules-lib/tsconfig.json index 296d071d78..048902c889 100644 --- a/src/modules-lib/tsconfig.json +++ b/src/modules-lib/tsconfig.json @@ -1,9 +1,7 @@ -// modules-lib +// modules-lib with tests { "compilerOptions": { - "declaration": true, - "noEmit": false, - "outDir": "dist" + "noEmit": true }, "extends": ["../tsconfig.json", "../tabs/tsconfig.json"], "include": ["./src"] diff --git a/src/lintplugin/tsconfig.prod.json b/src/modules-lib/tsconfig.prod.json similarity index 62% rename from src/lintplugin/tsconfig.prod.json rename to src/modules-lib/tsconfig.prod.json index f5000f7fc2..f37c46b5db 100644 --- a/src/lintplugin/tsconfig.prod.json +++ b/src/modules-lib/tsconfig.prod.json @@ -1,9 +1,10 @@ -// lintplugin build config +// modules-lib build tsconfig { "compilerOptions": { "declaration": true, "noEmit": false, - "outDir": "./dist" + "outDir": "./dist", + "removeComments": false }, "extends": "./tsconfig.json", "exclude": ["**/__tests__"] diff --git a/src/tabs/ArcadeTwod/index.tsx b/src/tabs/ArcadeTwod/index.tsx index f8b570cedb..68e35ed218 100644 --- a/src/tabs/ArcadeTwod/index.tsx +++ b/src/tabs/ArcadeTwod/index.tsx @@ -1,6 +1,6 @@ import { Button, ButtonGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import Phaser from 'phaser'; import React from 'react'; @@ -142,39 +142,15 @@ class GameTab extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when there is a stored game config, or if - * the string in the REPL is "[Arcade2D]". - * context.result.value is the return value from the playground code. - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn(context: DebuggerContext) { +export default defineTab({ + toSpawn(context) { const config = context.result?.value?.gameConfig; if (config) { return true; } return false; }, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: DebuggerContext) => , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ + body: context => , label: 'Arcade2D Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: IconNames.SHAPES -}; +}); diff --git a/src/tabs/ArcadeTwod/package.json b/src/tabs/ArcadeTwod/package.json index 435f23a2dd..a871148e31 100644 --- a/src/tabs/ArcadeTwod/package.json +++ b/src/tabs/ArcadeTwod/package.json @@ -5,14 +5,16 @@ "dependencies": { "@blueprintjs/core": "^5.10.2", "@blueprintjs/icons": "^5.9.0", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/AugmentedReality/package.json b/src/tabs/AugmentedReality/package.json index 4bfdc429fd..25d235ec7d 100644 --- a/src/tabs/AugmentedReality/package.json +++ b/src/tabs/AugmentedReality/package.json @@ -3,17 +3,19 @@ "version": "1.0.0", "private": true, "dependencies": { + "@blueprintjs/icons": "^5.9.0", + "@sourceacademy/bundle-ar": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1", "saar": "^1.0.4" }, "devDependencies": { - "@sourceacademy/bundle-ar": "workspace:^", - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/AugmentedReality/src/AugmentedContent.tsx b/src/tabs/AugmentedReality/src/AugmentedContent.tsx index 8976b8862c..7fb22a6635 100644 --- a/src/tabs/AugmentedReality/src/AugmentedContent.tsx +++ b/src/tabs/AugmentedReality/src/AugmentedContent.tsx @@ -1,14 +1,14 @@ -import { useState, type RefObject, useRef, useEffect } from 'react'; -import { usePlayArea } from 'saar/libraries/calibration_library/PlayAreaContext'; -import { useControls } from 'saar/libraries/controls_library/ControlsContext'; -import { ARObject } from 'saar/libraries/object_state_library/ARObject'; -import { useScreenState } from 'saar/libraries/screen_state_library/ScreenStateContext'; import { getModuleState, type ARState, setFrontObject, } from '@sourceacademy/bundle-ar/AR'; import type { OverlayHelper } from '@sourceacademy/bundle-ar/OverlayHelper'; +import { useState, type RefObject, useRef, useEffect } from 'react'; +import { usePlayArea } from 'saar/libraries/calibration_library/PlayAreaContext'; +import { useControls } from 'saar/libraries/controls_library/ControlsContext'; +import { ARObject } from 'saar/libraries/object_state_library/ARObject'; +import { useScreenState } from 'saar/libraries/screen_state_library/ScreenStateContext'; /** * Content to be shown on screen. diff --git a/src/tabs/AugmentedReality/src/AugmentedLayer.tsx b/src/tabs/AugmentedReality/src/AugmentedLayer.tsx index 5b301f3c45..d9d3ad387a 100644 --- a/src/tabs/AugmentedReality/src/AugmentedLayer.tsx +++ b/src/tabs/AugmentedReality/src/AugmentedLayer.tsx @@ -1,6 +1,6 @@ +import type { ARState } from '@sourceacademy/bundle-ar/AR'; import { PlayAreaContext } from 'saar/libraries/calibration_library/PlayAreaContext'; import { ControlsContext } from 'saar/libraries/controls_library/ControlsContext'; -import type { ARState } from '@sourceacademy/bundle-ar/AR'; import { AugmentedContent } from './AugmentedContent'; /** diff --git a/src/tabs/AugmentedReality/src/index.tsx b/src/tabs/AugmentedReality/src/index.tsx index e93fef839b..9ebd333850 100644 --- a/src/tabs/AugmentedReality/src/index.tsx +++ b/src/tabs/AugmentedReality/src/index.tsx @@ -1,4 +1,6 @@ +import { IconNames } from '@blueprintjs/icons'; import { getModuleState } from '@sourceacademy/bundle-ar/AR'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; import { ScreenStateContext } from 'saar/libraries/screen_state_library/ScreenStateContext'; import { StartButton } from './StartButton'; @@ -31,31 +33,9 @@ class ARMainComponent extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn: (_: any) => getModuleState() !== undefined, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (_context: any) => , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ +export default defineTab({ + toSpawn: () => getModuleState() !== undefined, + body: () => , label: 'AR Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ - iconName: 'intelligence', -}; + iconName: IconNames.Intelligence +}); diff --git a/src/tabs/CopyGc/package.json b/src/tabs/CopyGc/package.json index 0ff1a6529b..17ffc34352 100644 --- a/src/tabs/CopyGc/package.json +++ b/src/tabs/CopyGc/package.json @@ -3,14 +3,16 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/CopyGc/src/index.tsx b/src/tabs/CopyGc/src/index.tsx index 39e13d0e81..f1f9e3974b 100644 --- a/src/tabs/CopyGc/src/index.tsx +++ b/src/tabs/CopyGc/src/index.tsx @@ -1,5 +1,6 @@ import { Slider, Icon } from '@blueprintjs/core'; import { COMMAND } from '@sourceacademy/bundle-copy_gc/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; import { ThemeColor } from './style'; @@ -453,9 +454,9 @@ class CopyGC extends React.Component { } } -export default { +export default defineTab({ toSpawn: () => true, - body: (debuggerContext: any) => , + body: (debuggerContext) => , label: 'Copying Garbage Collector', iconName: 'duplicate' -}; +}); diff --git a/src/tabs/Csg/package.json b/src/tabs/Csg/package.json index 89d6d6b652..f9fcfbea8d 100644 --- a/src/tabs/Csg/package.json +++ b/src/tabs/Csg/package.json @@ -11,10 +11,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Csg/src/index.tsx b/src/tabs/Csg/src/index.tsx index f196dc3226..29ba5cd40f 100644 --- a/src/tabs/Csg/src/index.tsx +++ b/src/tabs/Csg/src/index.tsx @@ -1,13 +1,14 @@ /* [Imports] */ import { IconNames } from '@blueprintjs/icons'; -import { Core } from '@sourceacademy/bundle-csg/Core'; +import { Core } from '@sourceacademy/bundle-csg/core'; import type { CsgModuleState } from '@sourceacademy/bundle-csg/utilities'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import type { ReactElement } from 'react'; import CanvasHolder from './canvas_holder'; /* [Exports] */ -export default { +export default defineTab({ // Called by the frontend to decide whether to spawn the CSG tab toSpawn(debuggerContext: DebuggerContext): boolean { const moduleState: CsgModuleState = debuggerContext.context.moduleContexts.csg.state; @@ -32,4 +33,4 @@ export default { // Icon tooltip in sidebar label: 'CSG Tab' -}; +}); diff --git a/src/tabs/Curve/package.json b/src/tabs/Curve/package.json index 5d5b1659e7..61cb1a3425 100644 --- a/src/tabs/Curve/package.json +++ b/src/tabs/Curve/package.json @@ -11,10 +11,12 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", - "@types/react": "^18.3.1" + "@sourceacademy/modules-buildtools": "workspace:^", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Curve/src/__tests__/Curve.tsx b/src/tabs/Curve/src/__tests__/Curve.tsx index 7e384c9c4f..fbfc411932 100644 --- a/src/tabs/Curve/src/__tests__/Curve.tsx +++ b/src/tabs/Curve/src/__tests__/Curve.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { CurveTab } from '..'; import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected } from '@sourceacademy/bundle-curve'; import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; import { mockDebuggerContext } from '@sourceacademy/modules-lib/utilities'; +import { CurveTab } from '..'; test('Curve animations error gracefully', () => { const badAnimation = animate_curve(1, 60, draw_connected(200), t => 1 as any); diff --git a/src/tabs/Curve/src/animation_canvas_3d_curve.tsx b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx index e6e12b47d5..13774aec3d 100644 --- a/src/tabs/Curve/src/animation_canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx @@ -1,12 +1,12 @@ import { Icon, Slider, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React from 'react'; import type { AnimatedCurve } from '@sourceacademy/bundle-curve/types'; import AutoLoopSwitch from '@sourceacademy/modules-lib/tabs/AutoLoopSwitch'; import ButtonComponent from '@sourceacademy/modules-lib/tabs/ButtonComponent'; import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton'; import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; +import React from 'react'; type Props = { animation: AnimatedCurve; diff --git a/src/tabs/Curve/src/canvas_3d_curve.tsx b/src/tabs/Curve/src/canvas_3d_curve.tsx index 552134cee5..bae4fd8a5a 100644 --- a/src/tabs/Curve/src/canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/canvas_3d_curve.tsx @@ -1,10 +1,10 @@ import { Slider } from '@blueprintjs/core'; -import React from 'react'; import type { CurveDrawn } from '@sourceacademy/bundle-curve/curves_webgl'; -import { degreesToRadians } from '@sourceacademy/modules-lib/utilities'; import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton'; import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; +import { degreesToRadians } from '@sourceacademy/modules-lib/utilities'; +import React from 'react'; type State = { /** diff --git a/src/tabs/Curve/src/index.tsx b/src/tabs/Curve/src/index.tsx index 2f2299263e..a3129857d6 100644 --- a/src/tabs/Curve/src/index.tsx +++ b/src/tabs/Curve/src/index.tsx @@ -1,9 +1,10 @@ +import { IconNames } from '@blueprintjs/icons'; import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; +import AnimationCanvas from '@sourceacademy/modules-lib/tabs/AnimationCanvas'; import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; -import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; -import AnimationCanvas from '@sourceacdemy/modules-lib/tabs/AnimationCanvas'; import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; import CurveCanvas3D from './canvas_3d_curve'; @@ -41,7 +42,7 @@ export const CurveTab: ModuleTab = ({ context }) => { return ; }; -export default { +export default defineTab({ toSpawn(context: DebuggerContext) { const drawnCurves = context.context?.moduleContexts?.curve?.state?.drawnCurves; return drawnCurves.length > 0; @@ -50,5 +51,5 @@ export default { return ; }, label: 'Curves Tab', - iconName: 'media' // See https://blueprintjs.com/docs/#icons for more options -}; + iconName: IconNames.MEDIA // See https://blueprintjs.com/docs/#icons for more options +}); diff --git a/src/tabs/Game/package.json b/src/tabs/Game/package.json index a533dcba0c..d8f41e5037 100644 --- a/src/tabs/Game/package.json +++ b/src/tabs/Game/package.json @@ -3,14 +3,16 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Game/src/index.tsx b/src/tabs/Game/src/index.tsx index f76010c81c..e2b3a0281f 100644 --- a/src/tabs/Game/src/index.tsx +++ b/src/tabs/Game/src/index.tsx @@ -1,3 +1,4 @@ +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; import { Links } from './constants'; @@ -33,9 +34,9 @@ class Game extends React.PureComponent { } } -export default { +export default defineTab({ toSpawn: () => true, body: (debuggerContext: any) => , label: 'Game Info Tab', iconName: 'info-sign' -}; +}); diff --git a/src/tabs/MarkSweep/package.json b/src/tabs/MarkSweep/package.json index 9fcd13dfb3..46e735f633 100644 --- a/src/tabs/MarkSweep/package.json +++ b/src/tabs/MarkSweep/package.json @@ -3,14 +3,16 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/MarkSweep/src/index.tsx b/src/tabs/MarkSweep/src/index.tsx index 2c9f38be5c..d78adbdcda 100644 --- a/src/tabs/MarkSweep/src/index.tsx +++ b/src/tabs/MarkSweep/src/index.tsx @@ -1,4 +1,5 @@ import { Slider, Icon } from '@blueprintjs/core'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; import { ThemeColor } from './style'; @@ -463,11 +464,11 @@ class MarkSweep extends React.Component { } } -export default { +export default defineTab({ toSpawn: () => true, body: (debuggerContext: any) => ( ), label: 'Mark Sweep Garbage Collector', iconName: 'heat-grid' -}; +}); diff --git a/src/tabs/Nbody/index.tsx b/src/tabs/Nbody/index.tsx index 3487c47186..7f1f9df90c 100644 --- a/src/tabs/Nbody/index.tsx +++ b/src/tabs/Nbody/index.tsx @@ -1,5 +1,6 @@ import { Button, ButtonGroup, NumericInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import type { Simulation } from 'nbody'; import React from 'react'; @@ -185,35 +186,13 @@ class Nbody extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {any} context - * @returns {boolean} - */ - toSpawn(context: any) { +export default defineTab({ + toSpawn(context) { console.log('Nbody tospawn'); const simulations = context.context?.moduleContexts?.nbody.state.simulations; return simulations.length > 0; }, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: any) => , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ + body: (context) => , label: 'Nbody Viz Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'clean', -}; +}); diff --git a/src/tabs/Nbody/package.json b/src/tabs/Nbody/package.json index 1165b9d2b2..c98d5a77d9 100644 --- a/src/tabs/Nbody/package.json +++ b/src/tabs/Nbody/package.json @@ -3,15 +3,17 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "nbody": "^0.2.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Painter/index.tsx b/src/tabs/Painter/index.tsx index 2191401087..7c5235d86e 100644 --- a/src/tabs/Painter/index.tsx +++ b/src/tabs/Painter/index.tsx @@ -1,6 +1,6 @@ import type { LinePlot } from '@sourceacademy/bundle-painter/painter'; import Modal from '@sourceacademy/modules-lib/tabs/ModalDiv'; -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; type Props = { @@ -88,13 +88,13 @@ class Painter extends React.Component { } } -export default { - toSpawn(context: DebuggerContext) { +export default defineTab({ + toSpawn(context) { const drawnPainters = context.context?.moduleContexts?.painter.state.drawnPainters; console.log(drawnPainters); return drawnPainters.length > 0; }, - body: (debuggerContext: any) => , + body: (debuggerContext) => , label: 'Painter Test Tab', iconName: 'scatter-plot' -}; +}); diff --git a/src/tabs/Painter/package.json b/src/tabs/Painter/package.json index 1b95264151..0fbfa60fe3 100644 --- a/src/tabs/Painter/package.json +++ b/src/tabs/Painter/package.json @@ -9,10 +9,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Painter/tsconfig.json b/src/tabs/Painter/tsconfig.json index e69de29bb2..e4dd09d989 100644 --- a/src/tabs/Painter/tsconfig.json +++ b/src/tabs/Painter/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["./index.tsx"] +} \ No newline at end of file diff --git a/src/tabs/Physics2D/package.json b/src/tabs/Physics2D/package.json index 63005eb170..77fd8ac8dc 100644 --- a/src/tabs/Physics2D/package.json +++ b/src/tabs/Physics2D/package.json @@ -3,16 +3,18 @@ "version": "1.0.0", "private": true, "dependencies": { + "@box2d/debug-draw": "^0.10.0", "@sourceacademy/bundle-physics_2d": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Physics2D/src/index.tsx b/src/tabs/Physics2D/src/index.tsx index 4e7b0c8657..d0050b626c 100644 --- a/src/tabs/Physics2D/src/index.tsx +++ b/src/tabs/Physics2D/src/index.tsx @@ -1,4 +1,4 @@ -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import DebugDrawCanvas from './DebugDrawCanvas'; /** @@ -7,21 +7,9 @@ import DebugDrawCanvas from './DebugDrawCanvas'; * @author Yu Jiali */ -export default { - /** - * This function will be called to determine if the component will be - * rendered. - * @param {DebuggerContext} context - * @returns {boolean} - */ +export default defineTab({ toSpawn: () => true, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body(context: DebuggerContext) { + body(context) { const { context: { moduleContexts: { physics_2d: { state: { world } } } } } = context; return ( @@ -30,16 +18,6 @@ export default { ); }, - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Physics 2D', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'wind' -}; +}); diff --git a/src/tabs/Pixnflix/index.tsx b/src/tabs/Pixnflix/index.tsx index 4383b9bc04..fa2828f496 100644 --- a/src/tabs/Pixnflix/index.tsx +++ b/src/tabs/Pixnflix/index.tsx @@ -18,6 +18,7 @@ import { InputFeed, type TabsPacket } from '@sourceacademy/bundle-pix_n_flix/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React, { type ChangeEvent, type DragEvent } from 'react'; type Props = { @@ -417,11 +418,11 @@ class PixNFlix extends React.Component { } } -export default { +export default defineTab({ toSpawn: () => true, body: (debuggerContext: any) => ( ), label: 'PixNFlix Live Feed', iconName: 'mobile-video' -}; +}); diff --git a/src/tabs/Pixnflix/package.json b/src/tabs/Pixnflix/package.json index 3fcdb411d8..b84955a332 100644 --- a/src/tabs/Pixnflix/package.json +++ b/src/tabs/Pixnflix/package.json @@ -4,14 +4,16 @@ "private": true, "dependencies": { "@sourceacademy/bundle-pix_n_flix": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Plotly/index.tsx b/src/tabs/Plotly/index.tsx index bd101c1a16..5d8bee4f58 100644 --- a/src/tabs/Plotly/index.tsx +++ b/src/tabs/Plotly/index.tsx @@ -1,6 +1,6 @@ import type { DrawnPlot } from '@sourceacademy/bundle-plotly/plotly'; import Modal from '@sourceacademy/modules-lib/tabs/ModalDiv'; -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; type Props = { @@ -88,12 +88,12 @@ class Plotly extends React.Component { } } -export default { - toSpawn(context: DebuggerContext) { +export default defineTab({ + toSpawn(context) { const drawnPlots = context.context?.moduleContexts?.plotly.state.drawnPlots; return drawnPlots.length > 0; }, - body: (debuggerContext: any) => , + body: debuggerContext => , label: 'Plotly', iconName: 'scatter-plot' -}; +}); diff --git a/src/tabs/Plotly/package.json b/src/tabs/Plotly/package.json index c3c04eecd7..c76784473e 100644 --- a/src/tabs/Plotly/package.json +++ b/src/tabs/Plotly/package.json @@ -9,10 +9,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Repeat/index.tsx b/src/tabs/Repeat/index.tsx index 8323190d40..668447c478 100644 --- a/src/tabs/Repeat/index.tsx +++ b/src/tabs/Repeat/index.tsx @@ -1,3 +1,4 @@ +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; type Props = { @@ -12,9 +13,9 @@ class Repeat extends React.PureComponent { } } -export default { +export default defineTab({ toSpawn: () => true, body: (debuggerContext: any) => , label: 'Repeat Test Tab', iconName: 'build' -}; +}); diff --git a/src/tabs/Repeat/package.json b/src/tabs/Repeat/package.json index 533e76cfd9..df395c1049 100644 --- a/src/tabs/Repeat/package.json +++ b/src/tabs/Repeat/package.json @@ -3,14 +3,16 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Repl/index.tsx b/src/tabs/Repl/index.tsx index 166b6fd732..58a3f4bdde 100644 --- a/src/tabs/Repl/index.tsx +++ b/src/tabs/Repl/index.tsx @@ -9,7 +9,7 @@ import { IconNames } from '@blueprintjs/icons'; import { FONT_MESSAGE, MINIMUM_EDITOR_HEIGHT } from '@sourceacademy/bundle-repl/config'; import type { ProgrammableRepl } from '@sourceacademy/bundle-repl/programmable_repl'; -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; import AceEditor from 'react-ace'; @@ -146,35 +146,13 @@ class ProgrammableReplGUI extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn(_context: DebuggerContext) { +export default defineTab({ + toSpawn() { return true; }, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body(context: DebuggerContext) { + body(context) { return ; }, - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Programmable Repl Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'code' -}; +}); diff --git a/src/tabs/Repl/package.json b/src/tabs/Repl/package.json index 62da3e8d05..8d289bfd28 100644 --- a/src/tabs/Repl/package.json +++ b/src/tabs/Repl/package.json @@ -10,10 +10,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/RobotSimulation/package.json b/src/tabs/RobotSimulation/package.json index 0b92a517e7..623ed6dc1e 100644 --- a/src/tabs/RobotSimulation/package.json +++ b/src/tabs/RobotSimulation/package.json @@ -3,16 +3,18 @@ "version": "1.0.0", "private": true, "dependencies": { + "@dimforge/rapier3d-compat": "^0.11.2", "@sourceacademy/bundle-robot_simulation": "workspace:^", "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/RobotSimulation/src/index.tsx b/src/tabs/RobotSimulation/src/index.tsx index 2c22093cdc..87727d3e6d 100644 --- a/src/tabs/RobotSimulation/src/index.tsx +++ b/src/tabs/RobotSimulation/src/index.tsx @@ -1,4 +1,4 @@ -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import { Main } from './components/Main'; /** @@ -6,35 +6,13 @@ import { Main } from './components/Main'; * @author Joel Chan */ -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn(context: DebuggerContext) { +export default defineTab({ + toSpawn(context) { const worldState = context.context.moduleContexts.robot_simulation.state?.world?.state; return worldState !== undefined; }, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: DebuggerContext) =>
    , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ - label: 'Sample Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ + body: context =>
    , + label: 'Robot Simulation Tab', iconName: 'build', -}; +}); diff --git a/src/tabs/Rune/package.json b/src/tabs/Rune/package.json index 3fe32d30bc..e72e181b69 100644 --- a/src/tabs/Rune/package.json +++ b/src/tabs/Rune/package.json @@ -9,10 +9,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Rune/src/__tests__/Rune.tsx b/src/tabs/Rune/src/__tests__/Rune.tsx index b41df9ea90..9635f86e02 100644 --- a/src/tabs/Rune/src/__tests__/Rune.tsx +++ b/src/tabs/Rune/src/__tests__/Rune.tsx @@ -1,11 +1,10 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ +import { animate_rune } from '@sourceacademy/bundle-rune'; +import type { RuneModuleState } from '@sourceacademy/bundle-rune/functions'; +import { mockDebuggerContext } from '@sourceacademy/modules-lib/utilities'; import { RuneTab } from '..'; -import { animate_rune } from '../../../bundles/rune'; -import type { RuneModuleState } from '../../../bundles/rune/functions'; -import { mockDebuggerContext } from '../../common/testUtils'; test('Ensure that rune animations error gracefully', () => { - const badAnimation = animate_rune(1, 60, t => 1 as any); + const badAnimation = animate_rune(1, 60, _t => 1 as any); const mockContext = mockDebuggerContext({ drawnRunes: [badAnimation ]}, 'rune'); expect() .toMatchSnapshot(); diff --git a/src/tabs/Rune/src/index.tsx b/src/tabs/Rune/src/index.tsx index 06e1d003b7..2d2e9677b6 100644 --- a/src/tabs/Rune/src/index.tsx +++ b/src/tabs/Rune/src/index.tsx @@ -2,8 +2,8 @@ import { type RuneModuleState, isHollusionRune } from '@sourceacademy/bundle-run import AnimationCanvas from '@sourceacademy/modules-lib/tabs/AnimationCanvas'; import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; -import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; -import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; +import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { glAnimation, type ModuleTab } from '@sourceacademy/modules-lib/types'; import HollusionCanvas from './hollusion_canvas'; export const RuneTab: ModuleTab = ({ context }) => { @@ -36,37 +36,17 @@ export const RuneTab: ModuleTab = ({ context }) => { return ; }; -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when there is at least one rune to be - * displayed - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn(context: DebuggerContext) { +export default defineTab({ + toSpawn(context) { const drawnRunes = context.context?.moduleContexts?.rune?.state?.drawnRunes; return drawnRunes.length > 0; }, - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body(context: DebuggerContext) { + body(context) { return ; }, - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Runes Tab', - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'group-objects' -}; +}); diff --git a/src/tabs/Sound/index.tsx b/src/tabs/Sound/index.tsx index e4069ab226..7b28a35483 100644 --- a/src/tabs/Sound/index.tsx +++ b/src/tabs/Sound/index.tsx @@ -1,6 +1,6 @@ import type { SoundModuleState } from '@sourceacademy/bundle-sound/types'; import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; -import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; /** @@ -34,34 +34,14 @@ const SoundTab: ModuleTab = ({ context }) => { ); }; -export default { - /** - * This function will be called to determine if the component will be - * rendered. - * @returns {boolean} - */ +export default defineTab({ toSpawn(context: DebuggerContext) { const audioPlayed = context.context?.moduleContexts?.sound?.state?.audioPlayed; return audioPlayed.length > 0; }, - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ body(context: DebuggerContext) { return ; }, - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Sounds', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'music' -}; +}); diff --git a/src/tabs/Sound/package.json b/src/tabs/Sound/package.json index 85bec675f1..94a837245c 100644 --- a/src/tabs/Sound/package.json +++ b/src/tabs/Sound/package.json @@ -9,10 +9,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/SoundMatrix/index.tsx b/src/tabs/SoundMatrix/index.tsx index 6765de7ee9..2200d31942 100644 --- a/src/tabs/SoundMatrix/index.tsx +++ b/src/tabs/SoundMatrix/index.tsx @@ -1,4 +1,5 @@ import { Button, Classes } from '@blueprintjs/core'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import classNames from 'classnames'; import React from 'react'; @@ -84,31 +85,9 @@ class SoundMatrix extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn: (context: any) => context.result.value === 'test', - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: any) => , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ +export default defineTab({ + toSpawn: (context) => context.result.value === 'test', + body: (context) => , label: 'Sound Matrix', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'music' -}; +}); diff --git a/src/tabs/SoundMatrix/package.json b/src/tabs/SoundMatrix/package.json index e10ab75d02..8d51693f06 100644 --- a/src/tabs/SoundMatrix/package.json +++ b/src/tabs/SoundMatrix/package.json @@ -3,14 +3,17 @@ "version": "1.0.0", "private": true, "dependencies": { + "@sourceacademy/modules-lib": "workspace:^", + "classnames": "^2.3.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/StereoSound/index.tsx b/src/tabs/StereoSound/index.tsx index 85eb6687d7..172594efdd 100644 --- a/src/tabs/StereoSound/index.tsx +++ b/src/tabs/StereoSound/index.tsx @@ -1,7 +1,7 @@ import type { StereoSoundModuleState } from '@sourceacademy/bundle-stereo_sound/types'; import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; -import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; -import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; +import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import type { ModuleTab } from '@sourceacademy/modules-lib/types'; /** * Tab for Source Academy Sounds Module @@ -35,34 +35,14 @@ const SoundTab: ModuleTab = ({ context }) => { ); }; -export default { - /** - * This function will be called to determine if the component will be - * rendered. - * @returns {boolean} - */ - toSpawn(context: any) { +export default defineTab({ + toSpawn(context) { const audioPlayed = context.context?.moduleContexts?.stereo_sound?.state?.audioPlayed; return audioPlayed.length > 0; }, - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body(context: DebuggerContext) { + body(context) { return ; }, - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Stereo Sounds', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'music' -}; +}); diff --git a/src/tabs/StereoSound/package.json b/src/tabs/StereoSound/package.json index 4be8717ff7..31c7093a09 100644 --- a/src/tabs/StereoSound/package.json +++ b/src/tabs/StereoSound/package.json @@ -9,10 +9,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/Unittest/index.tsx b/src/tabs/Unittest/index.tsx index c83e886a9d..aacbc22b05 100644 --- a/src/tabs/Unittest/index.tsx +++ b/src/tabs/Unittest/index.tsx @@ -1,6 +1,5 @@ import type { SuiteResult, TestContext } from '@sourceacademy/bundle-unittest/types'; -import { getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { getModuleState, defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; /** @@ -96,34 +95,12 @@ class TestSuitesTab extends React.PureComponent { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn: (_context: DebuggerContext): boolean => true, - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: DebuggerContext) => { +export default defineTab({ + toSpawn: () => true, + body: context => { const moduleContext = getModuleState(context, 'unittest'); return ; }, - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Test suites', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ iconName: 'lab-test', -}; +}); diff --git a/src/tabs/Unittest/package.json b/src/tabs/Unittest/package.json index b36c25f360..442e9b537e 100644 --- a/src/tabs/Unittest/package.json +++ b/src/tabs/Unittest/package.json @@ -9,10 +9,11 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/UnityAcademy/index.tsx b/src/tabs/UnityAcademy/index.tsx index 355893e9c3..77d1871cd2 100644 --- a/src/tabs/UnityAcademy/index.tsx +++ b/src/tabs/UnityAcademy/index.tsx @@ -8,7 +8,7 @@ import { Button, NumericInput, Checkbox } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { getInstance } from '@sourceacademy/bundle-unity_academy/UnityAcademy'; import { UNITY_ACADEMY_BACKEND_URL } from '@sourceacademy/bundle-unity_academy/config'; -import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; +import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; import React from 'react'; type Props = {}; @@ -160,35 +160,16 @@ class Unity3DTab extends React.Component { } } -export default { - /** - * This function will be called to determine if the component will be - * rendered. - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn(_context: DebuggerContext) { +export default defineTab({ + toSpawn() { return getInstance() !== undefined; }, - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body(_context: DebuggerContext) { + body() { return ; }, - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ label: 'Unity Academy', - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ - iconName: 'cube' -}; + iconName: IconNames.CUBE +}); diff --git a/src/tabs/UnityAcademy/package.json b/src/tabs/UnityAcademy/package.json index c382a509ad..7b077816f6 100644 --- a/src/tabs/UnityAcademy/package.json +++ b/src/tabs/UnityAcademy/package.json @@ -6,14 +6,16 @@ "@blueprintjs/core": "^5.10.2", "@blueprintjs/icons": "^5.9.0", "@sourceacademy/bundle-unity_academy": "workspace:^", + "@sourceacademy/modules-lib": "workspace:^", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@sourceacademy/module-buildtools": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@types/react": "^18.3.1" }, "scripts": { - "build": "buildtools build tab ." + "build": "buildtools build tab .", + "lint": "buildtools lint ." } } diff --git a/src/tabs/package.json b/src/tabs/package.json index a127c8e311..cf9525c310 100644 --- a/src/tabs/package.json +++ b/src/tabs/package.json @@ -1,5 +1,5 @@ { - "name": "tabs", + "name": "@sourceacademy/tabs", "private": true, "workspaces": [ "./*" diff --git a/src/tabs/tsconfig.json b/src/tabs/tsconfig.json index eb85faf3bf..bac6e6e48d 100644 --- a/src/tabs/tsconfig.json +++ b/src/tabs/tsconfig.json @@ -4,7 +4,5 @@ "compilerOptions": { "jsx": "react-jsx", "noEmit": true - }, - "exclude": [], - "include": ["**/__tests__"] + } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 7cc7589c87..962f219a91 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,3 +1,5 @@ +// Deployment tsconfig + { "compilerOptions": { /* Allow JavaScript files to be imported inside your project, instead of just .ts and .tsx files. */ @@ -6,35 +8,29 @@ "allowSyntheticDefaultImports": true, /* See https://www.typescriptlang.org/tsconfig#esModuleInterop */ "esModuleInterop": true, + + "forceConsistentCasingInFileNames": true, /* See https://www.typescriptlang.org/tsconfig#lib */ "lib": ["es6", "dom", "es2016", "ESNext", "scripthost"], /* Sets the module system for the program. See the Modules reference page for more information. */ "module": "esnext", /* Specify the module resolution strategy: 'node' (Node.js) or 'classic' (used in TypeScript before the release of 1.6). */ - "moduleResolution": "node", + "moduleResolution": "bundler", + /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ "resolveJsonModule": true, /* Enables the generation of sourcemap files. These files allow debuggers and other tools to display the original TypeScript source code when actually working with the emitted JavaScript files. */ "sourceMap": false, - /* Skip running typescript on declaration files. This option is needed due to a known bug in react-ace */ - "skipLibCheck": true, /* The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. */ "strict": true, - "forceConsistentCasingInFileNames": true, /* The target setting changes which JS features are downleveled and which are left intact. */ "target": "es6", /* In some cases where no type annotations are present, TypeScript will fall back to a type of any for a variable when it cannot infer the type. */ /* *** TEMPORARILY ADDED UNTIL ALL MODULES HAVE BEEN REFACTORED!!!!!!!!!!! *** */ "noImplicitAny": false, - "verbatimModuleSyntax": true, - "paths": { - "js-slang/context": ["./commons/js-slang/context.d.ts"] - }, "experimentalDecorators": true, "emitDecoratorMetadata": true }, - /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ - "include": ["."], /* Specifies an array of filenames or patterns that should be skipped when resolving include. */ - "exclude": ["jest.config.js", "**/__tests__"] + "exclude": ["**/dist"] } From e2b1043e515e95bd53187e5756272b051d7cb194 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 22 May 2025 12:28:43 +0800 Subject: [PATCH 020/112] Add package json to devserver --- devserver/package.json | 28 +++++++++++++++++++ devserver/src/components/Playground.tsx | 2 -- devserver/src/components/sideContent/utils.ts | 26 +++++++++++------ devserver/src/main.tsx | 2 ++ devserver/tsconfig.node.json | 2 +- devserver/vite.config.ts | 28 +++++++++++++++++++ 6 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 devserver/package.json create mode 100644 devserver/vite.config.ts diff --git a/devserver/package.json b/devserver/package.json new file mode 100644 index 0000000000..eedf39498d --- /dev/null +++ b/devserver/package.json @@ -0,0 +1,28 @@ +{ + "name": "@sourceacademy/modules-devserver", + "description": "Lightweight React server powered by Vite for rapid development and testing of Source Academy Bundles and Tabs", + "readme": "./README.md", + "version": "1.0.0", + "private": true, + "type": "module", + "dependencies": { + "@blueprintjs/core": "^5.10.2", + "@blueprintjs/icons": "^5.9.0", + "ace-builds": "^1.25.1", + "classnames": "^2.3.1", + "re-resizable": "^6.9.11", + "react": "^18.3.1", + "react-ace": "^10.1.0", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "sass": "^1.85.0", + "vite": "^6.3.4" + }, + "scripts": { + "dev": "vite" + } +} diff --git a/devserver/src/components/Playground.tsx b/devserver/src/components/Playground.tsx index 386544bec5..7cb4440155 100644 --- a/devserver/src/components/Playground.tsx +++ b/devserver/src/components/Playground.tsx @@ -1,10 +1,8 @@ import { Classes, Intent, OverlayToaster, type ToastProps } from '@blueprintjs/core'; import classNames from 'classnames'; import { SourceDocumentation, getNames, runInContext, type Context } from 'js-slang'; - // Importing this straight from js-slang doesn't work for whatever reason import createContext from 'js-slang/dist/createContext'; - import { Chapter, Variant } from 'js-slang/dist/types'; import { stringify } from 'js-slang/dist/utils/stringify'; import React, { useCallback } from 'react'; diff --git a/devserver/src/components/sideContent/utils.ts b/devserver/src/components/sideContent/utils.ts index c6e5529fe6..89b4f3842d 100644 --- a/devserver/src/components/sideContent/utils.ts +++ b/devserver/src/components/sideContent/utils.ts @@ -1,17 +1,27 @@ import type { Context } from 'js-slang'; -import manifest from '../../../../modules.json'; import type { ModuleSideContent, SideContentTab } from './types'; -const moduleManifest = manifest as Record; - export const getDynamicTabs = async (context: Context) => { const moduleSideContents = await Promise.all(Object.keys(context.moduleContexts) - .flatMap((moduleName) => moduleManifest[moduleName].tabs.map(async (tabName) => { - const { default: rawTab } = await import(`../../../../src/tabs/${tabName}/index.tsx`); - return rawTab as ModuleSideContent; - }))); + .map(async (moduleName) => { + const manifest = await import(`../../../../src/bundles/${moduleName}/manifest.json`, { with: { type: 'json' }}); + if (manifest.tabs) { + const tabsToSpawn = manifest.tabs as string[]; + return Promise.all(tabsToSpawn.map(async (tabName): Promise => { + try { + const { default: rawTab } = await import(`../../../../src/tabs/${tabName}/src/index.tsx`); + return rawTab; + } catch { + const { default: rawTab } = await import(`../../../../src/tabs/${tabName}/index.tsx`); + return rawTab; + } + })); + } else { + return []; + } + })); - return moduleSideContents.filter(({ toSpawn }) => !toSpawn || toSpawn({ context })) + return moduleSideContents.flat().filter(({ toSpawn }) => !toSpawn || toSpawn({ context })) .map((tab): SideContentTab => ({ ...tab, // In the frontend, module tabs use their labels as IDs diff --git a/devserver/src/main.tsx b/devserver/src/main.tsx index 16fd848d99..2c9b2db288 100644 --- a/devserver/src/main.tsx +++ b/devserver/src/main.tsx @@ -1,8 +1,10 @@ +import { setModulesStaticURL } from 'js-slang/dist/modules/loader'; import React from 'react'; import ReactDOM from 'react-dom/client'; import Playground from './components/Playground'; import './styles/index.scss'; +setModulesStaticURL('../../build'); const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( diff --git a/devserver/tsconfig.node.json b/devserver/tsconfig.node.json index 7d55d4e27b..6b732160dd 100644 --- a/devserver/tsconfig.node.json +++ b/devserver/tsconfig.node.json @@ -6,5 +6,5 @@ "moduleResolution": "bundler", "allowSyntheticDefaultImports": true }, - "include": ["../vite.config.ts"] + "include": ["./vite.config.ts"] } diff --git a/devserver/vite.config.ts b/devserver/vite.config.ts new file mode 100644 index 0000000000..33bbbdb886 --- /dev/null +++ b/devserver/vite.config.ts @@ -0,0 +1,28 @@ +import pathlib from 'path'; +import react from '@vitejs/plugin-react'; +import { defineConfig, loadEnv } from 'vite'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()); + return { + plugins: [react()], + resolve: { + preserveSymlinks: true, + alias:[{ + find: /^js-slang\/context/, + replacement: pathlib.resolve('./src/mockModuleContext') + }] + }, + define: { + 'process.env': env + }, + optimizeDeps: { + esbuildOptions: { + // Node.js global to browser globalThis + define: { + global: 'globalThis' + }, + } + }, + }; +}); From f66eeaf6004091fc49f714a1b2e2b59e8daf45e5 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 22 May 2025 15:58:47 +0800 Subject: [PATCH 021/112] Migrate to vitest --- buildtools/manifest.schema.json | 20 - buildtools/package.json | 19 - buildtools/src/build/modules/bundle.ts | 21 - buildtools/src/build/modules/commons.ts | 65 - buildtools/src/build/modules/tab.ts | 29 - buildtools/src/commands/specialErrors.ts | 8 - buildtools/src/commands/utilities.ts | 16 - buildtools/src/type_map/index.ts | 69 - buildtools/src/types/js-slang/context.d.ts | 4 - buildtools/src/types/types.ts | 46 - buildtools/tsconfig.json | 16 - lib/.gitignore | 3 +- lib/buildtools/README.md | 10 - lib/buildtools/build.js | 3 - lib/buildtools/jest.config.js | 26 - lib/buildtools/jest.setup.ts | 20 - lib/buildtools/package.json | 12 +- lib/buildtools/src/__tests__/utils.test.ts | 1 + .../bundles/test0/manifest.json | 1 + .../__test_mocks__/bundles/tsconfig.json | 2 +- .../src/build/docs/__tests__/building.test.ts | 26 +- lib/buildtools/src/build/docs/docsUtils.ts | 2 +- .../build/modules/__tests__/bundle.test.ts | 7 +- .../{prebuild.test.ts => lint.test.ts} | 65 +- .../src/commands/__tests__/main.test.ts | 3 +- .../src/commands/__tests__/template.test.ts | 52 +- .../src/commands/__tests__/testing.test.ts | 32 - lib/buildtools/src/commands/lint.ts | 31 + lib/buildtools/src/commands/main.ts | 7 +- lib/buildtools/src/commands/prebuild.ts | 57 - lib/buildtools/src/commands/testing.ts | 16 - lib/buildtools/src/prebuild/typecheck.ts | 86 - .../src/templates/__tests__/names.test.ts | 1 + lib/buildtools/src/templates/bundle.ts | 2 +- lib/buildtools/src/templates/tab.ts | 2 +- lib/buildtools/src/testing/runner.ts | 10 - lib/buildtools/tsconfig.json | 2 +- lib/buildtools/vitest.config.ts | 11 + lib/buildtools/vitest.setup.ts | 39 + lib/lintplugin/package.json | 17 +- lib/lintplugin/vitest.config.ts | 13 + lib/tsconfig.json | 2 + package.json | 108 +- scripts/jest.config.js | 20 - scripts/jest.setup.ts | 30 - scripts/scripts_manager.js | 86 - scripts/src/build/__tests__/buildAll.test.ts | 83 - .../src/build/__tests__/buildUtils.test.ts | 155 - scripts/src/build/__tests__/testingUtils.ts | 101 - scripts/src/build/docs/__mocks__/docsUtils.ts | 16 - .../src/build/docs/__tests__/building.test.ts | 52 - scripts/src/build/docs/__tests__/docs.test.ts | 49 - scripts/src/build/docs/__tests__/json.test.ts | 42 - .../test_mocks/bundles/test0/index.ts | 8 - .../test_mocks/bundles/test1/index.ts | 4 - .../docs/__tests__/test_mocks/tsconfig.json | 45 - scripts/src/build/docs/docsUtils.ts | 34 - scripts/src/build/docs/docsreadme.md | 18 - scripts/src/build/docs/drawdown.ts | 190 - scripts/src/build/docs/html.ts | 63 - scripts/src/build/docs/index.ts | 39 - scripts/src/build/docs/json.ts | 120 - scripts/src/build/index.ts | 38 - .../build/modules/__tests__/bundle.test.ts | 32 - .../build/modules/__tests__/output.test.ts | 52 - .../build/modules/__tests__/streamMocker.ts | 28 - .../src/build/modules/__tests__/tab.test.ts | 30 - scripts/src/build/modules/bundles.ts | 31 - scripts/src/build/modules/commons.ts | 132 - scripts/src/build/modules/index.ts | 26 - scripts/src/build/modules/tabs.ts | 48 - scripts/src/build/prebuild/__mocks__/lint.ts | 10 - scripts/src/build/prebuild/__mocks__/tsc.ts | 10 - .../src/build/prebuild/__tests__/lint.test.ts | 59 - .../build/prebuild/__tests__/prebuild.test.ts | 144 - scripts/src/build/prebuild/index.ts | 68 - scripts/src/build/prebuild/lint.ts | 74 - scripts/src/build/prebuild/tsc.ts | 125 - scripts/src/build/prebuild/utils.ts | 38 - scripts/src/build/utils.ts | 254 - scripts/src/build/watch.ts | 130 - scripts/src/commandUtils.ts | 192 - scripts/src/index.ts | 14 - .../src/linting/__tests__/typeimports.test.ts | 45 - scripts/src/linting/typeimports.ts | 57 - scripts/src/manifest.ts | 13 - scripts/src/templates/__tests__/names.test.ts | 22 - .../src/templates/__tests__/template.test.ts | 157 - scripts/src/templates/index.ts | 38 - scripts/src/templates/module.ts | 44 - scripts/src/templates/print.ts | 27 - scripts/src/templates/tab.ts | 68 - scripts/src/templates/templates/__bundle__.ts | 25 - scripts/src/templates/templates/__tab__.tsx | 71 - scripts/src/templates/utilities.ts | 17 - scripts/src/testing/__tests__/runner.test.ts | 28 - scripts/src/testing/index.ts | 32 - scripts/src/testing/runner.ts | 6 - scripts/tsconfig.json | 20 - .../communication/src/__tests__/index.ts | 1 + src/bundles/curve/src/__tests__/curve.ts | 7 +- src/bundles/package.json | 7 +- src/bundles/repeat/src/__tests__/index.ts | 2 + .../__tests__/ev3/components/Chassis.ts | 40 +- .../__tests__/ev3/components/Mesh.ts | 40 +- .../__tests__/ev3/components/Motor.ts | 50 +- .../__tests__/ev3/components/Wheel.ts | 32 +- .../__tests__/ev3/ev3/default/ev3.ts | 38 +- .../ev3/feedback_control/PidController.ts | 8 +- .../__tests__/ev3/sensor/ColorSensor.ts | 46 +- .../__tests__/ev3/sensor/UltrasonicSensor.ts | 29 +- .../controllers/__tests__/program/Program.ts | 17 +- .../__tests__/utils/mergeConfig.ts | 1 + .../engine/__tests__/Core/CallbackHandler.ts | 17 +- .../src/engine/__tests__/Core/Controller.ts | 27 +- .../src/engine/__tests__/Core/Events.ts | 17 +- .../src/engine/__tests__/Core/RobotConsole.ts | 1 + .../src/engine/__tests__/Core/Timer.ts | 1 + .../src/engine/__tests__/Entity/Entity.ts | 19 +- .../src/engine/__tests__/Math/Convert.ts | 1 + .../src/engine/__tests__/Physics.ts | 29 +- .../engine/__tests__/Render/MeshFactory.ts | 13 +- .../engine/__tests__/Render/helpers/Camera.ts | 1 + .../src/__tests__/__snapshots__/index.ts.snap | 10 +- src/bundles/scrabble/src/__tests__/index.ts | 1 + src/bundles/sound/src/__tests__/sound.test.ts | 7 +- src/bundles/unittest/src/__tests__/index.ts | 3 +- src/bundles/vitest.config.ts | 15 + src/modules-lib/package.json | 17 +- .../src/__tests__/hextocolor.test.ts | 0 src/modules-lib/src/__tests__/hextocolor.ts | 17 - src/modules-lib/vitest.config.ts | 9 + src/tabs/Curve/src/__tests__/Curve.tsx | 1 + .../__tests__/__snapshots__/Curve.tsx.snap | 62 +- src/tabs/Rune/src/__tests__/Rune.tsx | 1 + .../src/__tests__/__snapshots__/Rune.tsx.snap | 26 +- src/tabs/package.json | 5 +- src/tabs/vitest.config.ts | 17 + vite.config.ts | 29 - vitest.config.ts | 14 + yarn.lock | 6905 ++++++++++------- 141 files changed, 4673 insertions(+), 7252 deletions(-) delete mode 100644 buildtools/manifest.schema.json delete mode 100644 buildtools/package.json delete mode 100644 buildtools/src/build/modules/bundle.ts delete mode 100644 buildtools/src/build/modules/commons.ts delete mode 100644 buildtools/src/build/modules/tab.ts delete mode 100644 buildtools/src/commands/specialErrors.ts delete mode 100644 buildtools/src/commands/utilities.ts delete mode 100644 buildtools/src/type_map/index.ts delete mode 100644 buildtools/src/types/js-slang/context.d.ts delete mode 100644 buildtools/src/types/types.ts delete mode 100644 buildtools/tsconfig.json delete mode 100644 lib/buildtools/README.md delete mode 100644 lib/buildtools/jest.config.js delete mode 100644 lib/buildtools/jest.setup.ts create mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/test0/manifest.json rename lib/buildtools/src/commands/__tests__/{prebuild.test.ts => lint.test.ts} (68%) delete mode 100644 lib/buildtools/src/commands/__tests__/testing.test.ts create mode 100644 lib/buildtools/src/commands/lint.ts delete mode 100644 lib/buildtools/src/commands/prebuild.ts delete mode 100644 lib/buildtools/src/commands/testing.ts delete mode 100644 lib/buildtools/src/prebuild/typecheck.ts delete mode 100644 lib/buildtools/src/testing/runner.ts create mode 100644 lib/buildtools/vitest.config.ts create mode 100644 lib/buildtools/vitest.setup.ts create mode 100644 lib/lintplugin/vitest.config.ts delete mode 100644 scripts/jest.config.js delete mode 100644 scripts/jest.setup.ts delete mode 100644 scripts/scripts_manager.js delete mode 100644 scripts/src/build/__tests__/buildAll.test.ts delete mode 100644 scripts/src/build/__tests__/buildUtils.test.ts delete mode 100644 scripts/src/build/__tests__/testingUtils.ts delete mode 100644 scripts/src/build/docs/__mocks__/docsUtils.ts delete mode 100644 scripts/src/build/docs/__tests__/building.test.ts delete mode 100644 scripts/src/build/docs/__tests__/docs.test.ts delete mode 100644 scripts/src/build/docs/__tests__/json.test.ts delete mode 100644 scripts/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts delete mode 100644 scripts/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts delete mode 100644 scripts/src/build/docs/__tests__/test_mocks/tsconfig.json delete mode 100644 scripts/src/build/docs/docsUtils.ts delete mode 100644 scripts/src/build/docs/docsreadme.md delete mode 100644 scripts/src/build/docs/drawdown.ts delete mode 100644 scripts/src/build/docs/html.ts delete mode 100644 scripts/src/build/docs/index.ts delete mode 100644 scripts/src/build/docs/json.ts delete mode 100644 scripts/src/build/index.ts delete mode 100644 scripts/src/build/modules/__tests__/bundle.test.ts delete mode 100644 scripts/src/build/modules/__tests__/output.test.ts delete mode 100644 scripts/src/build/modules/__tests__/streamMocker.ts delete mode 100644 scripts/src/build/modules/__tests__/tab.test.ts delete mode 100644 scripts/src/build/modules/bundles.ts delete mode 100644 scripts/src/build/modules/commons.ts delete mode 100644 scripts/src/build/modules/index.ts delete mode 100644 scripts/src/build/modules/tabs.ts delete mode 100644 scripts/src/build/prebuild/__mocks__/lint.ts delete mode 100644 scripts/src/build/prebuild/__mocks__/tsc.ts delete mode 100644 scripts/src/build/prebuild/__tests__/lint.test.ts delete mode 100644 scripts/src/build/prebuild/__tests__/prebuild.test.ts delete mode 100644 scripts/src/build/prebuild/index.ts delete mode 100644 scripts/src/build/prebuild/lint.ts delete mode 100644 scripts/src/build/prebuild/tsc.ts delete mode 100644 scripts/src/build/prebuild/utils.ts delete mode 100644 scripts/src/build/utils.ts delete mode 100644 scripts/src/build/watch.ts delete mode 100644 scripts/src/commandUtils.ts delete mode 100644 scripts/src/index.ts delete mode 100644 scripts/src/linting/__tests__/typeimports.test.ts delete mode 100644 scripts/src/linting/typeimports.ts delete mode 100644 scripts/src/manifest.ts delete mode 100644 scripts/src/templates/__tests__/names.test.ts delete mode 100644 scripts/src/templates/__tests__/template.test.ts delete mode 100644 scripts/src/templates/index.ts delete mode 100644 scripts/src/templates/module.ts delete mode 100644 scripts/src/templates/print.ts delete mode 100644 scripts/src/templates/tab.ts delete mode 100644 scripts/src/templates/templates/__bundle__.ts delete mode 100644 scripts/src/templates/templates/__tab__.tsx delete mode 100644 scripts/src/templates/utilities.ts delete mode 100644 scripts/src/testing/__tests__/runner.test.ts delete mode 100644 scripts/src/testing/index.ts delete mode 100644 scripts/src/testing/runner.ts delete mode 100644 scripts/tsconfig.json create mode 100644 src/bundles/vitest.config.ts rename buildtools/src/commands/__tests__/hextocolor.ts => src/modules-lib/src/__tests__/hextocolor.test.ts (100%) delete mode 100644 src/modules-lib/src/__tests__/hextocolor.ts create mode 100644 src/modules-lib/vitest.config.ts create mode 100644 src/tabs/vitest.config.ts delete mode 100644 vite.config.ts create mode 100644 vitest.config.ts diff --git a/buildtools/manifest.schema.json b/buildtools/manifest.schema.json deleted file mode 100644 index 76c02488eb..0000000000 --- a/buildtools/manifest.schema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of your bundle", - "pattern": "\b[a-z0-9]+(?:_[a-z0-9]+)*\b" - }, - "tabs": { - "description": "Tabs that will be loaded with this bundle", - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["name"] - } -} \ No newline at end of file diff --git a/buildtools/package.json b/buildtools/package.json deleted file mode 100644 index 3a4c05f8d2..0000000000 --- a/buildtools/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@sourceacademy/module-buildtools", - "version": "1.0.0", - "devDependencies": { - "@commander-js/extra-typings": "^13.0.0", - "@types/node": "^20.12.12" - }, - "bin": "./dist/commands/index.js", - "type": "module", - "dependencies": { - "acorn": "^8.8.1", - "astring": "^1.8.6", - "chalk": "^5.0.1", - "commander": "^13.0.0", - "console-table-printer": "^2.11.1", - "esbuild": "^0.25.0", - "typedoc": "^0.25.12" - } -} diff --git a/buildtools/src/build/modules/bundle.ts b/buildtools/src/build/modules/bundle.ts deleted file mode 100644 index 98bb41c72b..0000000000 --- a/buildtools/src/build/modules/bundle.ts +++ /dev/null @@ -1,21 +0,0 @@ -import fs from 'fs/promises' -import { build as esbuild } from 'esbuild' -import { commonEsbuildOptions, outputBundleOrTab } from './commons' - -export async function buildBundle(bundleDir: string) { - let actualManifest; - try { - const rawManifest = await fs.readFile(`${bundleDir}/manifest.json`, 'utf-8') - actualManifest = JSON.parse(rawManifest) - } catch (error) { - throw new Error(`${bundleDir} is not a valid bundle!`) - } - - const { outputFiles: [result]} = await esbuild({ - ...commonEsbuildOptions, - entryPoints: [`${bundleDir}/src/index.ts`], - tsconfig: `${bundleDir}/tsconfig.json` - }) - - await outputBundleOrTab(result, 'build') -} \ No newline at end of file diff --git a/buildtools/src/build/modules/commons.ts b/buildtools/src/build/modules/commons.ts deleted file mode 100644 index 0154b8e0ca..0000000000 --- a/buildtools/src/build/modules/commons.ts +++ /dev/null @@ -1,65 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path'; -import { parse } from 'acorn'; -import { generate } from 'astring'; -import type { BuildOptions as ESBuildOptions, OutputFile } from 'esbuild'; -import type es from 'estree'; - -export const commonEsbuildOptions: ESBuildOptions = { - bundle: true, - format: 'iife', - define: { - process: JSON.stringify({ - env: { - NODE_ENV: 'production' - } - }) - }, - external: ['js-slang*'], - globalName: 'module', - platform: 'browser', - target: 'es6', - write: false -}; - -export async function outputBundleOrTab({ path, text }: OutputFile, outDir: string) { - const [type, name] = path.split(pathlib.sep) - .slice(-3, -1); - - const parsed = parse(text, { ecmaVersion: 6 }) as es.Program; - - // Account for 'use strict'; directives - let declStatement: es.VariableDeclaration; - if (parsed.body[0].type === 'VariableDeclaration') { - declStatement = parsed.body[0]; - } else { - declStatement = parsed.body[1] as es.VariableDeclaration; - } - - const { init: callExpression } = declStatement.declarations[0]; - if (callExpression.type !== 'CallExpression') { - throw new Error(`Expected a CallExpression, got ${callExpression.type}`) - } - - const moduleCode = callExpression.callee - - if (moduleCode.type !== 'FunctionExpression' && moduleCode.type !== 'ArrowFunctionExpression') { - throw new Error(`Expected a function, got ${moduleCode.type}`) - } - - const output: es.ExportDefaultDeclaration = { - type: 'ExportDefaultDeclaration', - declaration: { - ...moduleCode, - params: [{ - type: 'Identifier', - name: 'require' - }] - } - }; - - const file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); - const writeStream = file.createWriteStream(); - generate(output, { output: writeStream }); -} - diff --git a/buildtools/src/build/modules/tab.ts b/buildtools/src/build/modules/tab.ts deleted file mode 100644 index 4814496c0d..0000000000 --- a/buildtools/src/build/modules/tab.ts +++ /dev/null @@ -1,29 +0,0 @@ -import fs from 'fs/promises' -import { build as esbuild } from 'esbuild' -import { commonEsbuildOptions, outputBundleOrTab } from './commons' - -export async function buildTab(tabDir: string) { - let actualManifest; - try { - const rawManifest = await fs.readFile(`${tabDir}/manifest.json`, 'utf-8') - actualManifest = JSON.parse(rawManifest) - } catch (error) { - throw new Error(`${tabDir} is not a valid tab!`) - } - - const { outputFiles: [result]} = await esbuild({ - ...commonEsbuildOptions, - entryPoints: [`${tabDir}/src/index.ts`], - external: [ - ...commonEsbuildOptions.external, - 'react', - 'react-ace', - 'react-dom', - 'react/jsx-runtime', - '@blueprintjs/*' - ], - tsconfig: `${tabDir}/tsconfig.json` - }) - - await outputBundleOrTab(result, 'build') -} \ No newline at end of file diff --git a/buildtools/src/commands/specialErrors.ts b/buildtools/src/commands/specialErrors.ts deleted file mode 100644 index 54aa1fbbfa..0000000000 --- a/buildtools/src/commands/specialErrors.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This function is used to interrupt the frontend execution. - * When called, the frontend will notify that the program has ended successfully - * and display a message that the program is stopped by a module. - */ -export function interrupt() { - throw 'source_academy_interrupt'; -} diff --git a/buildtools/src/commands/utilities.ts b/buildtools/src/commands/utilities.ts deleted file mode 100644 index f25780ffdd..0000000000 --- a/buildtools/src/commands/utilities.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* [Exports] */ -export function degreesToRadians(degrees: number): number { - return (degrees / 360) * (2 * Math.PI); -} - -export function hexToColor(hex: string): [number, number, number] { - const regex = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/igu; - const groups = regex.exec(hex); - - if (groups == undefined) return [0, 0, 0]; - return [ - parseInt(groups[1], 16) / 0xff, - parseInt(groups[2], 16) / 0xff, - parseInt(groups[3], 16) / 0xff - ]; -} diff --git a/buildtools/src/type_map/index.ts b/buildtools/src/type_map/index.ts deleted file mode 100644 index 014138baa9..0000000000 --- a/buildtools/src/type_map/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -export const type_map : Record = {}; - -const registerType = (name: string, declaration: string) => { - if (name == 'prelude') { - type_map['prelude'] = type_map['prelude'] != undefined ? type_map['prelude'] + '\n' + declaration : declaration; - } else { - type_map[name] = declaration; - } -}; - -export const classDeclaration = (name: string) => { - return (_target: any) => { - registerType('prelude', `class ${name} {}`); - }; -}; - -export const typeDeclaration = (type: string, declaration = null) => { - return (_target: any) => { - const typeAlias = `type ${_target.name} = ${type}`; - let variableDeclaration = `const ${_target.name} = ${declaration === null ? type : declaration}`; - - switch (type) { - case 'number': - variableDeclaration = `const ${_target.name} = 0`; - break; - case 'string': - variableDeclaration = `const ${_target.name} = ''`; - break; - case 'boolean': - variableDeclaration = `const ${_target.name} = false`; - break; - case 'void': - variableDeclaration = ''; - break; - } - - registerType('prelude', `${typeAlias};\n${variableDeclaration};`); - }; -}; - -export const functionDeclaration = (paramTypes: string, returnType: string) => { - return (_target: any, propertyKey: string, _descriptor: PropertyDescriptor) => { - let returnValue = ''; - switch (returnType) { - case 'number': - returnValue = 'return 0'; - break; - case 'string': - returnValue = "return ''"; - break; - case 'boolean': - returnValue = 'return false'; - break; - case 'void': - returnValue = ''; - break; - default: - returnValue = `return ${returnType}`; - break; - } - registerType(propertyKey, `function ${propertyKey} (${paramTypes}) : ${returnType} { ${returnValue} }`); - }; -}; - -export const variableDeclaration = (type: string) => { - return (_target: any, propertyKey: string) => { - registerType(propertyKey, `const ${propertyKey}: ${type} = ${type}`); - }; -}; diff --git a/buildtools/src/types/js-slang/context.d.ts b/buildtools/src/types/js-slang/context.d.ts deleted file mode 100644 index 841849bbc1..0000000000 --- a/buildtools/src/types/js-slang/context.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { Context } from 'js-slang'; - -const ctx: Context; -export default ctx; diff --git a/buildtools/src/types/types.ts b/buildtools/src/types/types.ts deleted file mode 100644 index d032ecfcf9..0000000000 --- a/buildtools/src/types/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Context } from 'js-slang'; -import type { FC } from 'react'; - -/** - * Represents an animation drawn using WebGL - * @field duration Duration of the animation in secondss - * @field fps Framerate in frames per second - */ -export abstract class glAnimation { - constructor(public readonly duration: number, public readonly fps: number) { } - - public abstract getFrame(timestamp: number): AnimFrame; - - public static isAnimation = (obj: any): obj is glAnimation => obj.fps !== undefined; -} -export interface AnimFrame { - draw: (canvas: HTMLCanvasElement) => void; -} -export type DeepPartial = T extends object ? { - [P in keyof T]?: DeepPartial; -} : T; - -/** - * DebuggerContext type used by frontend to assist typing information - */ -export type DebuggerContext = { - result: any; - lastDebuggerResult: any; - code: string; - context: Context; - workspaceLocation?: any; -}; - -export type ModuleContexts = Context['moduleContexts']; - -/** - * Interface to represent objects that require a string representation in the - * REPL - */ -export interface ReplResult { - toReplString: () => string; -} - -export type ModuleTab = FC<{ context: DebuggerContext }>; - -export const getModuleState = ({ context: { moduleContexts } }: DebuggerContext, moduleName: string) => moduleContexts[moduleName].state as T; diff --git a/buildtools/tsconfig.json b/buildtools/tsconfig.json deleted file mode 100644 index 4144761d5a..0000000000 --- a/buildtools/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "outDir": "dist", - "target": "ESNext", - "strict": false - }, - "include": ["./src", "jest.setup.ts"], - "exclude": [ - "./src/templates/templates/**", - "./src/build/docs/__tests__/test_mocks" - ] -} diff --git a/lib/.gitignore b/lib/.gitignore index 53c37a1660..cda2894a6c 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -1 +1,2 @@ -dist \ No newline at end of file +dist +dist.js \ No newline at end of file diff --git a/lib/buildtools/README.md b/lib/buildtools/README.md deleted file mode 100644 index 4c9ce5b647..0000000000 --- a/lib/buildtools/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Source Academy Buildtools - -## Testing -By default, Jest as a testing framework transforms everything from ESM to CJS before running tests. Unfortunately, the build tools are written in and compiled to ESM, which means -that certain features won't work properly if everything was written in pure ESM. - -`__dirname` is only available in CJS modules, but Node makes this availble in ESM using `import.meta.dirname`. `import.meta` is not available in CJS though, which means that Jest -won't be able to run tests. -Thus, we use `__dirname` in the source code but have `esbuild` replace it with `import.meta.dirname` using its `define` feature during compilation. At the same time -using `__dirname` means that Jest won't complain about using ESM features in CJS. \ No newline at end of file diff --git a/lib/buildtools/build.js b/lib/buildtools/build.js index 834f12ea96..36a6228eb8 100644 --- a/lib/buildtools/build.js +++ b/lib/buildtools/build.js @@ -12,9 +12,6 @@ const command = new Command() await build({ entryPoints: ['./src/commands/index.ts'], bundle: true, - define: { - __dirname: 'import.meta.dirname', - }, format: 'esm', minify: !dev, outfile: './bin/index.js', diff --git a/lib/buildtools/jest.config.js b/lib/buildtools/jest.config.js deleted file mode 100644 index d6613a49a6..0000000000 --- a/lib/buildtools/jest.config.js +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-check - -/** - * Buildtools Jest configuration - * @type {import('ts-jest').JestConfigWithTsJest} - */ -const jestConfig = { - clearMocks: true, - displayName: 'Build Tools', - extensionsToTreatAsEsm: ['.ts'], - testEnvironment: 'node', - transform: { - '.ts': ['ts-jest', { - useESM: true, - }] - }, - transformIgnorePatterns: [ - 'node_modules/(?!typedoc)', - ], - setupFilesAfterEnv: ['/jest.setup.ts'], - testMatch: [ - '/src/**/__tests__/**/*.test.ts', - ], -}; - -export default jestConfig; diff --git a/lib/buildtools/jest.setup.ts b/lib/buildtools/jest.setup.ts deleted file mode 100644 index 8046fa9ea1..0000000000 --- a/lib/buildtools/jest.setup.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Buildtools Jest setup -const mockChalkFunction = new Proxy((x: string) => x, { - get: () => mockChalkFunction -}); - -jest.mock('chalk', () => new Proxy({}, { - get: () => mockChalkFunction -})); - -jest.mock('fs/promises', () => ({ - copyFile: jest.fn(() => Promise.resolve()), - cp: jest.fn(() => Promise.resolve()), - open: jest.fn(), - mkdir: jest.fn(() => Promise.resolve()), - writeFile: jest.fn(() => Promise.resolve()), -})); - -global.process.exit = jest.fn(code => { - throw new Error(`process.exit called with ${code}`); -}); diff --git a/lib/buildtools/package.json b/lib/buildtools/package.json index 4d02de26cc..4050d1e971 100644 --- a/lib/buildtools/package.json +++ b/lib/buildtools/package.json @@ -6,9 +6,10 @@ "devDependencies": { "@commander-js/extra-typings": "^13.0.0", "@types/estree": "^1.0.0", - "@types/jest": "^29.0.0", "@types/lodash": "^4.14.198", - "@types/node": "^20.12.12" + "@types/node": "^20.12.12", + "typescript": "^5.8.2", + "vitest": "^3.1.4" }, "exports": { "*": null @@ -25,17 +26,14 @@ "console-table-printer": "^2.11.1", "esbuild": "^0.25.0", "eslint": "^9.21.0", - "jest": "^29.7.0", "jsonschema": "^1.5.0", "lodash": "^4.17.21", - "ts-jest": "^29.1.2", - "typedoc": "^0.28.4", - "typescript": "^5.8.2" + "typedoc": "^0.28.4" }, "scripts": { "build": "node ./build.js", "tsc": "tsc --project ./tsconfig.json", - "test": "jest", + "test": "vitest --config ./vitest.config.ts", "lint": "eslint" } } diff --git a/lib/buildtools/src/__tests__/utils.test.ts b/lib/buildtools/src/__tests__/utils.test.ts index 7e24ad4a37..ccca39025d 100644 --- a/lib/buildtools/src/__tests__/utils.test.ts +++ b/lib/buildtools/src/__tests__/utils.test.ts @@ -1,3 +1,4 @@ +import { describe, test, expect } from 'vitest'; import { findSeverity, type Severity } from '../utils'; describe('test findSeverity', () => { diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/test0/manifest.json b/lib/buildtools/src/build/__test_mocks__/bundles/test0/manifest.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/lib/buildtools/src/build/__test_mocks__/bundles/test0/manifest.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json b/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json index d9a93d7cd9..8dd948f1c3 100644 --- a/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json +++ b/lib/buildtools/src/build/__test_mocks__/bundles/tsconfig.json @@ -34,7 +34,7 @@ "noImplicitAny": false, "verbatimModuleSyntax": true, "paths": { - "js-slang/context": ["./typings/js-slang/context.d.ts"] + "js-slang/context": ["../../../../../../src/modules-lib/src/types/js-slang/context.d.ts"] }, "ignoreDeprecations": "5.0" }, diff --git a/lib/buildtools/src/build/docs/__tests__/building.test.ts b/lib/buildtools/src/build/docs/__tests__/building.test.ts index d357fc93f7..0f568906a2 100644 --- a/lib/buildtools/src/build/docs/__tests__/building.test.ts +++ b/lib/buildtools/src/build/docs/__tests__/building.test.ts @@ -1,12 +1,10 @@ import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; +import { resolve } from 'path'; import type { ProjectReflection } from 'typedoc'; +import { expect, test, vi, type MockedFunction } from 'vitest'; +import * as utils from '../../../utils'; import { initTypedoc, initTypedocForSingleBundle } from '../docsUtils'; -import * as json from '../json'; - -jest.mock('../json', () => ({ - buildJson: jest.fn() -})); +import { buildJson } from '../json'; const mockedWriteFile = fs.writeFile as MockedFunction; @@ -28,10 +26,11 @@ const test1Obj = { }; function matchObj(raw: string, expected: T) { - expect(JSON.parse(raw)).toMatchObject(expected); + expect(JSON.parse(raw)).toMatchObject(expected as any); } -const testMocksDir = `${__dirname}/../../__test_mocks__`; +const testMocksDir = resolve(`${import.meta.dirname}/../../__test_mocks__`); +vi.spyOn(utils, 'getBundlesDir').mockResolvedValue(`${testMocksDir}/bundles`); test('Building the json documentation for a single bundle', async () => { const project = await initTypedocForSingleBundle({ @@ -40,14 +39,16 @@ test('Building the json documentation for a single bundle', async () => { directory: `${testMocksDir}/bundles/test0`, entryPoint: `${testMocksDir}/bundles/test0/index.ts`, }); - await json.buildJson('test0', project, 'build'); + await buildJson('test0', project, 'build'); - expect(json.buildJson).toHaveBeenCalledTimes(1); const [[, test0str]] = mockedWriteFile.mock.calls; matchObj(test0str as string, test0Obj); }); test('initTypedoc for muiltiple bundles', async () => { + // This test will complain that the README can't be found, which is fine + // since it's not present inside the mock folder + const [project,] = await initTypedoc({ test0: { name: 'test0', @@ -64,12 +65,11 @@ test('initTypedoc for muiltiple bundles', async () => { }); const test0Reflection = project.getChildByName('test0') as ProjectReflection; - await json.buildJson('test0', test0Reflection, 'build'); + await buildJson('test0', test0Reflection, 'build'); const test1Reflection = project.getChildByName('test1') as ProjectReflection; - await json.buildJson('test1', test1Reflection, 'build'); + await buildJson('test1', test1Reflection, 'build'); - expect(json.buildJson).toHaveBeenCalledTimes(2); const [[, test0str], [, test1str]] = mockedWriteFile.mock.calls; matchObj(test0str as string, test0Obj); matchObj(test1str as string, test1Obj); diff --git a/lib/buildtools/src/build/docs/docsUtils.ts b/lib/buildtools/src/build/docs/docsUtils.ts index d6584d8a26..af544ce42f 100644 --- a/lib/buildtools/src/build/docs/docsUtils.ts +++ b/lib/buildtools/src/build/docs/docsUtils.ts @@ -48,7 +48,7 @@ export async function initTypedoc(manifest: Record) { alwaysCreateEntryPointModule: true, entryPoints, name: 'Source Academy Modules', - readme: `${__dirname}/docsreadme.md`, + readme: `${import.meta.dirname}/docsreadme.md`, tsconfig: tsconfigPath, }); diff --git a/lib/buildtools/src/build/modules/__tests__/bundle.test.ts b/lib/buildtools/src/build/modules/__tests__/bundle.test.ts index 63fa9438bb..5288bc0c93 100644 --- a/lib/buildtools/src/build/modules/__tests__/bundle.test.ts +++ b/lib/buildtools/src/build/modules/__tests__/bundle.test.ts @@ -1,16 +1,17 @@ import fs from 'fs/promises'; +import { vi, test, expect } from 'vitest'; import { buildBundle } from '../bundle'; const testMocksDir = `${__dirname}/../../__test_mocks__`; const written = []; -jest.spyOn(fs, 'open').mockResolvedValue({ +vi.spyOn(fs, 'open').mockResolvedValue({ createWriteStream: () => ({ write: data => { written.push(data); } }), - close: jest.fn() + close: vi.fn() } as any); test('esbuild transpilation', async () => { @@ -25,7 +26,7 @@ test('esbuild transpilation', async () => { // Trim the export default const trimmed = (data as string).slice('export default'.length); - const provider = jest.fn((p: string) => { + const provider = vi.fn((p: string) => { if (p === 'js-slang/context') { return { moduleContexts: { diff --git a/lib/buildtools/src/commands/__tests__/prebuild.test.ts b/lib/buildtools/src/commands/__tests__/lint.test.ts similarity index 68% rename from lib/buildtools/src/commands/__tests__/prebuild.test.ts rename to lib/buildtools/src/commands/__tests__/lint.test.ts index fbfa14aa99..9f6c6f0f5a 100644 --- a/lib/buildtools/src/commands/__tests__/prebuild.test.ts +++ b/lib/buildtools/src/commands/__tests__/lint.test.ts @@ -1,27 +1,30 @@ -import type { MockedFunction } from 'jest-mock'; -import * as tc from '../../prebuild/typecheck'; -import { getLintCommand, getTscCommand } from '../prebuild'; - -const lintFilesMock = jest.fn(); - -jest.mock('eslint', () => ({ - ...jest.requireActual('eslint'), - ESLint: class { - fix: boolean; - - constructor({ fix }) { - this.fix = fix; +import { vi, expect, describe, test } from 'vitest'; +import * as utils from '../../utils'; +import { getLintCommand } from '../lint'; + +const lintFilesMock = vi.hoisted(() => vi.fn()); + +vi.spyOn(utils, 'getGitRoot').mockResolvedValue('/'); + +vi.mock('eslint', async importActual => { + const actualEslint: any = await importActual(); + return { + ...actualEslint, + ESLint: class { + public fix: boolean; + + constructor({ fix }) { + this.fix = fix; + } + + static outputFixes = () => Promise.resolve(); + lintFiles = lintFilesMock; + loadFormatter = () => Promise.resolve({ + format: () => Promise.resolve('') + }); } - - lintFiles = lintFilesMock; - loadFormatter = () => Promise.resolve({ - format: () => Promise.resolve('') - }); - static outputFixes = () => Promise.resolve(); - } -})); - -jest.spyOn(tc, 'getTsconfig'); + }; +}); describe('Test Lint Command', () => { async function runCommand(...args: string[]) { @@ -106,19 +109,3 @@ describe('Test Lint Command', () => { await expect(runCommand('--fix', '--ci')).resolves.not.toThrow(); }); }); - -describe('Test Typecheck Command', () => { - const mockedGetTsconfig = tc.getTsconfig as MockedFunction; - - async function runCommand(...args: string[]) { - const command = getTscCommand(); - await command.parseAsync(args, { from: 'user' }); - - expect(tc.getTsconfig).toHaveBeenCalledTimes(1); - } - - test('tsc errors should error out', async () => { - mockedGetTsconfig.mockResolvedValueOnce({ severity: 'error' }); - await expect(runCommand()).rejects.toThrow('process.exit called with 1'); - }); -}); diff --git a/lib/buildtools/src/commands/__tests__/main.test.ts b/lib/buildtools/src/commands/__tests__/main.test.ts index bfb7dc9156..02f770fa7a 100644 --- a/lib/buildtools/src/commands/__tests__/main.test.ts +++ b/lib/buildtools/src/commands/__tests__/main.test.ts @@ -1,7 +1,8 @@ +import { describe, expect, test } from 'vitest'; import { getMainCommand } from '../main'; + describe('Make sure that all subcommands can execute', () => { const mainCommand = getMainCommand(); - // eslint-disable-next-line jest/valid-title mainCommand.commands.map(command => test(command.name(), () => { return expect(command.parseAsync(['--help'], { from: 'user' })) .rejects diff --git a/lib/buildtools/src/commands/__tests__/template.test.ts b/lib/buildtools/src/commands/__tests__/template.test.ts index d1a4deac90..59f826e1e0 100644 --- a/lib/buildtools/src/commands/__tests__/template.test.ts +++ b/lib/buildtools/src/commands/__tests__/template.test.ts @@ -1,33 +1,34 @@ import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; +import { expect, describe, test, it, vi, beforeEach, type MockedFunction } from 'vitest'; import * as manifest from '../../build/manifest'; import { askQuestion } from '../../templates/print'; import * as utils from '../../utils'; import getTemplateCommand from '../template'; -jest.spyOn(utils, 'getGitRoot').mockResolvedValue('/'); - -// Silence all the console logging -jest.spyOn(console, 'log').mockImplementation(() => {}); - -jest.mock('../../templates/print', () => ({ - ...jest.requireActual('../../templates/print'), - askQuestion: jest.fn(), - error(x: string) { - // The command has a catch-all for errors, - // so we rethrow the error to observe the value - throw new Error(x); - }, - // Because the functions run in perpetual while loops - // We need to throw an error to observe what value warn - // was called with - warn(x: string) { - throw new Error(x); - } -})); - -jest.spyOn(manifest, 'getBundleManifests').mockResolvedValue({ +vi.spyOn(utils, 'getBundlesDir').mockResolvedValue('/src/bundles'); +vi.spyOn(utils, 'getTabsDir').mockResolvedValue('/src/tabs'); + +vi.mock('../../templates/print', async importActual => { + const actualTemplates: any = await importActual(); + return ({ + ...actualTemplates, + askQuestion: vi.fn(), + error(x: string) { + // The command has a catch-all for errors, + // so we rethrow the error to observe the value + throw new Error(x); + }, + // Because the functions run in perpetual while loops + // We need to throw an error to observe what value warn + // was called with + warn(x: string) { + throw new Error(x); + } + }); +}); + +vi.spyOn(manifest, 'getBundleManifests').mockResolvedValue({ test0: { tabs: ['tab0'] }, test1: {}, }); @@ -55,11 +56,10 @@ function expectCall any>( }); } -async function expectCommandFailure(snapshot: string) { +async function expectCommandFailure(msg: string) { await expect(runCommand()) .rejects - // eslint-disable-next-line jest/no-interpolation-in-snapshots - .toMatchInlineSnapshot(`[Error: ERROR: ${snapshot}]`); + .toThrowError(`ERROR: ${msg}`); expect(fs.writeFile).not.toHaveBeenCalled(); expect(fs.copyFile).not.toHaveBeenCalled(); diff --git a/lib/buildtools/src/commands/__tests__/testing.test.ts b/lib/buildtools/src/commands/__tests__/testing.test.ts deleted file mode 100644 index 9e22ef1dcb..0000000000 --- a/lib/buildtools/src/commands/__tests__/testing.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import { runJest } from '../../testing/runner'; -import getTestCommand from '../testing'; - -jest.mock('../../testing/runner', () => ({ - runJest: jest.fn() -})); - -const runCommand = (...args: string[]) => getTestCommand() - .parseAsync(args, { from: 'user' }); -const mockRunJest = runJest as MockedFunction; - -test('Check that the test command properly passes options to jest', async () => { - await runCommand('-u', '0', '-w', './src/folder'); - - expect(runJest).toHaveBeenCalledTimes(1); - - const [[args, patterns]] = mockRunJest.mock.calls; - expect(args) - .toEqual(['-u', '0']); - expect(patterns) - .toEqual(['-w', './src/folder']); -}); - -test('Check that the test command handles windows paths as posix paths', async () => { - await runCommand('.\\src\\folder'); - expect(runJest).toHaveBeenCalledTimes(1); - - const [call] = mockRunJest.mock.calls; - expect(call[0]) - .toEqual(['./src/folder']); -}); diff --git a/lib/buildtools/src/commands/lint.ts b/lib/buildtools/src/commands/lint.ts new file mode 100644 index 0000000000..2794546958 --- /dev/null +++ b/lib/buildtools/src/commands/lint.ts @@ -0,0 +1,31 @@ +import pathlib from 'path'; +import { Command } from '@commander-js/extra-typings'; +import chalk from 'chalk'; +import { runEslint } from '../prebuild/lint'; +import { getGitRoot } from '../utils'; + +export const getLintCommand = () => new Command('lint') + .description('Run ESLint for the given directory, or the entire repository if no directory is specified') + .argument('[directory]') + .option('--fix') + .option('--ci') + .action(async (directory, { fix, ci }) => { + const fullyResolved = directory === undefined ? await getGitRoot() : pathlib.resolve(directory); + const { severity, formatted } = await runEslint(fullyResolved, fix); + let errStr: string; + + if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); + else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); + else errStr = chalk.greenBright('successfully'); + + console.log(`${chalk.cyanBright('Linting completed ')}${errStr}:\n${formatted}`); + + switch (severity) { + case 'warn': { + if (!ci) return; + } + case 'error': { + process.exit(1); + } + } + }); diff --git a/lib/buildtools/src/commands/main.ts b/lib/buildtools/src/commands/main.ts index 226dd2f959..2e7f31b621 100644 --- a/lib/buildtools/src/commands/main.ts +++ b/lib/buildtools/src/commands/main.ts @@ -1,14 +1,11 @@ import { Command } from '@commander-js/extra-typings'; import { getBuildCommand } from './build'; +import { getLintCommand } from './lint'; import { getListCommand } from './list'; -import { getLintCommand, getTscCommand } from './prebuild'; import getTemplateCommand from './template'; -import getTestCommand from './testing'; export const getMainCommand = () => new Command() .addCommand(getBuildCommand()) .addCommand(getLintCommand()) .addCommand(getListCommand()) - .addCommand(getTemplateCommand()) - .addCommand(getTestCommand()) - .addCommand(getTscCommand()); + .addCommand(getTemplateCommand()); diff --git a/lib/buildtools/src/commands/prebuild.ts b/lib/buildtools/src/commands/prebuild.ts deleted file mode 100644 index b2dcc7b7e6..0000000000 --- a/lib/buildtools/src/commands/prebuild.ts +++ /dev/null @@ -1,57 +0,0 @@ -import pathlib from 'path'; -import { Command } from '@commander-js/extra-typings'; -import chalk from 'chalk'; -import ts from 'typescript'; -import { runEslint } from '../prebuild/lint'; -import { runTsc } from '../prebuild/typecheck'; -import { getGitRoot } from '../utils'; - -export const getLintCommand = () => new Command('lint') - .description('Run ESLint for the given directory, or the entire repository if no directory is specified') - .argument('[directory]') - .option('--fix') - .option('--ci') - .action(async (directory, { fix, ci }) => { - const fullyResolved = directory === undefined ? await getGitRoot() : pathlib.resolve(directory); - const { severity, formatted } = await runEslint(fullyResolved, fix); - let errStr: string; - - if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); - else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); - else errStr = chalk.greenBright('successfully'); - - console.log(`${chalk.cyanBright('Linting completed ')}${errStr}:\n${formatted}`); - - switch (severity) { - case 'warn': { - if (!ci) return; - } - case 'error': { - process.exit(1); - } - } - }); - -export const getTscCommand = () => new Command('typecheck') - .description('Run tsc for the given directory, or the entire repository if no directory is specified') - .argument('[directory]') - .action(async directory => { - const fullyResolved = directory === undefined ? await getGitRoot() : pathlib.resolve(directory); - const tscResult = await runTsc(fullyResolved); - if (tscResult.severity === 'error' && tscResult.error) { - console.log(`${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}: ${tscResult.error}`)}`); - process.exit(1); - } - - const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { - getNewLine: () => '\n', - getCurrentDirectory: () => process.cwd(), - getCanonicalFileName: name => pathlib.basename(name) - }); - - if (tscResult.severity === 'error') { - console.log(`${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')}}`)}`); - process.exit(1); - } - console.log(`${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')}`)}`); - }); diff --git a/lib/buildtools/src/commands/testing.ts b/lib/buildtools/src/commands/testing.ts deleted file mode 100644 index 4737bfa581..0000000000 --- a/lib/buildtools/src/commands/testing.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Command } from '@commander-js/extra-typings'; - -import { runJest } from '../testing/runner'; - -const getTestCommand = () => new Command('test') - .description('Run jest') - .allowUnknownOption() - .argument('[patterns...]') - .action((patterns, args: Record, command) => { - return runJest( - Object.entries(command.args).flat(), - patterns - ); - }); - -export default getTestCommand; diff --git a/lib/buildtools/src/prebuild/typecheck.ts b/lib/buildtools/src/prebuild/typecheck.ts deleted file mode 100644 index 78ef0326c0..0000000000 --- a/lib/buildtools/src/prebuild/typecheck.ts +++ /dev/null @@ -1,86 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path'; -import ts from 'typescript'; - -type TsconfigResult = { - severity: 'error', - results?: ts.Diagnostic[] - error?: any -} | { - severity: 'success', - compilerOptions: ts.CompilerOptions - fileNames: string[] -}; - -type TscResult = { - severity: 'error' - results?: ts.Diagnostic[] - error?: any -} | { - severity: 'success', - results: ts.Diagnostic[] -}; - -export async function getTsconfig(srcDir: string): Promise { - // Step 1: Read the text from tsconfig.json - const tsconfigLocation = pathlib.join(srcDir, 'tsconfig.json'); - - try { - const configText = await fs.readFile(tsconfigLocation, 'utf-8'); - - // Step 2: Parse the raw text into a json object - const { error: configJsonError, config: configJson } = ts.parseConfigFileTextToJson(tsconfigLocation, configText); - if (configJsonError) { - return { - severity: 'error', - results: [configJsonError] - }; - } - - // Step 3: Parse the json object into a config object for use by tsc - const { errors: parseErrors, options: tsconfig, fileNames } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); - if (parseErrors.length > 0) { - return { - severity: 'error', - results: parseErrors - }; - } - - return { - severity: 'success', - compilerOptions: tsconfig, - fileNames - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -} - -export async function runTsc(srcDir: string): Promise { - const tsconfigRes = await getTsconfig(srcDir); - if (tsconfigRes.severity === 'error') { - return tsconfigRes; - } - - const { compilerOptions, fileNames } = tsconfigRes; - - try { - const tsc = ts.createProgram(fileNames, compilerOptions); - const results = tsc.emit(); - const diagnostics = ts.getPreEmitDiagnostics(tsc) - .concat(results.diagnostics); - - return { - severity: diagnostics.length > 0 ? 'error' : 'success', - results: diagnostics - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -} diff --git a/lib/buildtools/src/templates/__tests__/names.test.ts b/lib/buildtools/src/templates/__tests__/names.test.ts index 633cca5014..5f2edc1d3d 100644 --- a/lib/buildtools/src/templates/__tests__/names.test.ts +++ b/lib/buildtools/src/templates/__tests__/names.test.ts @@ -1,3 +1,4 @@ +import { describe, test, expect } from 'vitest'; import { isPascalCase, isSnakeCase } from '../utilities'; function testFunction( diff --git a/lib/buildtools/src/templates/bundle.ts b/lib/buildtools/src/templates/bundle.ts index d4c225412c..ea14840919 100644 --- a/lib/buildtools/src/templates/bundle.ts +++ b/lib/buildtools/src/templates/bundle.ts @@ -24,7 +24,7 @@ export async function addNew(bundlesDir: string, rl: Interface) { const bundleDestination = `${bundlesDir}/${moduleName}`; await fs.mkdir(bundlesDir, { recursive: true }); - await fs.cp(`${__dirname}/templates/bundle`, bundleDestination); + await fs.cp(`${import.meta.dirname}/templates/bundle`, bundleDestination); const typescriptVersion = _package.devDependencies.typescript; diff --git a/lib/buildtools/src/templates/tab.ts b/lib/buildtools/src/templates/tab.ts index 465208b880..07a9864f4d 100644 --- a/lib/buildtools/src/templates/tab.ts +++ b/lib/buildtools/src/templates/tab.ts @@ -72,7 +72,7 @@ export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) }; const tabDestination = `${tabsDir}/${tabName}`; - await fs.cp(`${__dirname}/templates/tabs`, tabDestination); + await fs.cp(`${import.meta.dirname}/templates/tabs`, tabDestination); await Promise.all([ fs.writeFile(`${tabDestination}/package.json`, JSON.stringify(packageJson, null, 2)), fs.writeFile(`${bundlesDir}/${moduleName}/manifest.json`, JSON.stringify(newManifest, null, 2)) diff --git a/lib/buildtools/src/testing/runner.ts b/lib/buildtools/src/testing/runner.ts deleted file mode 100644 index 908c3d67e7..0000000000 --- a/lib/buildtools/src/testing/runner.ts +++ /dev/null @@ -1,10 +0,0 @@ -import pathlib from 'path'; -import jest from 'jest'; - -export function runJest(jestArgs: string[], patterns: string[]) { - const filePatterns = patterns.map(pattern => pattern.split(pathlib.sep).join(pathlib.posix.sep)); - return jest.run([ - ...jestArgs, - ...filePatterns - ]); -} diff --git a/lib/buildtools/tsconfig.json b/lib/buildtools/tsconfig.json index 5451ccd1a5..7ce1106274 100644 --- a/lib/buildtools/tsconfig.json +++ b/lib/buildtools/tsconfig.json @@ -7,7 +7,7 @@ "noEmit": true }, "extends": "../tsconfig.json", - "include": ["./src", "jest.setup.ts"], + "include": ["./src", "vitest.setup.ts"], "exclude": [ "./bin", "./src/build/__test_mocks__" diff --git a/lib/buildtools/vitest.config.ts b/lib/buildtools/vitest.config.ts new file mode 100644 index 0000000000..a3d9a847c4 --- /dev/null +++ b/lib/buildtools/vitest.config.ts @@ -0,0 +1,11 @@ +// Buildtools Vitest config +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + clearMocks: true, + environment: 'node', + name: 'Build Tools', + setupFiles: ['./vitest.setup.ts'], + } +}); diff --git a/lib/buildtools/vitest.setup.ts b/lib/buildtools/vitest.setup.ts new file mode 100644 index 0000000000..3667bf0c97 --- /dev/null +++ b/lib/buildtools/vitest.setup.ts @@ -0,0 +1,39 @@ +// Buildtools vitest setup +import { vi } from 'vitest'; + +vi.mock('chalk', () => { + const mockChalkFunction = new Proxy((x: string) => x, { + get: () => mockChalkFunction + }); + + return { + default: new Proxy({}, { + get: () => mockChalkFunction + }) + }; +}); + +vi.mock('fs/promises', () => ({ + default: { + copyFile: vi.fn(() => Promise.resolve()), + cp: vi.fn(() => Promise.resolve()), + open: vi.fn(), + mkdir: vi.fn(() => Promise.resolve()), + writeFile: vi.fn(() => Promise.resolve()), + } +})); + +vi.spyOn(process, 'exit').mockImplementation(code => { + throw new Error(`process.exit called with ${code}`); +}); + +vi.mock('lodash', async importOriginal => { + const lodash: any = await importOriginal(); + + return { + default: { + ...lodash, + memoize: x => x + } + }; +}); diff --git a/lib/lintplugin/package.json b/lib/lintplugin/package.json index 5ced3439b6..1051a7c5a2 100644 --- a/lib/lintplugin/package.json +++ b/lib/lintplugin/package.json @@ -21,23 +21,12 @@ }, "devDependencies": { "@typescript-eslint/rule-tester": "^8.32.1", - "jest": "^29.7.0", - "ts-jest": "^29.1.2", - "typescript": "^5.8.2" + "typescript": "^5.8.2", + "vitest": "^3.1.4" }, "scripts": { "build": "tsc --project ./tsconfig.prod.json && node ./build.js", "tsc": "tsc --project ./tsconfig.json", - "test": "yarn node --experimental-vm-modules $(yarn bin jest)" - }, - "jest": { - "displayName": "Lint Plugin", - "extensionsToTreatAsEsm": [ - ".ts" - ], - "preset": "ts-jest/presets/default-esm", - "testMatch": [ - "/src/**/__tests__/**/*.test.ts" - ] + "test": "vitest --config ./vitest.config.ts" } } diff --git a/lib/lintplugin/vitest.config.ts b/lib/lintplugin/vitest.config.ts new file mode 100644 index 0000000000..6de909d24e --- /dev/null +++ b/lib/lintplugin/vitest.config.ts @@ -0,0 +1,13 @@ +// Lint Plugin vitest setup +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + name: 'Lint Plugin', + environment: 'node', + + // Required because the ESLint rule tester tries to use the test helpers from + // the global scope + globals: true + } +}); diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 797f42be42..835ff5bf2c 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -2,8 +2,10 @@ { "compilerOptions": { "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "bundler", + "noEmit": true, "resolveJsonModule": true, "target": "ESNext", "verbatimModuleSyntax": true diff --git a/package.json b/package.json index c25a9e9231..6d797bb389 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,10 @@ { "private": true, - "name": "modules", + "name": "@sourceacademy/modules", "version": "1.0.0", "repository": "https://github.com/source-academy/modules.git", "license": "Apache-2.0", "scripts-info": { - "//NOTE": "Run `npm i npm-scripts-info -g` to install once globally, then run `npm-scripts-info` as needed to list these descriptions", "template": "Interactively initialise a new bundle or tab from their templates", "devserver": "Start the tab development server", "devserver:lint": "Lint code related to the dev server", @@ -13,61 +12,37 @@ "docs": "Build only documentation", "lint": "Lint bundle and tab code", "build": "Lint code, then build modules and documentation", - "build:help": "Show help for the build scripts", "serve": "Start the HTTP server to serve all files in `build/`, with the same directory structure", - "scripts": "Run a script within the scripts directory", - "scripts:build": "Compile build scripts", - "scripts:lint": "Lint build script code", "prepare": "Enable git hooks", "test": "Run unit tests", "test:watch": "Watch files for changes and rerun tests related to changed files", "dev": "Build bundles and tabs only, then serve. Skips linting / type checking, does not build jsons and HTML docs. For rapid testing during development", - "watch": "Watch files for changes and rebuild on those changes", "postinstall": "Install all patches to node_modules packages" }, "type": "module", "scripts": { - "build": "yarn scripts build", - "build:help": "yarn scripts build --help", - "dev": "yarn scripts build modules && yarn serve", - "docs": "yarn scripts build docs", - "lint": "yarn scripts lint", + "build:all": "buildtools build all", + "build:docs": "buildtools build docs", + "bundles:tsc": "yarn workspaces foreach -pW --since --from ./src/bundles run tsc", + "tabs:tsc": "yarn workspaces foreach -pW --since --from ./src/tabs run tsc", "prepare": "husky", - "postinstall": "patch-package && yarn scripts:build", - "scripts": "node --max-old-space-size=4096 scripts/dist/bin.js", + "postinstall": "patch-package", "serve": "http-server --cors=* -c-1 -p 8022 ./build", - "template": "yarn scripts template", - "test": "yarn scripts test", - "test:all": "yarn test && yarn scripts:test", - "test:watch": "yarn scripts test --watch", - "watch": "yarn scripts build watch", + "template": "buildtools template", "devserver": "vite", "devserver:lint": "eslint devserver", - "devserver:tsc": "tsc --project devserver/tsconfig.json", - "scripts:all": "node scripts/scripts_manager.js", - "scripts:build": "node scripts/scripts_manager.js build && yarn lint:build", - "scripts:lint": "eslint scripts", - "scripts:tsc": "tsc --project scripts/tsconfig.json", - "scripts:test": "node scripts/scripts_manager.js test", - "lint:build": "esbuild --bundle --format=esm --outfile=scripts/dist/typeimports.js scripts/src/linting/typeimports.ts" + "devserver:tsc": "tsc --project devserver/tsconfig.json" }, "devDependencies": { - "@commander-js/extra-typings": "^13.0.0", + "@rspack/cli": "^1.3.9", + "@rspack/core": "^1.3.9", + "@sourceacademy/lint-plugin": "workspace:^", + "@sourceacademy/modules-buildtools": "workspace:^", "@stylistic/eslint-plugin": "^4.2.0", - "@types/estree": "^1.0.0", - "@types/jest": "^29.0.0", - "@types/lodash": "^4.14.198", "@types/node": "^20.12.12", - "@types/plotly.js": "^2.35.4", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", - "@types/three": "^0.175.0", - "@vitejs/plugin-react": "^4.3.4", - "acorn": "^8.8.1", - "astring": "^1.8.6", - "chalk": "^5.0.1", - "commander": "^13.0.0", - "console-table-printer": "^2.11.1", + "@yarnpkg/types": "^4.0.1", "esbuild": "^0.25.0", "eslint": "^9.21.0", "eslint-plugin-import": "^2.31.0", @@ -75,57 +50,40 @@ "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.1.0", - "globals": "^15.11.0", "http-server": "^0.13.0", "husky": "^9.0.11", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.4.1", - "jest-mock": "^29.7.0", + "patch-package": "^6.5.1", "postinstall-postinstall": "^2.1.0", - "re-resizable": "^6.9.11", - "sass": "^1.85.0", - "ts-jest": "^29.1.2", - "typedoc": "^0.25.12", "typescript": "^5.8.2", "typescript-eslint": "^8.24.1", - "vite": "^6.3.4", + "vitest": "^3.1.4", "yarnhook": "^0.6.0" }, "dependencies": { - "@blueprintjs/core": "^5.10.2", - "@blueprintjs/icons": "^5.9.0", - "@box2d/core": "^0.10.0", - "@box2d/debug-draw": "^0.10.0", - "@dimforge/rapier3d-compat": "^0.11.2", - "@jscad/modeling": "2.9.6", - "@jscad/regl-renderer": "^2.6.1", - "@jscad/stl-serializer": "2.1.11", - "ace-builds": "^1.25.1", - "classnames": "^2.3.1", - "gl-matrix": "^3.3.0", "js-slang": "^1.0.81", "lodash": "^4.17.21", - "mqtt": "^4.3.7", - "nbody": "^0.2.0", - "patch-package": "^6.5.1", - "phaser": "^3.54.0", - "plotly.js-dist": "^2.17.1", "react": "^18.3.1", - "react-ace": "^10.1.0", - "react-dom": "^18.3.1", - "regl": "^2.1.0", - "saar": "^1.0.4", - "save-file": "^2.3.1", - "source-academy-utils": "^1.0.0", - "source-academy-wabt": "^1.0.4", - "three": "^0.175.0", - "uniqid": "^5.4.0" + "react-dom": "^18.3.1" }, "jest": { "projects": [ - "src/jest.config.js", - "scripts/jest.config.js" + "src/*", + "lib/*" ] }, - "packageManager": "yarn@4.8.1" + "devEngines": { + "packageManager": { + "name": "yarn", + "version": "4.8.1", + "onFail": "warn" + } + }, + "packageManager": "yarn@4.8.1", + "workspaces": [ + "./devserver", + "./lib/*", + "./src/bundles", + "./src/tabs", + "./src/modules-lib" + ] } diff --git a/scripts/jest.config.js b/scripts/jest.config.js deleted file mode 100644 index fd5d1ef128..0000000000 --- a/scripts/jest.config.js +++ /dev/null @@ -1,20 +0,0 @@ -import { pathsToModuleNameMapper } from 'ts-jest'; -import tsconfig from './tsconfig.json' with { type: 'json' }; - -/** - * @type {import('jest').Config} - */ -const jestConfig = { - clearMocks: true, - displayName: 'Scripts', - extensionsToTreatAsEsm: ['.ts'], - testEnvironment: 'node', - moduleNameMapper: pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { prefix: '/' }), - preset: 'ts-jest/presets/default-esm', - setupFilesAfterEnv: ['/jest.setup.ts'], - testMatch: [ - '/src/**/__tests__/**/*.test.ts', - ], -}; - -export default jestConfig; diff --git a/scripts/jest.setup.ts b/scripts/jest.setup.ts deleted file mode 100644 index 94c883175c..0000000000 --- a/scripts/jest.setup.ts +++ /dev/null @@ -1,30 +0,0 @@ -const chalkFunction = new Proxy((x: string) => x, { - get: () => chalkFunction -}); - -jest.mock('chalk', () => new Proxy({}, { - get: () => chalkFunction -})); - -jest.mock('fs/promises', () => ({ - copyFile: jest.fn(() => Promise.resolve()), - mkdir: jest.fn(() => Promise.resolve()), - open: jest.fn(), - writeFile: jest.fn(() => Promise.resolve()) -})); - -jest.mock('./src/manifest', () => ({ - retrieveManifest: jest.fn(() => Promise.resolve({ - test0: { - tabs: ['tab0'] - }, - test1: { tabs: [] }, - test2: { - tabs: ['tab1'] - } - })) -})); - -global.process.exit = jest.fn(code => { - throw new Error(`process.exit called with ${code}`); -}); diff --git a/scripts/scripts_manager.js b/scripts/scripts_manager.js deleted file mode 100644 index 308726f34d..0000000000 --- a/scripts/scripts_manager.js +++ /dev/null @@ -1,86 +0,0 @@ -import pathlib from 'path'; -import { fileURLToPath } from 'url'; -import { Command } from '@commander-js/extra-typings'; -import { build as esbuild } from 'esbuild'; -import jest from 'jest'; -import _ from 'lodash'; -import { pathsToModuleNameMapper } from 'ts-jest'; -import tsconfig from './tsconfig.json' with { type: 'json' }; - -function cjsDirname(url) { - return pathlib.join(pathlib.dirname(fileURLToPath(url))); -} - -async function buildScripts({ dev }) { - const dirname = cjsDirname(import.meta.url); - - await esbuild({ - bundle: true, - entryPoints: [pathlib.join(dirname, 'src', 'index.ts')], - format: 'esm', - logLevel: 'warning', - minify: !dev, - outfile: pathlib.join(dirname, 'dist', 'bin.js'), - packages: 'external', - platform: 'node', - treeShaking: true, - tsconfig: pathlib.join(dirname, 'tsconfig.json'), - plugins: [{ - name: 'Paths to module name translator', - setup(pluginBuild) { - const replacements = pathsToModuleNameMapper(tsconfig.compilerOptions.paths); - Object.entries(replacements).forEach(([key, value]) => { - const filter = new RegExp(key, 'gm'); - - pluginBuild.onResolve({ filter }, args => { - const newPath = args.path.replace(filter, value); - return pluginBuild.resolve( - newPath, - { - kind: args.kind, - resolveDir: dirname, - }, - ); - }); - }); - } - }] - }); -} - -const buildCommand = new Command('build') - .description('Build scripts') - .option('--dev', 'Use for script development builds') - .action(buildScripts); - -/** - * Run Jest programmatically - * @param {string[]} patterns - * @returns {Promise} - */ -function runJest(patterns) { - const [args, filePatterns] = _.partition(patterns ?? [], (arg) => arg.startsWith('-')); - - // command.args automatically includes the source directory option - // which is not supported by Jest, so we need to remove it - const toRemove = args.findIndex((arg) => arg.startsWith('--srcDir')); - if (toRemove !== -1) { - args.splice(toRemove, 1); - } - - const jestArgs = args.concat(filePatterns.map((pattern) => pattern.split(pathlib.win32.sep) - .join(pathlib.posix.sep))); - - return jest.run(jestArgs, './scripts/jest.config.js'); -} - -const testCommand = new Command('test') - .description('Run tests for script files') - .allowUnknownOption() - .allowExcessArguments() - .action((_, command) => runJest(command.args)); - -await new Command() - .addCommand(buildCommand) - .addCommand(testCommand) - .parseAsync(); diff --git a/scripts/src/build/__tests__/buildAll.test.ts b/scripts/src/build/__tests__/buildAll.test.ts deleted file mode 100644 index fc7ac69ed8..0000000000 --- a/scripts/src/build/__tests__/buildAll.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import getBuildAllCommand from '..'; -import * as htmlModule from '../docs/html'; -import * as jsonModule from '../docs/json'; -import * as bundleModule from '../modules/bundles'; -import * as tabsModule from '../modules/tabs'; - -import { testBuildCommand } from './testingUtils'; - -jest.mock('../prebuild/tsc'); -jest.mock('../prebuild/lint'); -jest.mock('../docs/docsUtils'); - -jest.mock('esbuild', () => ({ - build: jest.fn() - .mockResolvedValue({ outputFiles: [] }) -})); - -jest.spyOn(jsonModule, 'buildJsons'); -jest.spyOn(htmlModule, 'buildHtml'); -jest.spyOn(tabsModule, 'bundleTabs'); -jest.spyOn(bundleModule, 'bundleBundles'); - -const asMock = any>(func: T) => func as MockedFunction; -const runCommand = (...args: string[]) => getBuildAllCommand() - .parseAsync(args, { from: 'user' }); - -describe('test build all command', () => { - testBuildCommand( - 'buildAll', - getBuildAllCommand, - [ - jsonModule.buildJsons, - htmlModule.buildHtml, - tabsModule.bundleTabs, - bundleModule.bundleBundles - ] - ); - - it('should exit with code 1 if buildJsons returns with an error', async () => { - asMock(jsonModule.buildJsons) - .mockResolvedValueOnce({ - jsons: [{ - severity: 'error', - name: 'test0', - error: {} - }] - }); - try { - await runCommand(); - } catch (error) { - expect(error) - .toEqual(new Error('process.exit called with 1')); - } - - expect(process.exit) - .toHaveBeenCalledWith(1); - }); - - it('should exit with code 1 if buildHtml returns with an error', async () => { - asMock(htmlModule.buildHtml) - .mockResolvedValueOnce({ - elapsed: 0, - result: { - severity: 'error', - error: {} - } - }); - - try { - await runCommand(); - } catch (error) { - expect(error) - .toEqual(new Error('process.exit called with 1')); - } - - expect(process.exit) - .toHaveBeenCalledWith(1); - - expect(htmlModule.buildHtml) - .toHaveBeenCalledTimes(1); - }); -}); diff --git a/scripts/src/build/__tests__/buildUtils.test.ts b/scripts/src/build/__tests__/buildUtils.test.ts deleted file mode 100644 index dce466ea77..0000000000 --- a/scripts/src/build/__tests__/buildUtils.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { retrieveBundles, retrieveBundlesAndTabs, retrieveTabs } from '@src/commandUtils'; - -describe('Test retrieveBundlesAndTabs', () => { - type TestCase = [ - desc: string, - { - bundles?: string[] | null - tabs?: string[] | null - }, - boolean, - Awaited> - ]; - - const testCases: TestCase[] = [ - [ - 'Should return all bundles and tabs when null is given for both and shouldAddModuleTabs is true', - {}, - true, - { - modulesSpecified: false, - bundles: ['test0', 'test1', 'test2'], - tabs: ['tab0', 'tab1'] - } - ], - [ - 'Should return all bundles and tabs when null is given for both and shouldAddModuleTabs is false', - {}, - false, - { - modulesSpecified: false, - bundles: ['test0', 'test1', 'test2'], - tabs: ['tab0', 'tab1'] - } - ], - [ - 'Should add the tabs of specified bundles if shouldAddModuleTabs is true', - { bundles: ['test0'], tabs: [] }, - true, - { - modulesSpecified: true, - bundles: ['test0'], - tabs: ['tab0'] - } - ], - [ - 'Should not add the tabs of specified bundles if shouldAddModuleTabs is false', - { bundles: ['test0'], tabs: [] }, - false, - { - modulesSpecified: true, - bundles: ['test0'], - tabs: [] - } - ], - [ - 'Should only return specified tabs if shouldAddModuleTabs is false', - { bundles: [], tabs: ['tab0', 'tab1'] }, - false, - { - modulesSpecified: true, - bundles: [], - tabs: ['tab0', 'tab1'] - } - ], - [ - 'Should only return specified tabs even if shouldAddModuleTabs is true', - { bundles: [], tabs: ['tab0', 'tab1'] }, - true, - { - modulesSpecified: true, - bundles: [], - tabs: ['tab0', 'tab1'] - } - ], - [ - 'Should return specified tabs and bundles (and the tabs of those bundles) if shouldAddModuleTabs is true', - { - bundles: ['test0'], - tabs: ['tab1'] - }, - true, - { - modulesSpecified: true, - bundles: ['test0'], - tabs: ['tab0', 'tab1'] - } - ], - [ - 'Should only return specified tabs and bundles if shouldAddModuleTabs is false', - { - bundles: ['test0'], - tabs: ['tab1'] - }, - false, - { - modulesSpecified: true, - bundles: ['test0'], - tabs: ['tab1'] - } - ] - ]; - - test.each(testCases)('%#. %s:', async (_, { bundles, tabs }, shouldAddModuleTabs, expected) => { - const outputs = await retrieveBundlesAndTabs('modules.json', bundles, tabs, shouldAddModuleTabs); - - expect(outputs) - .toMatchObject(expected); - }); - - it('should throw an exception when encountering unknown modules or tabs', () => Promise.all([ - expect(retrieveBundlesAndTabs('', ['random'], [], true)) - .rejects.toMatchObject(new Error('Unknown bundles: random')), - - expect(retrieveBundlesAndTabs('', [], ['random1', 'random2'], false)) - .rejects.toMatchObject(new Error('Unknown tabs: random1, random2')) - ])); - - it('should always return unique modules and tabs', async () => { - const result = await retrieveBundlesAndTabs('', ['test0', 'test0'],['tab0'], false); - - expect(result.bundles) - .toEqual(['test0']); - expect(result.modulesSpecified) - .toBe(true); - expect(result.tabs) - .toEqual(['tab0']); - }); -}); - -describe('test retrieveBundles', () => { - it('should throw an exception when encountering unknown bundles', () => expect(retrieveBundles('', ['unknown'])) - .rejects - .toMatchInlineSnapshot('[Error: Unknown bundles: unknown]') - ); - - it('should always return unique bundles', () => expect(retrieveBundles('', ['test0', 'test0'])) - .resolves - .toMatchObject({ - bundles: ['test0'], - modulesSpecified: true - })); -}); - -describe('test retrieveTabs', () => { - it('should throw an exception when encountering unknown tabs', () => expect(retrieveTabs('', ['unknown'])) - .rejects - .toMatchInlineSnapshot('[Error: Unknown tabs: unknown]') - ); - - it('should always return unique bundles', () => expect(retrieveTabs('', ['tab0', 'tab0'])) - .resolves - .toMatchObject({ - tabs: ['tab0'] - })); -}); diff --git a/scripts/src/build/__tests__/testingUtils.ts b/scripts/src/build/__tests__/testingUtils.ts deleted file mode 100644 index 354f22d41d..0000000000 --- a/scripts/src/build/__tests__/testingUtils.ts +++ /dev/null @@ -1,101 +0,0 @@ -import fs from 'fs/promises'; -import type { Command } from '@commander-js/extra-typings'; -import type { MockedFunction } from 'jest-mock'; - -import * as lint from '../prebuild/lint'; -jest.spyOn(lint, 'runEslint'); - -import * as tsc from '../prebuild/tsc'; -jest.spyOn(tsc, 'runTsc'); - -const mockedTsc = tsc.runTsc as MockedFunction; -const mockedLint = lint.runEslint as MockedFunction; - -export function testBuildCommand( - commandName: string, - commandGetter: (...args: string[]) => Command, - mockedFunctions: MockedFunction[] -) { - function expectToBeCalled(times: number) { - mockedFunctions.forEach(func => expect(func) - .toHaveBeenCalledTimes(times)); - } - - function runCommand(...args: string[]) { - return commandGetter() - .parseAsync(args, { from: 'user' }); - } - - test(`${commandName} should run tsc when --tsc is specified`, async () => { - mockedTsc.mockResolvedValueOnce({ - elapsed: 0, - result: { - severity: 'success', - results: [] - } - }); - - await runCommand('--tsc'); - expect(tsc.runTsc) - .toHaveBeenCalledTimes(1); - expectToBeCalled(1); - }); - - test(`${commandName} should not run if tsc throws an error`, async () => { - mockedTsc.mockResolvedValueOnce({ - elapsed: 0, - result: { - severity: 'error', - results: [] - } - }); - - await expect(runCommand('--tsc')) - .rejects - .toMatchInlineSnapshot('[Error: process.exit called with 1]'); - - expect(tsc.runTsc) - .toHaveBeenCalledTimes(1); - expectToBeCalled(0); - }); - - test(`${commandName} should run linting when --lint is specified`, async () => { - mockedLint.mockResolvedValueOnce({ - elapsed: 0, - result: { - severity: 'success', - formatted: '' - } - }); - await runCommand('--lint'); - expect(lint.runEslint) - .toHaveBeenCalledTimes(1); - expectToBeCalled(1); - }); - - test(`${commandName} should not run if linting throws an error`, async () => { - mockedLint.mockResolvedValueOnce({ - elapsed: 0, - result: { - severity: 'error', - formatted: '' - } - }); - - await expect(runCommand('--lint')) - .rejects - .toMatchInlineSnapshot('[Error: process.exit called with 1]'); - - expect(lint.runEslint) - .toHaveBeenCalledTimes(1); - - expectToBeCalled(0); - }); - - test(`${commandName} should copy the manifest if there are no errors`, async () => { - await runCommand(); - expectToBeCalled(1); - expect(fs.copyFile) - .toHaveBeenCalledTimes(1); - }); -} diff --git a/scripts/src/build/docs/__mocks__/docsUtils.ts b/scripts/src/build/docs/__mocks__/docsUtils.ts deleted file mode 100644 index 8532cf4da5..0000000000 --- a/scripts/src/build/docs/__mocks__/docsUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const initTypedoc = jest.fn(() => { - const proj = { - getChildByName: () => ({ - children: [] - }), - path: '' - } as any; - - const app = { - convert: jest.fn() - .mockReturnValue(proj), - generateDocs: jest.fn(() => Promise.resolve()) - }; - - return Promise.resolve([proj, app]); -}); diff --git a/scripts/src/build/docs/__tests__/building.test.ts b/scripts/src/build/docs/__tests__/building.test.ts deleted file mode 100644 index 786787b57f..0000000000 --- a/scripts/src/build/docs/__tests__/building.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; -import { initTypedoc } from '../docsUtils'; -import * as json from '../json'; - -jest.spyOn(json, 'buildJson'); - -const mockedWriteFile = fs.writeFile as MockedFunction; - -const test0Obj = { - test_function: { - kind: 'function', - params: [['_param0', 'string']], - description: '

    This is just some test function

    ', - retType: 'number' - } -}; - -const test1Obj = { - test_variable: { - kind: 'variable', - type: 'number', - description: '

    Test variable

    ' - } -}; - -const workingDir = __dirname + '/test_mocks'; - -function matchObj(raw: string, expected: T) { - expect(JSON.parse(raw)).toMatchObject(expected); -} - -describe('Check that buildJsons can handle building different numbers of bundles', () => { - test('Building the json documentation for a single bundle', async () => { - const [project,] = await initTypedoc(['test0'], workingDir, false, false); - await json.buildJsons({ bundles: ['test0'] }, workingDir, project); - - expect(json.buildJson).toHaveBeenCalledTimes(1); - const [[, test0str]] = mockedWriteFile.mock.calls; - matchObj(test0str as string, test0Obj); - }); - - test('Building the json documentation for multiple bundles', async () => { - const [project,] = await initTypedoc(['test0', 'test1'], workingDir, false, false); - await json.buildJsons({ bundles: ['test0', 'test1'] }, workingDir, project); - - expect(json.buildJson).toHaveBeenCalledTimes(2); - const [[, test0Str], [, test1Str]] = mockedWriteFile.mock.calls; - matchObj(test0Str as string, test0Obj); - matchObj(test1Str as string, test1Obj); - }); -}); diff --git a/scripts/src/build/docs/__tests__/docs.test.ts b/scripts/src/build/docs/__tests__/docs.test.ts deleted file mode 100644 index 5877d52010..0000000000 --- a/scripts/src/build/docs/__tests__/docs.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import { testBuildCommand } from '@src/build/__tests__/testingUtils'; -import { getBuildDocsCommand } from '..'; -import * as html from '../html'; -import * as json from '../json'; - -jest.mock('../docsUtils'); - -jest.spyOn(json, 'buildJsons'); -jest.spyOn(html, 'buildHtml'); - -const asMock = any>(func: T) => func as MockedFunction; -const mockBuildJson = asMock(json.buildJsons); - -const runCommand = (...args: string[]) => getBuildDocsCommand() - .parseAsync(args, { from: 'user' }); - -describe('test the docs command', () => { - testBuildCommand( - 'buildDocs', - getBuildDocsCommand, - [json.buildJsons, html.buildHtml] - ); - - it('should only build the documentation for specified modules', async () => { - await runCommand('-b', 'test0', 'test1'); - - expect(json.buildJsons) - .toHaveBeenCalledTimes(1); - - const buildJsonCall = mockBuildJson.mock.calls[0]; - expect(buildJsonCall[0]) - .toEqual({ - bundles: ['test0', 'test1'], - modulesSpecified: true - }); - - expect(html.buildHtml) - .toHaveBeenCalledTimes(1); - - expect(html.buildHtml) - .toReturnWith(Promise.resolve({ - elapsed: 0, - result: { - severity: 'warn' - } - })); - }); -}); diff --git a/scripts/src/build/docs/__tests__/json.test.ts b/scripts/src/build/docs/__tests__/json.test.ts deleted file mode 100644 index 86581ec019..0000000000 --- a/scripts/src/build/docs/__tests__/json.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; -import { testBuildCommand } from '@src/build/__tests__/testingUtils'; -import * as json from '../json'; - -jest.spyOn(json, 'buildJsons'); -jest.mock('../docsUtils'); - -const mockBuildJson = json.buildJsons as MockedFunction; -const runCommand = (...args: string[]) => json.getBuildJsonsCommand() - .parseAsync(args, { from: 'user' }); - -describe('test json command', () => { - testBuildCommand( - 'buildJsons', - json.getBuildJsonsCommand, - [json.buildJsons] - ); - - test('normal function', async () => { - await runCommand(); - - expect(fs.mkdir) - .toBeCalledWith('build/jsons', { recursive: true }); - - expect(json.buildJsons) - .toHaveBeenCalledTimes(1); - }); - - it('should only build the jsons for specified modules', async () => { - await runCommand('-b', 'test0', 'test1'); - - expect(json.buildJsons) - .toHaveBeenCalledTimes(1); - - const buildJsonCall = mockBuildJson.mock.calls[0]; - expect(buildJsonCall[0]) - .toMatchObject({ - bundles: ['test0', 'test1'] - }); - }); -}); diff --git a/scripts/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts b/scripts/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts deleted file mode 100644 index e7dee10395..0000000000 --- a/scripts/src/build/docs/__tests__/test_mocks/bundles/test0/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This is just some test function - * @param _param0 Test parameter - * @returns Zero - */ -export function test_function(_param0: string) { - return 0; -} diff --git a/scripts/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts b/scripts/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts deleted file mode 100644 index 9f313a9cbd..0000000000 --- a/scripts/src/build/docs/__tests__/test_mocks/bundles/test1/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Test variable - */ -export const test_variable: number = 0; diff --git a/scripts/src/build/docs/__tests__/test_mocks/tsconfig.json b/scripts/src/build/docs/__tests__/test_mocks/tsconfig.json deleted file mode 100644 index d9a93d7cd9..0000000000 --- a/scripts/src/build/docs/__tests__/test_mocks/tsconfig.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "compilerOptions": { - /* Allow JavaScript files to be imported inside your project, instead of just .ts and .tsx files. */ - "allowJs": false, - /* When set to true, allowSyntheticDefaultImports allows you to write an import like "import React from "react";" */ - "allowSyntheticDefaultImports": true, - /* See https://www.typescriptlang.org/tsconfig#esModuleInterop */ - "esModuleInterop": true, - /* Controls how JSX constructs are emitted in JavaScript files. This only affects output of JS files that started in .tsx files. */ - "jsx": "react-jsx", - /* See https://www.typescriptlang.org/tsconfig#lib */ - "lib": ["es6", "dom", "es2016", "ESNext", "scripthost"], - /* Sets the module system for the program. See the Modules reference page for more information. */ - "module": "esnext", - /* Specify the module resolution strategy: 'node' (Node.js) or 'classic' (used in TypeScript before the release of 1.6). */ - "moduleResolution": "node", - /* Do not emit compiler output files like JavaScript source code, source-maps or declarations. */ - "noEmit": true, - /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ - "resolveJsonModule": true, - /* The longest common path of all non-declaration input files. */ - "rootDir": "./", - /* Enables the generation of sourcemap files. These files allow debuggers and other tools to display the original TypeScript source code when actually working with the emitted JavaScript files. */ - "sourceMap": false, - /* Skip running typescript on declaration files. This option is needed due to a known bug in react-ace */ - "skipLibCheck": true, - /* The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. */ - "strict": true, - "forceConsistentCasingInFileNames": true, - /* The target setting changes which JS features are downleveled and which are left intact. */ - "target": "es6", - /* In some cases where no type annotations are present, TypeScript will fall back to a type of any for a variable when it cannot infer the type. */ - /* *** TEMPORARILY ADDED UNTIL ALL MODULES HAVE BEEN REFACTORED!!!!!!!!!!! *** */ - "noImplicitAny": false, - "verbatimModuleSyntax": true, - "paths": { - "js-slang/context": ["./typings/js-slang/context.d.ts"] - }, - "ignoreDeprecations": "5.0" - }, - /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ - "include": ["."], - /* Specifies an array of filenames or patterns that should be skipped when resolving include. */ - "exclude": ["jest.config.js"] -} diff --git a/scripts/src/build/docs/docsUtils.ts b/scripts/src/build/docs/docsUtils.ts deleted file mode 100644 index df8111a7e1..0000000000 --- a/scripts/src/build/docs/docsUtils.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as td from 'typedoc'; -import { expandBundleNames } from '../utils'; - -export function initTypedoc(bundles: string[], srcDir: string, verbose: boolean, watch: true): Promise<[null, td.Application]>; -export function initTypedoc(bundles: string[], srcDir: string, verbose: boolean, watch: false): Promise<[td.ProjectReflection, td.Application]>; -export async function initTypedoc( - bundles: string[], - srcDir: string, - verbose: boolean, - watch: boolean -) { - const app = await td.Application.bootstrap({ - categorizeByGroup: true, - entryPoints: expandBundleNames(srcDir, bundles), - excludeInternal: true, - // logger: watch ? 'none' : undefined, - logLevel: verbose ? 'Info' : 'Error', - name: 'Source Academy Modules', - readme: './scripts/src/build/docs/docsreadme.md', - tsconfig: `${srcDir}/tsconfig.json`, - skipErrorChecking: true, - preserveWatchOutput: watch, - }, [ new td.TSConfigReader() ]); - - if (watch) return [null, app]; - - const project = await app.convert(); - if (!project) { - throw new Error('Failed to initialize typedoc - Make sure to check that the source files have no compilation errors!'); - } - return [project, app] as [td.ProjectReflection, td.Application]; -} - -export type TypedocResult = Awaited>; diff --git a/scripts/src/build/docs/docsreadme.md b/scripts/src/build/docs/docsreadme.md deleted file mode 100644 index 0adf8f5beb..0000000000 --- a/scripts/src/build/docs/docsreadme.md +++ /dev/null @@ -1,18 +0,0 @@ -# Overview - -The Source Academy allows programmers to import functions and constants from a module, using JavaScript's `import` directive. For example, the programmer may decide to import the function `thrice` from the module `repeat` by starting the program with -``` -import { thrice } from "repeat"; -``` - -When evaluating such a directive, the Source Academy looks for a module with the matching name, here `repeat`, in a preconfigured modules site. The Source Academy at https://sourceacademy.org uses the default modules site (located at https://source-academy.github.io/modules). - -After importing functions or constants from a module, they can be used as usual. -``` -thrice(display)(8); // displays 8 three times -``` -if `thrice` is declared in the module `repeat` as follows: -``` -const thrice = f => x => f(f(f(x))); -``` -[List of modules](modules.html) available at the default modules site. \ No newline at end of file diff --git a/scripts/src/build/docs/drawdown.ts b/scripts/src/build/docs/drawdown.ts deleted file mode 100644 index 8fe5d5aaa8..0000000000 --- a/scripts/src/build/docs/drawdown.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* eslint-disable*/ -/** - * Module to convert from markdown into HTML - * drawdown.js - * (c) Adam Leggett - */ - -export default (src: string): string => { - var rx_lt = //g; - var rx_space = /\t|\r|\uf8ff/g; - var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g; - var rx_hr = /^([*\-=_] *){3,}$/gm; - var rx_blockquote = /\n *> *([^]*?)(?=(\n|$){2})/g; - var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g; - var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g; - var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+)|`)(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g; - var rx_code = /\n((```|~~~).*\n?([^]*?)\n?\2|(( {4}.*?\n)+))/g; - var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g; - var rx_table = /\n(( *\|.*?\| *\n)+)/g; - var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/; - var rx_row = /.*\n/g; - var rx_cell = /\||(.*?[^\\])\|/g; - var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g; - var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g; - var rx_stash = /-\d+\uf8ff/g; - - function replace(rex, fn) { - src = src.replace(rex, fn); - } - - function element(tag, content) { - return '<' + tag + '>' + content + ''; - } - - function blockquote(src) { - return src.replace(rx_blockquote, function (all, content) { - return element( - 'blockquote', - blockquote(highlight(content.replace(/^ *> */gm, ''))) - ); - }); - } - - function list(src) { - return src.replace(rx_list, function (all, ind, ol, num, low, content) { - var entry = element( - 'li', - highlight( - content - .split( - RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g') - ) - .map(list) - .join('
  • ') - ) - ); - - return ( - '\n' + - (ol - ? '
      ' - : parseInt(ol, 36) - - 9 + - '" style="list-style-type:' + - (low ? 'low' : 'upp') + - 'er-alpha">') + - entry + - '
    ' - : element('ul', entry)) - ); - }); - } - - function highlight(src) { - return src.replace( - rx_highlight, - function (all, _, p1, emp, sub, sup, small, big, p2, content) { - return ( - _ + - element( - emp - ? p2 - ? 'strong' - : 'em' - : sub - ? p2 - ? 's' - : 'sub' - : sup - ? 'sup' - : small - ? 'small' - : big - ? 'big' - : 'code', - highlight(content) - ) - ); - } - ); - } - - function unesc(str) { - return str.replace(rx_escape, '$1'); - } - - var stash = []; - var si = 0; - - src = '\n' + src + '\n'; - - replace(rx_lt, '<'); - replace(rx_gt, '>'); - replace(rx_space, ' '); - - // blockquote - src = blockquote(src); - - // horizontal rule - replace(rx_hr, '
    '); - - // list - src = list(src); - replace(rx_listjoin, ''); - - // code - replace(rx_code, function (all, p1, p2, p3, p4) { - stash[--si] = element( - 'pre', - element('code', p3 || p4.replace(/^ {4}/gm, '')) - ); - return si + '\uf8ff'; - }); - - // link or image - replace(rx_link, function (all, p1, p2, p3, p4, p5, p6) { - stash[--si] = p4 - ? p2 - ? '' + p3 + '' - : '' + unesc(highlight(p3)) + '' - : p6; - return si + '\uf8ff'; - }); - - // table - replace(rx_table, function (all, table) { - var sep = table.match(rx_thead)[1]; - return ( - '\n' + - element( - 'table', - table.replace(rx_row, function (row, ri) { - return row == sep - ? '' - : element( - 'tr', - row.replace(rx_cell, function (all, cell, ci) { - return ci - ? element( - sep && !ri ? 'th' : 'td', - unesc(highlight(cell || '')) - ) - : ''; - }) - ); - }) - ) - ); - }); - - // heading - replace(rx_heading, function (all, _, p1, p2) { - return _ + element('h' + p1.length, unesc(highlight(p2))); - }); - - // paragraph - replace(rx_para, function (all, content) { - return element('p', unesc(highlight(content))); - }); - - // stash - replace(rx_stash, function (all) { - return stash[parseInt(all)]; - }); - - return src.trim(); -}; \ No newline at end of file diff --git a/scripts/src/build/docs/html.ts b/scripts/src/build/docs/html.ts deleted file mode 100644 index 8594bed418..0000000000 --- a/scripts/src/build/docs/html.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Command } from '@commander-js/extra-typings'; -import chalk from 'chalk'; -import { manifestOption, outDirOption, retrieveBundlesAndTabs, srcDirOption, wrapWithTimer, type BuildInputs } from '@src/commandUtils'; -import type { AwaitedReturn } from '../utils'; -import { initTypedoc, type TypedocResult } from './docsUtils'; - -export type HtmlResult = { - severity: 'error' | 'warn' - error: any -} | { - severity: 'success' -}; - -export const buildHtml = wrapWithTimer(async ( - inputs: Omit, - outDir: string, - [project, app]: TypedocResult -): Promise => { - if (inputs.modulesSpecified) { - return { - severity: 'warn', - error: 'Not all modules were built, skipping building HTML documentation' - }; - } - - try { - await app.generateDocs(project, `${outDir}/documentation`); - return { - severity: 'success' - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -}); - -export function htmlLogger({ result, elapsed }: AwaitedReturn) { - const timeStr = `${(elapsed / 1000).toFixed(2)}s`; - switch (result.severity) { - case 'success': - return `${chalk.cyanBright('Built HTML documentation')} ${chalk.greenBright('successfully')} in ${timeStr}`; - case 'warn': - return chalk.yellowBright(result.error); - case 'error': - return `${chalk.redBright('Failed')} ${chalk.cyanBright('to build HTML documentation: ')} ${result.error}`; - } -} - -export const getBuildHtmlCommand = () => new Command('html') - .addOption(srcDirOption) - .addOption(outDirOption) - .addOption(manifestOption) - .option('-v, --verbose') - .action(async opts => { - const inputs = await retrieveBundlesAndTabs(opts.manifest, null, [], false); - const tdResult = await initTypedoc(inputs.bundles, opts.srcDir, opts.verbose, false); - const result = await buildHtml(inputs, opts.outDir, tdResult); - console.log(htmlLogger(result)); - - if (result.result.severity === 'error') process.exit(1); - }); diff --git a/scripts/src/build/docs/index.ts b/scripts/src/build/docs/index.ts deleted file mode 100644 index 85968247df..0000000000 --- a/scripts/src/build/docs/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { bundlesOption, type BuildInputs } from '@src/commandUtils'; -import { createBuildCommand, createBuildCommandHandler, type AwaitedReturn } from '../utils'; -import { initTypedoc, type TypedocResult } from './docsUtils'; -import { buildHtml } from './html'; -import { buildJsons } from './json'; - -export async function buildDocs(inputs: BuildInputs, outDir: string, tdResult: TypedocResult): Promise< - AwaitedReturn & { html: AwaitedReturn } -> { - const [jsonsResult, htmlResult] = await Promise.all([ - buildJsons(inputs, outDir, tdResult[0]), - buildHtml(inputs, outDir, tdResult) - ]); - - return { - ...jsonsResult, - html: htmlResult - }; -} - -const docsCommandHandler = createBuildCommandHandler(async (inputs, { srcDir, outDir, verbose }) => { - const tdResult = await initTypedoc(inputs.bundles, srcDir, verbose, false); - return buildDocs(inputs, outDir, tdResult); -}, 'tabs'); - -export const getBuildDocsCommand = () => createBuildCommand( - 'docs', - 'Build HTML and json documentation' -) - .addOption(bundlesOption) - .action(opts => docsCommandHandler({ - ...opts, - tabs: [] - })); - -export { getBuildJsonsCommand } from './json'; -export { getBuildHtmlCommand } from './html'; - -export { buildJsons, buildHtml }; diff --git a/scripts/src/build/docs/json.ts b/scripts/src/build/docs/json.ts deleted file mode 100644 index 87746811df..0000000000 --- a/scripts/src/build/docs/json.ts +++ /dev/null @@ -1,120 +0,0 @@ -import fs from 'fs/promises'; -import * as td from 'typedoc'; -import { bundlesOption, type BuildInputs } from '@src/commandUtils'; -import { createBuildCommand, createBuildCommandHandler, type OperationResult } from '../utils'; -import { initTypedoc } from './docsUtils'; -import drawdown from './drawdown'; - -const typeToName = (type?: td.SomeType) => type.stringify(td.TypeContext.none); - -const parsers = { - [td.ReflectionKind.Function](obj) { - // Functions should have only 1 signature - const [signature] = obj.signatures; - - let description: string; - if (signature.comment) { - description = drawdown(signature.comment.summary.map(({ text }) => text) - .join('')); - } else { - description = 'No description available'; - } - - const params = signature.parameters.map(({ type, name }) => [name, typeToName(type)] as [string, string]); - - return { - kind: 'function', - name: obj.name, - description, - params, - retType: typeToName(signature.type) - }; - }, - [td.ReflectionKind.Variable](obj) { - let description: string; - if (obj.comment) { - description = drawdown(obj.comment.summary.map(({ text }) => text) - .join('')); - } else { - description = 'No description available'; - } - - return { - kind: 'variable', - name: obj.name, - description, - type: typeToName(obj.type) - }; - } -} satisfies Partial any>>; - -export const buildJson = async (name: string, reflection: td.DeclarationReflection, outDir: string): Promise => { - try { - const jsonData = reflection.children.reduce((res, element) => { - const parser = parsers[element.kind]; - return { - ...res, - [element.name]: parser - ? parser(element) - : { kind: 'unknown' } - }; - }, {}); - - await fs.writeFile(`${outDir}/jsons/${name}.json`, JSON.stringify(jsonData, null, 2)); - - return { - name, - severity: 'success' - }; - } catch (error) { - return { - name, - severity: 'error', - error - }; - } -}; - -// For some reason if you want to properly test these functions in Jest -// They've gotta be declared as constants -export const buildJsons = async ( - { bundles }: BuildInputs, - outDir: string, - project: td.ProjectReflection -): Promise> => { - await fs.mkdir(`${outDir}/jsons`, { recursive: true }); - - if (bundles.length === 1) { - // Typedoc returns a different format if only one bundle is specified - // Hence we need slightly different code - const [bundle] = bundles; - const result = await buildJson( - bundle, - project as unknown as td.DeclarationReflection, - outDir - ); - - return { - jsons: [result] - }; - } - - const results = await Promise.all(bundles.map(bundle => buildJson( - bundle, - project.getChildByName(bundle) as td.DeclarationReflection, - outDir - ))); - - return { - jsons: results - }; -}; - -const jsonCommandHandler = createBuildCommandHandler(async (inputs, { srcDir, outDir, verbose }) => { - const [project] = await initTypedoc(inputs.bundles, srcDir, verbose, false); - return buildJsons(inputs, outDir, project); -}, 'tabs'); - -export const getBuildJsonsCommand = () => createBuildCommand('jsons', 'Build json documentation') - .addOption(bundlesOption) - .action(opts => jsonCommandHandler({ ...opts, tabs: [] })); diff --git a/scripts/src/build/index.ts b/scripts/src/build/index.ts deleted file mode 100644 index e3484e3839..0000000000 --- a/scripts/src/build/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Command } from '@commander-js/extra-typings'; -import { bundlesOption, tabsOption } from '@src/commandUtils'; -import { buildDocs, getBuildDocsCommand, getBuildHtmlCommand, getBuildJsonsCommand } from './docs'; -import { initTypedoc } from './docs/docsUtils'; -import { buildModules, getBuildBundlesCommand, getBuildTabsCommand } from './modules'; -import { createBuildCommand, type BuildTask, createBuildCommandHandler } from './utils'; -import getWatchCommand from './watch'; - -const buildAll: BuildTask = async (inputs, opts) => { - const tdResult = await initTypedoc(inputs.bundles, opts.srcDir, opts.verbose, false); - - const [modulesResult, docsResult] = await Promise.all([ - buildModules(inputs, opts), - buildDocs(inputs, opts.outDir, tdResult) - ]); - - return { - ...modulesResult, - ...docsResult - }; -}; - -const buildAllCommandHandler = createBuildCommandHandler(buildAll); -const getBuildAllCommand = () => createBuildCommand('all', 'Build bundles and tabs and documentation') - .addOption(bundlesOption) - .addOption(tabsOption) - .action(buildAllCommandHandler); - -const getBuildCommand = () => new Command('build') - .addCommand(getBuildAllCommand(), { isDefault: true }) - .addCommand(getBuildBundlesCommand()) - .addCommand(getBuildDocsCommand()) - .addCommand(getBuildHtmlCommand()) - .addCommand(getBuildJsonsCommand()) - .addCommand(getBuildTabsCommand()) - .addCommand(getWatchCommand()); - -export default getBuildCommand; diff --git a/scripts/src/build/modules/__tests__/bundle.test.ts b/scripts/src/build/modules/__tests__/bundle.test.ts deleted file mode 100644 index 817af39174..0000000000 --- a/scripts/src/build/modules/__tests__/bundle.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import { testBuildCommand } from '@src/build/__tests__/testingUtils'; -import * as bundles from '../bundles'; - -jest.spyOn(bundles, 'bundleBundles'); - -jest.mock('esbuild', () => ({ - build: jest.fn() - .mockResolvedValue({ outputFiles: [] }) -})); - -testBuildCommand( - 'buildBundles', - bundles.getBuildBundlesCommand, - [bundles.bundleBundles] -); - -test('Normal command', async () => { - await bundles.getBuildBundlesCommand() - .parseAsync(['-b', 'test0'], { from: 'user' }); - - expect(bundles.bundleBundles) - .toHaveBeenCalledTimes(1); - - const [args] = (bundles.bundleBundles as MockedFunction).mock.calls[0]; - expect(args) - .toMatchObject({ - bundles: ['test0'], - tabs: ['tab0'], - modulesSpecified: true - }); -}); diff --git a/scripts/src/build/modules/__tests__/output.test.ts b/scripts/src/build/modules/__tests__/output.test.ts deleted file mode 100644 index 51599a176d..0000000000 --- a/scripts/src/build/modules/__tests__/output.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { build as esbuild } from 'esbuild'; -import { commonEsbuildOptions, outputBundleOrTab } from '../commons'; -import { mockStream } from './streamMocker'; - -const testBundle = ` - import context from 'js-slang/context'; - - export const foo = () => 'foo'; - export const bar = () => { - context.moduleContexts.test0.state = 'bar'; - }; -`; - -test('building a bundle', async () => { - const { outputFiles: [file] } = await esbuild({ - ...commonEsbuildOptions, - stdin: { - contents: testBundle - }, - outdir: '.', - outbase: '.', - external: ['js-slang*'] - }); - - const rawBundleTextPromise = mockStream(); - - const result = await outputBundleOrTab(file, 'build'); - expect(result.severity) - .toEqual('success'); - - const bundleText = (await rawBundleTextPromise).slice('export default'.length); - const mockContext = { - moduleContexts: { - test0: { - state: null - } - } - }; - const bundleFuncs = eval(bundleText)(x => ({ - 'js-slang/context': mockContext - }[x])); - expect(bundleFuncs.foo()) - .toEqual('foo'); - expect(bundleFuncs.bar()) - .toEqual(undefined); - expect(mockContext.moduleContexts) - .toMatchObject({ - test0: { - state: 'bar' - } - }); -}); diff --git a/scripts/src/build/modules/__tests__/streamMocker.ts b/scripts/src/build/modules/__tests__/streamMocker.ts deleted file mode 100644 index aab3c3e706..0000000000 --- a/scripts/src/build/modules/__tests__/streamMocker.ts +++ /dev/null @@ -1,28 +0,0 @@ -import fs from 'fs/promises'; -import { PassThrough } from 'stream'; -import type { MockedFunction } from 'jest-mock'; - -const mockedFsOpen = (fs.open as MockedFunction); - -export function mockStream() { - const stream = new PassThrough(); - mockedFsOpen.mockResolvedValueOnce({ - createWriteStream: () => stream as any, - close() { - stream.end(); - return this; - } - } as any); - - return new Promise((resolve, reject) => { - const data: string[] = []; - - stream.on('data', chunk => { - data.push(chunk.toString()); - }); - - stream.on('error', reject); - - stream.on('end', () => resolve(data.join(''))); - }); -} diff --git a/scripts/src/build/modules/__tests__/tab.test.ts b/scripts/src/build/modules/__tests__/tab.test.ts deleted file mode 100644 index 8f6cad2dfc..0000000000 --- a/scripts/src/build/modules/__tests__/tab.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import { testBuildCommand } from '@src/build/__tests__/testingUtils'; -import * as tabs from '../tabs'; - -jest.mock('esbuild', () => ({ - build: jest.fn() - .mockResolvedValue({ outputFiles: [] }) -})); - -jest.spyOn(tabs, 'bundleTabs'); - -testBuildCommand( - 'buildTabs', - tabs.getBuildTabsCommand, - [tabs.bundleTabs] -); - -test('Normal command', async () => { - await tabs.getBuildTabsCommand() - .parseAsync(['-t', 'tab0'], { from: 'user' }); - - expect(tabs.bundleTabs) - .toHaveBeenCalledTimes(1); - - const [args] = (tabs.bundleTabs as MockedFunction).mock.calls[0]; - expect(args) - .toMatchObject({ - tabs: ['tab0'], - }); -}); diff --git a/scripts/src/build/modules/bundles.ts b/scripts/src/build/modules/bundles.ts deleted file mode 100644 index 6dea0d6c9e..0000000000 --- a/scripts/src/build/modules/bundles.ts +++ /dev/null @@ -1,31 +0,0 @@ -import fs from 'fs/promises'; -import { build as esbuild } from 'esbuild'; -import { bundlesOption, promiseAll } from '@src/commandUtils'; -import { expandBundleNames, type BuildTask, createBuildCommandHandler, createBuildCommand } from '../utils'; -import { commonEsbuildOptions, outputBundleOrTab, jsSlangExportCheckingPlugin, assertPolyfillPlugin } from './commons'; - -export const bundleBundles: BuildTask = async ({ bundles }, { srcDir, outDir }) => { - const [{ outputFiles }] = await promiseAll(esbuild({ - ...commonEsbuildOptions, - entryPoints: expandBundleNames(srcDir, bundles), - outbase: outDir, - outdir: outDir, - plugins: [ - assertPolyfillPlugin, - jsSlangExportCheckingPlugin - ], - tsconfig: `${srcDir}/tsconfig.json` - }), fs.mkdir(`${outDir}/bundles`, { recursive: true })); - - const results = await Promise.all(outputFiles.map(file => outputBundleOrTab(file, outDir))); - return { bundles: results }; -}; - -const bundlesCommandHandler = createBuildCommandHandler((...args) => bundleBundles(...args)); - -export const getBuildBundlesCommand = () => createBuildCommand( - 'bundles', - 'Build bundles' -) - .addOption(bundlesOption) - .action(opts => bundlesCommandHandler({ ...opts, tabs: [] })); diff --git a/scripts/src/build/modules/commons.ts b/scripts/src/build/modules/commons.ts deleted file mode 100644 index 682f63127c..0000000000 --- a/scripts/src/build/modules/commons.ts +++ /dev/null @@ -1,132 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path'; -import { parse } from 'acorn'; -import { generate } from 'astring'; -import type { BuildOptions as ESBuildOptions, OutputFile, Plugin as ESBuildPlugin } from 'esbuild'; -import type { ArrowFunctionExpression, CallExpression, ExportDefaultDeclaration, Program, VariableDeclaration } from 'estree'; -import type { OperationResult } from '../utils'; - -export const commonEsbuildOptions: ESBuildOptions = { - bundle: true, - format: 'iife', - define: { - process: JSON.stringify({ - env: { - NODE_ENV: 'production' - } - }) - }, - external: ['js-slang*'], - globalName: 'module', - platform: 'browser', - target: 'es6', - write: false -}; - -export async function outputBundleOrTab({ path, text }: OutputFile, outDir: string): Promise { - const [type, name] = path.split(pathlib.sep) - .slice(-3, -1); - let file: fs.FileHandle | null = null; - try { - const parsed = parse(text, { ecmaVersion: 6 }) as unknown as Program; - - // Account for 'use strict'; directives - let declStatement: VariableDeclaration; - if (parsed.body[0].type === 'VariableDeclaration') { - declStatement = parsed.body[0]; - } else { - declStatement = parsed.body[1] as unknown as VariableDeclaration; - } - const varDeclarator = declStatement.declarations[0]; - const callExpression = varDeclarator.init as CallExpression; - const moduleCode = callExpression.callee as ArrowFunctionExpression; - - const output: ExportDefaultDeclaration = { - type: 'ExportDefaultDeclaration', - declaration: { - ...moduleCode, - params: [{ - type: 'Identifier', - name: 'require' - }] - } - }; - - file = await fs.open(`${outDir}/${type}/${name}.js`, 'w'); - const writeStream = file.createWriteStream(); - generate(output, { output: writeStream }); - return { - severity: 'success', - name - }; - } catch (error) { - return { - name, - severity: 'error', - error - }; - } finally { - await file?.close(); - } -} - -const jsslangExports = [ - 'js-slang', - 'js-slang/context', - 'js-slang/dist/cse-machine/interpreter', - 'js-slang/dist/stdlib', - 'js-slang/dist/stdlib/list', - 'js-slang/dist/stdlib/misc', - 'js-slang/dist/stdlib/stream', - 'js-slang/dist/types', - 'js-slang/dist/utils/assert', - 'js-slang/dist/utils/stringify', - 'js-slang/dist/parser/parser', -]; - -// Only the exports listed above are supported. Trying to use anything else -// in your modules or tabs will cause errors during runtime -// If you really need that functionality contact the SA leadership -export const jsSlangExportCheckingPlugin: ESBuildPlugin = { - name: 'js-slang import checker', - setup(pluginBuild) { - pluginBuild.onResolve({ filter: /^js-slang/u }, args => { - if (!jsslangExports.includes(args.path)) { - return { - errors: [{ - text: 'This export from js-slang is not supported!' - }] - }; - } - return undefined; - }); - } -}; - -export const assertPolyfillPlugin: ESBuildPlugin = { - name: 'Assert Polyfill', - setup(build) { - // Polyfill the NodeJS assert module - build.onResolve({ filter: /^assert/u }, () => ({ - path: 'assert', - namespace: 'bundleAssert' - })); - - build.onLoad({ - filter: /^assert/u, - namespace: 'bundleAssert' - }, () => ({ - contents: ` - export default function assert(condition, message) { - if (condition) return; - - if (typeof message === 'string' || message === undefined) { - throw new Error(message); - } - - throw message; - } - ` - })); - } -}; diff --git a/scripts/src/build/modules/index.ts b/scripts/src/build/modules/index.ts deleted file mode 100644 index 9df2e64290..0000000000 --- a/scripts/src/build/modules/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { bundlesOption, tabsOption } from '@src/commandUtils'; -import { createBuildCommand, type BuildTask, createBuildCommandHandler } from '../utils'; -import { bundleBundles } from './bundles'; -import { bundleTabs } from './tabs'; - -export const buildModules: BuildTask = async (inputs, opts) => { - const [bundlesResult, tabsResult] = await Promise.all([ - bundleBundles(inputs, opts), - bundleTabs(inputs, opts) - ]); - - return { - ...bundlesResult, - ...tabsResult - }; -}; - -const modulesCommandHandler = createBuildCommandHandler(buildModules); - -export const getBuildModulesCommand = () => createBuildCommand('modules', 'Build bundles and tabs') - .addOption(bundlesOption) - .addOption(tabsOption) - .action(modulesCommandHandler); - -export { getBuildBundlesCommand } from './bundles'; -export { getBuildTabsCommand } from './tabs'; diff --git a/scripts/src/build/modules/tabs.ts b/scripts/src/build/modules/tabs.ts deleted file mode 100644 index 8703f4ac99..0000000000 --- a/scripts/src/build/modules/tabs.ts +++ /dev/null @@ -1,48 +0,0 @@ -import fs from 'fs/promises'; -import { build as esbuild, type Plugin as ESBuildPlugin } from 'esbuild'; -import { promiseAll, tabsOption } from '@src/commandUtils'; -import { expandTabNames, createBuildCommandHandler, type BuildTask, createBuildCommand } from '../utils'; -import { commonEsbuildOptions, jsSlangExportCheckingPlugin, outputBundleOrTab } from './commons'; - -export const tabContextPlugin: ESBuildPlugin = { - name: 'Tab Context', - setup(build) { - build.onResolve({ filter: /^js-slang\/context/u }, () => ({ - errors: [{ - text: 'If you see this message, it means that your tab code is importing js-slang/context directly or indirectly. Do not do this' - }] - })); - } -}; - -export const bundleTabs: BuildTask = async ({ tabs }, { srcDir, outDir }) => { - const [{ outputFiles }] = await promiseAll(esbuild({ - ...commonEsbuildOptions, - entryPoints: expandTabNames(srcDir, tabs), - external: [ - ...commonEsbuildOptions.external, - 'react', - 'react-ace', - 'react-dom', - 'react/jsx-runtime', - '@blueprintjs/*' - // 'phaser', - ], - jsx: 'automatic', - outbase: outDir, - outdir: outDir, - tsconfig: `${srcDir}/tsconfig.json`, - plugins: [ - tabContextPlugin, - jsSlangExportCheckingPlugin - ] - }), fs.mkdir(`${outDir}/tabs`, { recursive: true })); - - const results = await Promise.all(outputFiles.map(file => outputBundleOrTab(file, outDir))); - return { tabs: results }; -}; - -const tabCommandHandler = createBuildCommandHandler((...args) => bundleTabs(...args), 'bundles'); -export const getBuildTabsCommand = () => createBuildCommand('tabs', 'Build tabs') - .addOption(tabsOption) - .action(opts => tabCommandHandler({ ...opts, bundles: [] })); diff --git a/scripts/src/build/prebuild/__mocks__/lint.ts b/scripts/src/build/prebuild/__mocks__/lint.ts deleted file mode 100644 index eac3ba04fa..0000000000 --- a/scripts/src/build/prebuild/__mocks__/lint.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const runEslint = jest.fn() - .mockImplementation(() => ({ - elapsed: 0, - result: { - formatted: '', - severity: 'error' - } - })); - -export const eslintResultsLogger = jest.fn(() => ''); diff --git a/scripts/src/build/prebuild/__mocks__/tsc.ts b/scripts/src/build/prebuild/__mocks__/tsc.ts deleted file mode 100644 index 18f7fd9a6e..0000000000 --- a/scripts/src/build/prebuild/__mocks__/tsc.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const tscResultsLogger = jest.fn(() => ''); - -export const runTsc = jest.fn() - .mockResolvedValue({ - elapsed: 0, - result: { - severity: 'error', - results: [] - } - }); diff --git a/scripts/src/build/prebuild/__tests__/lint.test.ts b/scripts/src/build/prebuild/__tests__/lint.test.ts deleted file mode 100644 index ef51354c91..0000000000 --- a/scripts/src/build/prebuild/__tests__/lint.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getLintCommand } from '../lint'; - -const mockedLinter = jest.fn(); - -jest.mock('eslint', () => ({ - loadESLint: () => { - return class { - public fix: boolean; - constructor({ fix }: { fix: boolean }) { - this.fix = fix; - } - - lintFiles = mockedLinter; - loadFormatter() { - return Promise.resolve({ - format: () => Promise.resolve('') - }); - } - - static outputFixes() { - return Promise.resolve(); - } - }; - } -})); - -function runCommand(...args: string[]) { - return getLintCommand().parseAsync(args, { from: 'user' }); -} - -describe('test runEslint', () => { - test('Fixable errors without fix should still cause errors', () => { - mockedLinter.mockResolvedValueOnce([{ fatalErrorCount: 0, errorCount: 1 }]); - return expect(runCommand()) - .rejects - .toMatchInlineSnapshot('[Error: process.exit called with 1]'); - }); - - test('Fixable errors with fix should not cause errors', () => { - mockedLinter.mockResolvedValueOnce([{ fatalErrorCount: 0, errorCount: 1 }]); - return expect(runCommand('--fix')) - .resolves - .not.toThrow(); - }); - - test('Unfixable errors without fix should cause errors', () => { - mockedLinter.mockResolvedValueOnce([{ fatalErrorCount: 1, errorCount: 0 }]); - return expect(runCommand()) - .rejects - .toMatchInlineSnapshot('[Error: process.exit called with 1]'); - }); - - test('Unfixable errors with fix should cause errors', () => { - mockedLinter.mockResolvedValueOnce([{ fatalErrorCount: 1, errorCount: 0 }]); - return expect(runCommand('--fix')) - .rejects - .toMatchInlineSnapshot('[Error: process.exit called with 1]'); - }); -}); diff --git a/scripts/src/build/prebuild/__tests__/prebuild.test.ts b/scripts/src/build/prebuild/__tests__/prebuild.test.ts deleted file mode 100644 index 934883a486..0000000000 --- a/scripts/src/build/prebuild/__tests__/prebuild.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; - -import * as lintModule from '../lint'; -import * as tscModule from '../tsc'; - -jest.spyOn(lintModule, 'runEslint'); -jest.spyOn(tscModule, 'runTsc'); - -const asMock = any>(func: T) => func as MockedFunction; -const mockedTsc = asMock(tscModule.runTsc); -const mockedEslint = asMock(lintModule.runEslint); - -describe('test eslint command', () => { - const runCommand = async (...args: string[]) => { - await lintModule.getLintCommand() - .parseAsync(args, { from: 'user' }); - }; - - test('regular command function', async () => { - mockedEslint.mockResolvedValueOnce({ - elapsed: 0, - result: { - formatted: '', - severity: 'success' - } - }); - - await runCommand(); - - expect(lintModule.runEslint) - .toHaveBeenCalledTimes(1); - }); - - it('should only lint specified bundles and tabs', async () => { - mockedEslint.mockResolvedValueOnce({ - elapsed: 0, - result: { - formatted: '', - severity: 'success' - } - }); - - await runCommand('-b', 'test0', '-t', 'tab0'); - - expect(lintModule.runEslint) - .toHaveBeenCalledTimes(1); - - const [lintCall] = mockedEslint.mock.calls[0]; - expect(lintCall) - .toMatchObject({ - bundles: ['test0'], - tabs: ['tab0'], - srcDir: 'src' - }); - }); - - it('should exit with code 1 if there are linting errors', async () => { - mockedEslint.mockResolvedValueOnce({ - elapsed: 0, - result: { - formatted: '', - severity: 'error' - } - }); - - try { - await runCommand(); - } catch (error) { - expect(error) - .toEqual(new Error('process.exit called with 1')); - } - expect(lintModule.runEslint) - .toHaveBeenCalledTimes(1); - - expect(process.exit) - .toHaveBeenCalledWith(1); - }); -}); - -describe('test tsc command', () => { - const runCommand = (...args: string[]) => tscModule.getTscCommand() - .parseAsync(args, { from: 'user' }); - - test('regular command function', async () => { - mockedTsc.mockResolvedValueOnce({ - elapsed: 0, - result: { - results: [], - severity: 'success' - } - }); - - await runCommand(); - - expect(tscModule.runTsc) - .toHaveBeenCalledTimes(1); - }); - - it('should only typecheck specified bundles and tabs', async () => { - mockedTsc.mockResolvedValueOnce({ - elapsed: 0, - result: { - results: [], - severity: 'success' - } - }); - - await runCommand('-b', 'test0', '-t', 'tab0'); - - expect(tscModule.runTsc) - .toHaveBeenCalledTimes(1); - - const [tscCall] = mockedTsc.mock.calls[0]; - expect(tscCall) - .toMatchObject({ - bundles: ['test0'], - tabs: ['tab0'], - srcDir: 'src' - }); - }); - - it('should exit with code 1 if there are type check errors', async () => { - mockedTsc.mockResolvedValueOnce({ - elapsed: 0, - result: { - results: [], - severity: 'error' - } - }); - - try { - await runCommand(); - } catch (error) { - expect(error) - .toEqual(new Error('process.exit called with 1')); - } - - expect(tscModule.runTsc) - .toHaveBeenCalledTimes(1); - - expect(process.exit) - .toHaveBeenCalledWith(1); - }); -}); diff --git a/scripts/src/build/prebuild/index.ts b/scripts/src/build/prebuild/index.ts deleted file mode 100644 index d8c4832b86..0000000000 --- a/scripts/src/build/prebuild/index.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { type Severity, findSeverity, type BuildOptions } from '@src/build/utils'; -import { promiseAll } from '@src/commandUtils'; -import { eslintResultsLogger, runEslint } from './lint'; -import { runTsc, tscResultsLogger } from './tsc'; - -interface PrebuildResult { - lint?: Awaited> - tsc?: Awaited> - severity: Severity -} - -export default async function prebuild( - bundles: string[], - tabs: string[], - { tsc, lint, ...opts }: Pick -): Promise { - const combinedOpts = { - ...opts, - bundles, - tabs - }; - - if (tsc) { - if (!lint) { - const tsc = await runTsc(combinedOpts); - return { - tsc, - severity: tsc.result.severity - }; - } - - const [tscResult, lintResult] = await promiseAll( - runTsc(combinedOpts), - runEslint(combinedOpts) - ); - - const overallSev = findSeverity([tscResult, lintResult], ({ result: { severity } }) => severity); - - return { - tsc: tscResult, - lint: lintResult, - severity: overallSev - }; - } - - if (lint) { - const lintResult = await runEslint(combinedOpts); - return { - lint: lintResult, - severity: lintResult.result.severity - }; - } - return null; -} - -export function formatPrebuildResults(results: PrebuildResult) { - const output: string[] = []; - if (results.tsc) { - output.push(tscResultsLogger(results.tsc)); - } - - if (results.lint) { - const lintResult = eslintResultsLogger(results.lint); - output.push(lintResult); - } - - return output.length > 0 ? output.join('\n') : null; -} diff --git a/scripts/src/build/prebuild/lint.ts b/scripts/src/build/prebuild/lint.ts deleted file mode 100644 index 128fba69b9..0000000000 --- a/scripts/src/build/prebuild/lint.ts +++ /dev/null @@ -1,74 +0,0 @@ -import chalk from 'chalk'; -import { loadESLint, type ESLint } from 'eslint'; -import { lintFixOption, retrieveBundlesAndTabs, wrapWithTimer } from '@src/commandUtils'; -import { divideAndRound, findSeverity, type AwaitedReturn, type Severity } from '../utils'; -import { createPrebuildCommand, createPrebuildCommandHandler, type PrebuildOptions } from './utils'; - -interface LintResults { - formatted: string - severity: Severity -} - -interface LintOptions extends Omit { - fix?: boolean -} - -export const runEslint = wrapWithTimer(async ({ bundles, tabs, srcDir, fix }: LintOptions): Promise => { - const ESlint = await loadESLint({ useFlatConfig: true }); - const linter: ESLint = new ESlint({ fix }); - - const fileNames = [ - ...bundles.map(bundleName => `${srcDir}/bundles/${bundleName}/**/*.ts`), - ...tabs.map(tabName => `${srcDir}/tabs/${tabName}/**/*.ts*`) - ]; - - try { - const linterResults = await linter.lintFiles(fileNames); - if (fix) { - await ESlint.outputFixes(linterResults); - } - - const outputFormatter = await linter.loadFormatter('stylish'); - const formatted = await outputFormatter.format(linterResults); - const severity = findSeverity(linterResults, ({ warningCount, errorCount, fatalErrorCount }) => { - - if (!fix && (fatalErrorCount + errorCount) > 0) return 'error'; - if (fix && fatalErrorCount > 0) { - return 'error'; - } - if (warningCount > 0) return 'warn'; - return 'success'; - }); - - return { - formatted, - severity - }; - } catch (error) { - return { - severity: 'error', - formatted: error.toString() - }; - } -}); - -export function eslintResultsLogger({ elapsed, result: { formatted, severity } }: AwaitedReturn) { - let errStr: string; - - if (severity === 'error') errStr = chalk.cyanBright('with ') + chalk.redBright('errors'); - else if (severity === 'warn') errStr = chalk.cyanBright('with ') + chalk.yellowBright('warnings'); - else errStr = chalk.greenBright('successfully'); - - return `${chalk.cyanBright(`Linting completed in ${divideAndRound(elapsed, 1000)}s ${errStr}:`)}\n${formatted}`; -} - -const lintCommandHandler = createPrebuildCommandHandler((...args) => runEslint(...args), eslintResultsLogger); - -export function getLintCommand() { - return createPrebuildCommand('lint', 'Run eslint') - .addOption(lintFixOption) - .action(async opts => { - const inputs = await retrieveBundlesAndTabs(opts.manifest, opts.bundles, opts.tabs, false); - await lintCommandHandler({ ...opts, ...inputs }); - }); -} diff --git a/scripts/src/build/prebuild/tsc.ts b/scripts/src/build/prebuild/tsc.ts deleted file mode 100644 index 0e7e9d3102..0000000000 --- a/scripts/src/build/prebuild/tsc.ts +++ /dev/null @@ -1,125 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path'; -import chalk from 'chalk'; -import ts from 'typescript'; -import { retrieveBundlesAndTabs, wrapWithTimer } from '@src/commandUtils'; -import { expandBundleNames, expandTabNames, divideAndRound, type AwaitedReturn } from '../utils'; -import { createPrebuildCommand, createPrebuildCommandHandler, type PrebuildOptions } from './utils'; - -type TsconfigResult = { - severity: 'error', - results?: ts.Diagnostic[] - error?: any -} | { - severity: 'success', - results: ts.CompilerOptions -}; - -type TscResult = { - severity: 'error' - results?: ts.Diagnostic[] - error?: any -} | { - severity: 'success', - results: ts.Diagnostic[] -}; - -async function getTsconfig(srcDir: string): Promise { - // Step 1: Read the text from tsconfig.json - const tsconfigLocation = pathlib.join(srcDir, 'tsconfig.json'); - try { - const configText = await fs.readFile(tsconfigLocation, 'utf-8'); - - // Step 2: Parse the raw text into a json object - const { error: configJsonError, config: configJson } = ts.parseConfigFileTextToJson(tsconfigLocation, configText); - if (configJsonError) { - return { - severity: 'error', - results: [configJsonError] - }; - } - - // Step 3: Parse the json object into a config object for use by tsc - const { errors: parseErrors, options: tsconfig } = ts.parseJsonConfigFileContent(configJson, ts.sys, srcDir); - if (parseErrors.length > 0) { - return { - severity: 'error', - results: parseErrors - }; - } - - return { - severity: 'success', - results: tsconfig - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -} - -export const runTsc = wrapWithTimer(async ({ bundles, tabs, srcDir }: Omit): Promise => { - const tsconfigRes = await getTsconfig(srcDir); - if (tsconfigRes.severity === 'error') { - return tsconfigRes; - } - - const fileNames: string[] = []; - - if (bundles.length > 0) { - expandBundleNames(srcDir, bundles) - .forEach(name => fileNames.push(name)); - } - - if (tabs.length > 0) { - expandTabNames(srcDir, tabs) - .forEach(name => fileNames.push(name)); - } - - try { - const tsc = ts.createProgram(fileNames, tsconfigRes.results); - const results = tsc.emit(); - const diagnostics = ts.getPreEmitDiagnostics(tsc) - .concat(results.diagnostics); - - return { - severity: diagnostics.length > 0 ? 'error' : 'success', - results: diagnostics - }; - } catch (error) { - return { - severity: 'error', - error - }; - } -}); - -export function tscResultsLogger({ elapsed, result: tscResult }: AwaitedReturn) { - if (tscResult.severity === 'error' && tscResult.error) { - return `${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')} in ${divideAndRound(elapsed, 1000)}s: ${tscResult.error}`)}`; - } - - const diagStr = ts.formatDiagnosticsWithColorAndContext(tscResult.results, { - getNewLine: () => '\n', - getCurrentDirectory: () => process.cwd(), - getCanonicalFileName: name => pathlib.basename(name) - }); - - if (tscResult.severity === 'error') { - return `${diagStr}\n${chalk.cyanBright(`tsc finished with ${chalk.redBright('errors')} in ${divideAndRound(elapsed, 1000)}s`)}`; - } - return `${diagStr}\n${chalk.cyanBright(`tsc completed ${chalk.greenBright('successfully')} in ${divideAndRound(elapsed, 1000)}s`)}`; -} - -const tscCommandHandler = createPrebuildCommandHandler( - (...args) => runTsc(...args), - tscResultsLogger -); - -export const getTscCommand = () => createPrebuildCommand('tsc', 'Run the typescript compiler to perform type checking') - .action(async opts => { - const inputs = await retrieveBundlesAndTabs(opts.manifest, opts.bundles, opts.tabs, true); - await tscCommandHandler({ ...opts, ...inputs }); - }); diff --git a/scripts/src/build/prebuild/utils.ts b/scripts/src/build/prebuild/utils.ts deleted file mode 100644 index 4ec2edfbf5..0000000000 --- a/scripts/src/build/prebuild/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Command } from '@commander-js/extra-typings'; -import { bundlesOption, manifestOption, srcDirOption, tabsOption, type TimedResult } from '@src/commandUtils'; -import { logInputs, type Severity } from '../utils'; - -export interface PrebuildOptions { - srcDir: string - manifest: string - bundles: string[] - tabs: string[] -} - -export type PrebuildResult = TimedResult; - -export function createPrebuildCommand( - commandName: string, - description: string -) { - return new Command(commandName) - .description(description) - .addOption(srcDirOption) - .addOption(manifestOption) - .addOption(bundlesOption) - .addOption(tabsOption); -} - -export function createPrebuildCommandHandler( - func: (opts: PrebuildOptions) => Promise>, - resultsProcessor: (results: PrebuildResult) => string -) { - return async (opts: PrebuildOptions) => { - console.log(logInputs(opts, {})); - const results = await func(opts); - const toLog = resultsProcessor(results); - - console.log(toLog); - if (results.result.severity === 'error') process.exit(1); - }; -} diff --git a/scripts/src/build/utils.ts b/scripts/src/build/utils.ts deleted file mode 100644 index 439b153ea8..0000000000 --- a/scripts/src/build/utils.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { copyFile } from 'fs/promises'; -import { Command } from '@commander-js/extra-typings'; -import chalk from 'chalk'; -import { Table } from 'console-table-printer'; -import type { BuildInputs } from '@src/commandUtils'; -import { lintFixOption, lintOption, manifestOption, objectEntries, outDirOption, retrieveBundles, retrieveBundlesAndTabs, retrieveTabs, srcDirOption } from '@src/commandUtils'; -import { htmlLogger, type buildHtml } from './docs/html'; -import prebuild, { formatPrebuildResults } from './prebuild'; - -export interface BuildOptions { - srcDir: string - outDir: string - manifest: string - lint?: boolean - fix?: boolean - tsc?: boolean - verbose?: boolean -} - -export interface SuccessResult { - name: string - severity: 'success', -} - -export interface WarnResult { - name: string, - severity: 'warn', - error: any -} - -export interface ErrorResult { - name: string, - severity: 'error', - error: any -} - -export type OperationResult = ErrorResult | SuccessResult | WarnResult; -export type Severity = OperationResult['severity']; - -export const isSuccessResult = (obj: OperationResult): obj is SuccessResult => obj.severity === 'success'; -export const isWarnResult = (obj: OperationResult): obj is WarnResult => obj.severity === 'warn'; - -export function findSeverity(results: T[], mapper?: (item: T) => Severity): Severity { - let overallSev: Severity = 'success'; - - for (const result of results) { - let severity: Severity; - if ('severity' in result) { - severity = result.severity as Severity; - } else { - if (!mapper) throw new Error(`Mapping function required to convert ${result} to severity`); - severity = mapper(result); - } - - if (severity === 'error') return 'error'; - if (severity === 'warn') { - overallSev = 'warn'; - } - } - - return overallSev; -} - -export const expandBundleNames = (srcDir: string, bundles: string[]) => bundles.map(bundle => `${srcDir}/bundles/${bundle}/index.ts`); -export const expandTabNames = (srcDir: string, tabNames: string[]) => tabNames.map(tabName => `${srcDir}/tabs/${tabName}/index.tsx`); - -export type AwaitedReturn = T extends (...args: any) => Promise ? U : never; - -export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFixed(2); - -type AssetType = 'bundles' | 'jsons' | 'tabs'; -type LogType = Partial & { html: Awaited> }>; - -export type BuildTask = (inputs: BuildInputs, opts: BuildOptions) => Promise; - -/** - * Take the results from all the operations and format them neatly in a readable way - * Also calls `process.exit(1)` if any operation returned with an error if `exitOnError` - * is true - */ -export function processResults(results: LogType, verbose: boolean, exitOnError: true): Exclude; -export function processResults(results: LogType, verbose: boolean, exitOnError: false): Severity; -export function processResults( - results: LogType, - verbose: boolean, - exitOnError: boolean -) { - const notSuccessFilter = (result: OperationResult): result is Exclude => result.severity !== 'success'; - - const logs = objectEntries(results) - .map(([label, results]): [Severity, string] => { - if (label === 'html') { - return [results.result.severity, htmlLogger(results)]; - } - - const overallSev = findSeverity(results); - const upperCaseLabel = label[0].toUpperCase() + label.slice(1); - if (!verbose) { - if (overallSev === 'success') { - return ['success', `${chalk.cyanBright(`${upperCaseLabel} built`)} ${chalk.greenBright('successfully')}\n`]; - } - if (overallSev === 'warn') { - return ['warn', chalk.cyanBright(`${upperCaseLabel} built with ${chalk.yellowBright('warnings')}:\n${results - .filter(isWarnResult) - .map(({ name: bundle, error }, i) => chalk.yellowBright(`${i + 1}. ${bundle}: ${error}`)) - .join('\n')}\n`)]; - } - - return ['error', chalk.cyanBright(`${upperCaseLabel} build ${chalk.redBright('failed')} with errors:\n${results - .filter(notSuccessFilter) - .map(({ name: bundle, error, severity }, i) => (severity === 'error' - ? chalk.redBright(`${i + 1}. Error ${bundle}: ${error}`) - : chalk.yellowBright(`${i + 1}. Warning ${bundle}: ${error}`))) - .join('\n')}\n`)]; - } - - const outputTable = new Table({ - columns: [{ - name: 'name', - title: upperCaseLabel - }, - { - name: 'severity', - title: 'Status' - }, - { - name: 'error', - title: 'Errors' - }] - }); - results.forEach(result => { - if (isWarnResult(result)) { - outputTable.addRow({ - ...result, - severity: 'Warning' - }, { color: 'yellow' }); - } else if (isSuccessResult(result)) { - outputTable.addRow({ - ...result, - error: '-', - severity: 'Success' - }, { color: 'green' }); - } else { - outputTable.addRow({ - ...result, - severity: 'Error' - }, { color: 'red' }); - } - }); - - if (overallSev === 'success') { - return ['success', `${chalk.cyanBright(`${upperCaseLabel} built`)} ${chalk.greenBright('successfully')}:\n${outputTable.render()}\n`]; - } - if (overallSev === 'warn') { - return ['warn', `${chalk.cyanBright(`${upperCaseLabel} built`)} with ${chalk.yellowBright('warnings')}:\n${outputTable.render()}\n`]; - } - return ['error', `${chalk.cyanBright(`${upperCaseLabel} build ${chalk.redBright('failed')} with errors`)}:\n${outputTable.render()}\n`]; - }); - - console.log(logs.map(x => x[1]) - .join('\n')); - - const overallOverallSev = findSeverity(logs, ([sev]) => sev); - if (overallOverallSev === 'error' && exitOnError) { - process.exit(1); - } - return overallOverallSev; -} - -export function logInputs( - { bundles, tabs }: BuildInputs, - { tsc, lint }: Partial>, - ignore?: 'bundles' | 'tabs' -) { - const output: string[] = []; - if (tsc) { - output.push(chalk.yellowBright('--tsc specified, will run typescript checker')); - } - - if (lint) { - output.push(chalk.yellowBright('Linting specified, will run ESlint')); - } - - if (ignore !== 'bundles' && bundles.length > 0) { - output.push(chalk.magentaBright('Processing the following bundles:')); - bundles.forEach((bundle, i) => output.push(`${i + 1}. ${bundle}`)); - } - - if (ignore !== 'tabs' && tabs.length > 0 ) { - output.push(chalk.magentaBright('Processing the following tabs:')); - tabs.forEach((tab, i) => output.push(`${i + 1}. ${tab}`)); - } - - return output.join('\n'); -} - -type CommandHandler = (opts: BuildOptions & { bundles?: string[] | null, tabs?: string[] | null }) => Promise; - -export function createBuildCommandHandler(func: BuildTask, ignore?: 'bundles' | 'tabs'): CommandHandler { - return async opts => { - let inputs: BuildInputs; - - switch (ignore) { - case 'bundles': { - inputs = await retrieveTabs(opts.manifest, opts.tabs); - break; - } - case 'tabs': { - inputs = await retrieveBundles(opts.manifest, opts.bundles); - break; - } - case undefined: { - inputs = await retrieveBundlesAndTabs(opts.manifest, opts.bundles, opts.tabs); - break; - } - } - - // Log all inputs - console.log(logInputs(inputs, opts, ignore)); - // Then run prebuilds. This will return null if no prebuild was specified - const prebuildResult = await prebuild(inputs.bundles, inputs.tabs, { - lint: opts.lint, - fix: opts.fix, - tsc: opts.tsc, - srcDir: opts.srcDir - }); - - if (prebuildResult !== null) { - const prebuildResultFormatted = formatPrebuildResults(prebuildResult); - console.log(prebuildResultFormatted); - - // If there was some error, then exit without running the main command - if (prebuildResult.severity === 'error') process.exit(1); - } - - const result = await func(inputs, opts); - processResults(result, opts.verbose, true); - await copyFile(opts.manifest, `${opts.outDir}/modules.json`); - }; -} - -export function createBuildCommand( - commandName: string, - description: string -) { - return new Command(commandName) - .description(description) - .addOption(srcDirOption) - .addOption(outDirOption) - .addOption(lintOption) - .addOption(lintFixOption) - .addOption(manifestOption) - .option('--tsc', 'Run tsc before building'); -} diff --git a/scripts/src/build/watch.ts b/scripts/src/build/watch.ts deleted file mode 100644 index 67bb9b86e1..0000000000 --- a/scripts/src/build/watch.ts +++ /dev/null @@ -1,130 +0,0 @@ -import fs from 'fs/promises'; -import { Command } from '@commander-js/extra-typings'; -import chalk from 'chalk'; -import { context as esbuild, type BuildContext } from 'esbuild'; -import { manifestOption, outDirOption, promiseAll, retrieveBundlesAndTabs, srcDirOption } from '@src/commandUtils'; -import { buildHtml, buildJsons } from './docs'; -import { initTypedoc } from './docs/docsUtils'; -import { assertPolyfillPlugin, commonEsbuildOptions, jsSlangExportCheckingPlugin, outputBundleOrTab } from './modules/commons'; -import { tabContextPlugin } from './modules/tabs'; -import { expandBundleNames, expandTabNames, logInputs, processResults } from './utils'; - -/** - * Wait until the user presses 'ctrl+c' on the keyboard - */ -const waitForQuit = () => new Promise((resolve, reject) => { - process.stdin.setRawMode(true); - process.stdin.on('data', data => { - const byteArray = [...data]; - if (byteArray.length > 0 && byteArray[0] === 3) { - console.log('^C'); - process.stdin.setRawMode(false); - resolve(); - } - }); - process.stdin.on('error', reject); -}); - -export default function getWatchCommand() { - return new Command('watch') - .description('Watch the source directory and rebuild on changes') - .addOption(srcDirOption) - .addOption(outDirOption) - .addOption(manifestOption) - .option('-v, --verbose') - .action(async opts => { - const [inputs] = await promiseAll( - retrieveBundlesAndTabs(opts.manifest, null, null), - fs.mkdir(opts.outDir).then(() => fs.copyFile(opts.manifest, `${opts.outDir}/modules.json`)) - ); - console.log(logInputs(inputs, {})); - - let bundlesContext: BuildContext | null = null; - let tabsContext: BuildContext | null = null; - - try { - await promiseAll( - fs.mkdir(`${opts.outDir}/bundles`), - fs.mkdir(`${opts.outDir}/tabs`), - fs.mkdir(`${opts.outDir}/jsons`), - ) - - ;([bundlesContext, tabsContext] = await promiseAll( - esbuild({ - ...commonEsbuildOptions, - entryPoints: expandBundleNames(opts.srcDir, inputs.bundles), - outbase: opts.outDir, - outdir: opts.outDir, - plugins: [ - assertPolyfillPlugin, - jsSlangExportCheckingPlugin, - { - name: 'Bundles output', - setup(build) { - build.onStart(() => { - console.log(chalk.magentaBright('Beginning bundles build...')); - }); - build.onEnd(async ({ outputFiles }) => { - const rawResults = await Promise.all(outputFiles.map(file => outputBundleOrTab(file, opts.outDir))); - processResults({ bundles: rawResults }, opts.verbose, false); - }); - } - } - ], - tsconfig: `${opts.srcDir}/tsconfig.json` - }), - esbuild({ - ...commonEsbuildOptions, - entryPoints: expandTabNames(opts.srcDir, inputs.tabs), - outbase: opts.outDir, - outdir: opts.outDir, - plugins: [ - tabContextPlugin, - jsSlangExportCheckingPlugin, - { - name: 'Tabs output', - setup(build) { - build.onStart(() => { - console.log(chalk.magentaBright('Beginning tabs build...')); - }); - - build.onEnd(async ({ outputFiles }) => { - const rawResults = await Promise.all(outputFiles.map(file => outputBundleOrTab(file, opts.outDir))); - processResults({ tabs: rawResults }, opts.verbose, false); - }); - }, - }], - tsconfig: `${opts.srcDir}/tsconfig.json` - }), - initTypedoc(inputs.bundles, opts.srcDir, opts.verbose, true).then(([, app]) => { - app.convertAndWatch(async proj => { - const [jsonResults, htmlResult] = await promiseAll( - buildJsons(inputs, opts.outDir, proj), - buildHtml(inputs, opts.outDir, [proj, app]) - ); - - processResults({ - ...jsonResults, - html: htmlResult - }, opts.verbose, false); - }); - }))); - - console.log(chalk.yellowBright(`Watching ${chalk.cyanBright(`./${opts.srcDir}`)} for changes\nPress CTRL + C to stop`)); - await waitForQuit(); - - console.log(chalk.yellowBright('Quitting!')); - await promiseAll( - bundlesContext.cancel(), - tabsContext.cancel(), - ); - } finally { - await promiseAll( - bundlesContext?.dispose(), - tabsContext?.dispose(), - ); - } - // So that the typedoc watcher stops running - process.exit(); - }); -} diff --git a/scripts/src/commandUtils.ts b/scripts/src/commandUtils.ts deleted file mode 100644 index 8ae4498ef8..0000000000 --- a/scripts/src/commandUtils.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { Option } from '@commander-js/extra-typings'; -import type { AwaitedReturn } from './build/utils'; -import { retrieveManifest } from './manifest'; - -class OptionNew< - UsageT extends string = '', - PresetT = undefined, - DefaultT = undefined, - CoerceT = undefined, - Mandatory extends boolean = false, - ChoicesT = undefined -> - extends Option { - default(value: T, description?: string): Option { - return super.default(value, description); - } -} - -export const srcDirOption = new OptionNew('--srcDir ', 'Location of the source files') - .default('src'); - -export const outDirOption = new OptionNew('--outDir ', 'Location of output directory') - .default('build'); - -export const manifestOption = new OptionNew('--manifest ', 'Location of manifest') - .default('modules.json'); - -export const lintOption = new OptionNew('--lint', 'Run ESLint'); - -export const lintFixOption = new OptionNew('--fix', 'Fix automatically fixable linting errors') - .implies({ lint: true }); - -export const bundlesOption = new OptionNew('-b, --bundles ', 'Manually specify which bundles') - .default(null); - -export const tabsOption = new OptionNew('-t, --tabs ', 'Manually specify which tabs') - .default(null); - -export function promiseAll[]>(...args: T): Promise<{ [K in keyof T]: Awaited }> { - return Promise.all(args); -} - -export interface TimedResult { - result: T - elapsed: number -} - -export function wrapWithTimer Promise>(func: T) { - return async (...args: Parameters): Promise>> => { - const startTime = performance.now(); - const result = await func(...args); - return { - result, - elapsed: performance.now() - startTime - }; - }; -} - -type ValuesOfRecord = T extends Record ? U : never; - -export type EntriesOfRecord> = ValuesOfRecord<{ - [K in keyof T]: [K, T[K]] -}>; - -export function objectEntries>(obj: T) { - return Object.entries(obj) as EntriesOfRecord[]; -} - -export interface BuildInputs { - bundles?: string[] | null; - tabs?: string[] | null; - modulesSpecified?: boolean; -} - -/** - * Determines which bundles and tabs to build based on the user's input. - * - * If no modules and no tabs are specified, it is assumed the user wants to - * build everything. - * - * If modules but no tabs are specified, it is assumed the user only wants to - * build those bundles (and possibly those modules' tabs based on - * shouldAddModuleTabs). - * - * If tabs but no modules are specified, it is assumed the user only wants to - * build those tabs. - * - * If both modules and tabs are specified, both of the above apply and are - * combined. - * - * @param modules module names specified by the user - * @param tabOptions tab names specified by the user - * @param shouldAddModuleTabs whether to also automatically include the tabs of - * specified modules - */ -export const retrieveBundlesAndTabs = async ( - manifestFile: string, - modules: string[] | null | undefined, - tabOptions: string[] | null | undefined, - shouldAddModuleTabs: boolean = true, -): Promise => { - const manifest = await retrieveManifest(manifestFile); - const knownBundles = Object.keys(manifest); - const knownTabs = Object - .values(manifest) - .flatMap(x => x.tabs); - - let bundles: string[] = []; - let tabs: string[] = []; - - const isNullOrUndefined = (x: T): x is Exclude => x === undefined || x === null; - - function addSpecificModules() { - // If unknown modules were specified, error - const unknownModules = modules.filter(m => !knownBundles.includes(m)); - if (unknownModules.length > 0) { - throw new Error(`Unknown bundles: ${unknownModules.join(', ')}`); - } - - bundles = bundles.concat(modules); - - if (shouldAddModuleTabs) { - // Add the modules' tabs too - tabs = [...tabs, ...modules.flatMap(bundle => manifest[bundle].tabs)]; - } - } - function addSpecificTabs() { - // If unknown tabs were specified, error - const unknownTabs = tabOptions.filter(t => !knownTabs.includes(t)); - if (unknownTabs.length > 0) { - throw new Error(`Unknown tabs: ${unknownTabs.join(', ')}`); - } - - tabs = tabs.concat(tabOptions); - } - function addAllBundles() { - bundles = bundles.concat(knownBundles); - } - function addAllTabs() { - tabs = tabs.concat(knownTabs); - } - - if (isNullOrUndefined(modules) && isNullOrUndefined(tabOptions)) { - addAllBundles(); - addAllTabs(); - } else { - if (modules !== null) addSpecificModules(); - if (tabOptions !== null) addSpecificTabs(); - } - - return { - bundles: [...new Set(bundles)], - tabs: [...new Set(tabs)], - modulesSpecified: !isNullOrUndefined(modules) - }; -}; - -export async function retrieveBundles(manifestFile: string, bundles: string[] | null): Promise { - const manifest = await retrieveManifest(manifestFile); - const knownBundles = Object.keys(manifest); - - if (bundles === null) { - return { - bundles: knownBundles, - modulesSpecified: false - }; - } - const unknownModules = bundles.filter(m => !knownBundles.includes(m)); - if (unknownModules.length > 0) { - throw new Error(`Unknown bundles: ${unknownModules.join(', ')}`); - } - return { - bundles: [...new Set(bundles)], - modulesSpecified: true - }; -} - -export async function retrieveTabs(manifestFile: string, tabs: string[] | null): Promise { - const manifest = await retrieveManifest(manifestFile); - const knownTabs = Object.values(manifest).flatMap(each => each.tabs); - - if (tabs === null) { - return { tabs: knownTabs }; - } - - const unknownTabs = tabs.filter(t => !knownTabs.includes(t)); - if (unknownTabs.length > 0) { - throw new Error(`Unknown tabs: ${unknownTabs.join(', ')}`); - } - - return { tabs: [...new Set(tabs) ] }; -} diff --git a/scripts/src/index.ts b/scripts/src/index.ts deleted file mode 100644 index b62a027a96..0000000000 --- a/scripts/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Command } from '@commander-js/extra-typings'; -import getBuildCommand from './build'; -import { getLintCommand } from './build/prebuild/lint'; -import { getTscCommand } from './build/prebuild/tsc'; -import getTemplateCommand from './templates'; -import getTestCommand from './testing'; - -await new Command('scripts') - .addCommand(getBuildCommand()) - .addCommand(getLintCommand()) - .addCommand(getTestCommand()) - .addCommand(getTscCommand()) - .addCommand(getTemplateCommand()) - .parseAsync(); diff --git a/scripts/src/linting/__tests__/typeimports.test.ts b/scripts/src/linting/__tests__/typeimports.test.ts deleted file mode 100644 index cf4d721fa3..0000000000 --- a/scripts/src/linting/__tests__/typeimports.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { RuleTester } from 'eslint'; -import typeImportsPlugin from '../typeimports'; - -describe('Test collateTypeImports', () => { - const tester = new RuleTester({ - 'languageOptions': { - // eslint-disable-next-line @typescript-eslint/no-require-imports - parser: require('@typescript-eslint/parser'), - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } - }, - }); - - tester.run( - 'collate-type-imports', - typeImportsPlugin.rules['collate-type-imports'], - { - valid: [ - 'import type { a, b } from "wherever"', - '', - 'import { type a, b } from "wherever"', - 'import { a, b } from "wherever"', - 'import a, { type b } from "wherever"', - 'import type { a as b } from "wherever"', - 'import { type a as b, c } from "wherever"', - 'import "wherever"', - ], - invalid: [{ - code: 'import { type a, type b } from "wherever"', - errors: 1, - output: 'import type { a, b } from \'wherever\'' - }, { - code: 'import { type a } from "wherever"', - errors: 1, - output: 'import type { a } from \'wherever\'' - }, { - code: 'import { type a as b } from "wherever"', - errors: 1, - output: "import type { a as b } from 'wherever'" - }] - } - ); -}); diff --git a/scripts/src/linting/typeimports.ts b/scripts/src/linting/typeimports.ts deleted file mode 100644 index 9070e6b85e..0000000000 --- a/scripts/src/linting/typeimports.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ESLint } from 'eslint'; -import type es from 'estree'; - -function isImportSpecifier(spec: es.ImportDeclaration['specifiers'][number]): spec is es.ImportSpecifier { - return spec.type === 'ImportSpecifier'; -} - -function specToString(spec: es.ImportSpecifier) { - if (spec.imported.type === 'Identifier') { - if (spec.imported.name === spec.local.name) { - return spec.imported.name; - } - return `${spec.imported.name} as ${spec.local.name}`; - } - return ''; -} - -const typeImportsPlugin = { - meta: { - name: 'Type Imports Plugin', - version: '1.0.0' - }, - rules: { - 'collate-type-imports': { - meta: { - type: 'suggestion', - fixable: 'code' - }, - create: context => ({ - ImportDeclaration(node) { - if (node.importKind === 'type' || node.specifiers.length === 0) return; - - // @ts-expect-error import kind is unknown property - if (node.specifiers.some(spec => !isImportSpecifier(spec) || spec.importKind !== 'type')) return; - - context.report({ - node, - message: 'Use a single type specifier', - fix(fixer) { - const regularSpecs = (node.specifiers as es.ImportSpecifier[]).map(specToString); - - return [ - fixer.remove(node), - fixer.insertTextAfter( - node, - `import type { ${regularSpecs.join(', ')} } from '${node.source.value}'` - ) - ]; - } - }); - } - }) - } - } -} satisfies ESLint.Plugin; - -export default typeImportsPlugin; diff --git a/scripts/src/manifest.ts b/scripts/src/manifest.ts deleted file mode 100644 index 53b38a02c1..0000000000 --- a/scripts/src/manifest.ts +++ /dev/null @@ -1,13 +0,0 @@ -import fs from 'fs/promises'; - -export type ModuleManifest = Record; - -export async function retrieveManifest(manifest: string) { - try { - const rawManifest = await fs.readFile(manifest, 'utf-8'); - return JSON.parse(rawManifest) as ModuleManifest; - } catch (error) { - if (error.code === 'ENOENT') throw new Error(`Could not locate manifest file at ${manifest}`); - throw error; - } -} diff --git a/scripts/src/templates/__tests__/names.test.ts b/scripts/src/templates/__tests__/names.test.ts deleted file mode 100644 index 633cca5014..0000000000 --- a/scripts/src/templates/__tests__/names.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { isPascalCase, isSnakeCase } from '../utilities'; - -function testFunction( - func: (value: string) => boolean, - tcs: [string, boolean][] -) { - describe(`Testing ${func.name}`, () => test.each(tcs)('%#: %s', (value, valid) => expect(func(value)) - .toEqual(valid))); -} - -testFunction(isPascalCase, [ - ['PascalCase', true], - ['snake_case', false], - ['Snake_Case', false] -]); - -testFunction(isSnakeCase, [ - ['snake_case', true], - ['arcade_2d', true], - ['PascalCase', false], - ['camelCase', false] -]); diff --git a/scripts/src/templates/__tests__/template.test.ts b/scripts/src/templates/__tests__/template.test.ts deleted file mode 100644 index 28d14e2b30..0000000000 --- a/scripts/src/templates/__tests__/template.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import fs from 'fs/promises'; -import type { MockedFunction } from 'jest-mock'; -import { retrieveManifest } from '@src/manifest'; - -import getTemplateCommand from '..'; -import { askQuestion } from '../print'; - -jest.mock('../print', () => ({ - ...jest.requireActual('../print'), - askQuestion: jest.fn(), - error(x: string) { - // The command has a catch-all for errors, - // so we rethrow the error to observe the value - throw new Error(x); - }, - // Because the functions run in perpetual while loops - // We need to throw an error to observe what value warn - // was called with - warn(x: string) { - throw new Error(x); - } -})); - -const asMock = any>(func: T) => func as MockedFunction; - -const mockedAskQuestion = asMock(askQuestion); - -function runCommand(...args: string[]) { - return getTemplateCommand() - .parseAsync(args, { from: 'user' }); -} - -function expectCall any>( - func: T, - ...expected: Parameters[]) { - const mocked = asMock(func); - - expect(func) - .toHaveBeenCalledTimes(expected.length); - - mocked.mock.calls.forEach((actual, i) => { - expect(actual) - .toEqual(expected[i]); - }); -} - -async function expectCommandFailure(snapshot: string) { - await expect(runCommand()) - .rejects - // eslint-disable-next-line jest/no-interpolation-in-snapshots - .toMatchInlineSnapshot(`[Error: ERROR: ${snapshot}]`); - - expect(fs.writeFile).not.toHaveBeenCalled(); - expect(fs.copyFile).not.toHaveBeenCalled(); - expect(fs.mkdir).not.toHaveBeenCalled(); -} - -describe('Test adding new module', () => { - beforeEach(() => { - mockedAskQuestion.mockResolvedValueOnce('module'); - }); - - it('should require camel case for module names', async () => { - mockedAskQuestion.mockResolvedValueOnce('camelCase'); - await expectCommandFailure('Module names must be in snake case. (eg. binary_tree)'); - }); - - it('should check for existing modules', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - await expectCommandFailure('A module with the same name already exists.'); - }); - - test('successfully adding a new module', async () => { - mockedAskQuestion.mockResolvedValueOnce('new_module'); - await runCommand(); - - expectCall( - fs.mkdir, - ['src/bundles/new_module', { recursive: true }] - ); - - expectCall( - fs.copyFile, - [ - './scripts/src/templates/templates/__bundle__.ts', - 'src/bundles/new_module/index.ts' - ] - ); - - const oldManifest = await retrieveManifest('modules.json'); - const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; - expect(manifestPath) - .toEqual('modules.json'); - - expect(JSON.parse(newManifest as string)) - .toMatchObject({ - ...oldManifest, - new_module: { tabs: [] } - }); - }); -}); - -describe('Test adding new tab', () => { - beforeEach(() => { - mockedAskQuestion.mockResolvedValueOnce('tab'); - }); - - it('should require pascal case for tab names', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - mockedAskQuestion.mockResolvedValueOnce('unknown_tab'); - await expectCommandFailure('Tab names must be in pascal case. (eg. BinaryTree)'); - }); - - it('should check if the given tab already exists', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - mockedAskQuestion.mockResolvedValueOnce('tab0'); - await expectCommandFailure('A tab with the same name already exists.'); - }); - - it('should check if the given module exists', async () => { - mockedAskQuestion.mockResolvedValueOnce('unknown_module'); - await expectCommandFailure('Module unknown_module does not exist.'); - }); - - test('Successfully adding new tab', async () => { - mockedAskQuestion.mockResolvedValueOnce('test0'); - mockedAskQuestion.mockResolvedValueOnce('TabNew'); - - await runCommand(); - - expectCall( - fs.mkdir, - ['src/tabs/TabNew', { recursive: true }] - ); - - expectCall( - fs.copyFile, - [ - './scripts/src/templates/templates/__tab__.tsx', - 'src/tabs/TabNew/index.tsx' - ] - ); - - const oldManifest = await retrieveManifest('modules.json'); - const [[manifestPath, newManifest]] = asMock(fs.writeFile).mock.calls; - expect(manifestPath) - .toEqual('modules.json'); - - expect(JSON.parse(newManifest as string)) - .toMatchObject({ - ...oldManifest, - test0: { - tabs: ['tab0', 'TabNew'] - } - }); - }); -}); diff --git a/scripts/src/templates/index.ts b/scripts/src/templates/index.ts deleted file mode 100644 index a8fd7d34f8..0000000000 --- a/scripts/src/templates/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Interface } from 'readline/promises'; -import { Command } from '@commander-js/extra-typings'; - -import { manifestOption, srcDirOption } from '@src/commandUtils'; -import { addNew as addNewModule } from './module'; -import { error as _error, askQuestion, getRl, info, warn } from './print'; -import { addNew as addNewTab } from './tab'; - -async function askMode(rl: Interface) { - while (true) { - const mode = await askQuestion('What would you like to create? (module/tab)', rl); - if (mode !== 'module' && mode !== 'tab') { - warn("Please answer with only 'module' or 'tab'."); - } else { - return mode; - } - } -} - -export default function getTemplateCommand() { - return new Command('template') - .addOption(srcDirOption) - .addOption(manifestOption) - .description('Interactively create a new module or tab') - .action(async buildOpts => { - const rl = getRl(); - try { - const mode = await askMode(rl); - if (mode === 'module') await addNewModule(buildOpts, rl); - else if (mode === 'tab') await addNewTab(buildOpts, rl); - } catch (error) { - _error(`ERROR: ${error.message}`); - info('Terminating module app...'); - } finally { - rl.close(); - } - }); -} diff --git a/scripts/src/templates/module.ts b/scripts/src/templates/module.ts deleted file mode 100644 index e53bbb51e0..0000000000 --- a/scripts/src/templates/module.ts +++ /dev/null @@ -1,44 +0,0 @@ -import fs from 'fs/promises'; -import type { Interface } from 'readline/promises'; -import { promiseAll } from '@src/commandUtils'; -import { type ModuleManifest, retrieveManifest } from '@src/manifest'; -import { askQuestion, success, warn } from './print'; -import { type Options, isSnakeCase } from './utilities'; - -export const check = (manifest: ModuleManifest, name: string) => Object.keys(manifest) - .includes(name); - -async function askModuleName(manifest: ModuleManifest, rl: Interface) { - while (true) { - const name = await askQuestion('What is the name of your new module? (eg. binary_tree)', rl); - if (isSnakeCase(name) === false) { - warn('Module names must be in snake case. (eg. binary_tree)'); - } else if (check(manifest, name)) { - warn('A module with the same name already exists.'); - } else { - return name; - } - } -} - -export async function addNew({ srcDir, manifest: manifestFile }: Options, rl: Interface) { - const manifest = await retrieveManifest(manifestFile); - const moduleName = await askModuleName(manifest, rl); - - const bundleDestination = `${srcDir}/bundles/${moduleName}`; - await fs.mkdir(bundleDestination, { recursive: true }); - await promiseAll( - fs.copyFile( - './scripts/src/templates/templates/__bundle__.ts', - `${bundleDestination}/index.ts` - ), - fs.writeFile( - manifestFile, - JSON.stringify({ - ...manifest, - [moduleName]: { tabs: [] } - }, null, 2) - ) - ); - success(`Bundle for module ${moduleName} created at ${bundleDestination}.`); -} diff --git a/scripts/src/templates/print.ts b/scripts/src/templates/print.ts deleted file mode 100644 index f358a2fc66..0000000000 --- a/scripts/src/templates/print.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type Interface, createInterface } from 'readline/promises'; -import chalk from 'chalk'; - -export const getRl = () => createInterface({ - input: process.stdin, - output: process.stdout -}); - -export function info(...args: any[]) { - return console.log(...args.map(string => chalk.grey(string))); -} - -export function error(...args) { - return console.log(...args.map(string => chalk.red(string))); -} - -export function warn(...args) { - return console.log(...args.map(string => chalk.yellow(string))); -} - -export function success(...args) { - return console.log(...args.map(string => chalk.green(string))); -} - -export function askQuestion(question: string, rl: Interface) { - return rl.question(chalk.blueBright(`${question}\n`)); -} diff --git a/scripts/src/templates/tab.ts b/scripts/src/templates/tab.ts deleted file mode 100644 index d08bcd784a..0000000000 --- a/scripts/src/templates/tab.ts +++ /dev/null @@ -1,68 +0,0 @@ -import fs from 'fs/promises'; -import type { Interface } from 'readline/promises'; -import { promiseAll } from '@src/commandUtils'; -import { type ModuleManifest, retrieveManifest } from '@src/manifest'; -import { check as _check } from './module'; -import { askQuestion, success, warn } from './print'; -import { type Options, isPascalCase } from './utilities'; - -export function check(manifest: ModuleManifest, tabName: string) { - return Object.values(manifest) - .flatMap(x => x.tabs) - .includes(tabName); -} - -async function askModuleName(manifest: ModuleManifest, rl: Interface) { - while (true) { - const name = await askQuestion('Add a new tab to which module?', rl); - if (!_check(manifest, name)) { - warn(`Module ${name} does not exist.`); - } else { - return name; - } - } -} - -async function askTabName(manifest: ModuleManifest, rl: Interface) { - while (true) { - const name = await askQuestion('What is the name of your new tab? (eg. BinaryTree)', rl); - if (check(manifest, name)) { - warn('A tab with the same name already exists.'); - } else if (!isPascalCase(name)) { - warn('Tab names must be in pascal case. (eg. BinaryTree)'); - } else { - return name; - } - } -} - -export async function addNew({ manifest: manifestFile, srcDir }: Options, rl: Interface) { - const manifest = await retrieveManifest(manifestFile); - - const moduleName = await askModuleName(manifest, rl); - const tabName = await askTabName(manifest, rl); - - // Copy module tab template into correct destination and show success message - const tabDestination = `${srcDir}/tabs/${tabName}`; - await fs.mkdir(tabDestination, { recursive: true }); - await promiseAll( - fs.copyFile( - './scripts/src/templates/templates/__tab__.tsx', - `${tabDestination}/index.tsx` - ), - fs.writeFile( - manifestFile, - JSON.stringify( - { - ...manifest, - [moduleName]: { tabs: [...manifest[moduleName].tabs, tabName] } - }, - null, - 2 - ) - ) - ); - success( - `Tab ${tabName} for module ${moduleName} created at ${tabDestination}.` - ); -} diff --git a/scripts/src/templates/templates/__bundle__.ts b/scripts/src/templates/templates/__bundle__.ts deleted file mode 100644 index a3deb9ec02..0000000000 --- a/scripts/src/templates/templates/__bundle__.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * A single sentence summarising the module (this sentence is displayed larger). - * - * Sentences describing the module. More sentences about the module. - * - * @module module_name - * @author Author Name - * @author Author Name - */ - -/* - To access things like the context or module state you can just import the context - using the import below - */ -import context from 'js-slang/context'; - -/** - * Sample function. Increments a number by 1. - * - * @param x The number to be incremented. - * @returns The incremented value of the number. - */ -export function sample_function(x: number): number { - return ++x; -} // Then any functions or variables you want to expose to the user is exported from the bundle's index.ts file diff --git a/scripts/src/templates/templates/__tab__.tsx b/scripts/src/templates/templates/__tab__.tsx deleted file mode 100644 index 34eb95b091..0000000000 --- a/scripts/src/templates/templates/__tab__.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; - -/** - * - * @author - * @author - */ - -/** - * React Component props for the Tab. - */ -type Props = { - children?: never; - className?: never; - context?: any; -}; - -/** - * React Component state for the Tab. - */ -type State = { - counter: number; -}; - -/** - * The main React Component of the Tab. - */ -class Repeat extends React.Component { - constructor(props) { - super(props); - this.state = { - counter: 0, - }; - } - - public render() { - const { counter } = this.state; - return ( -
    This is spawned from the repeat package. Counter is {counter}
    - ); - } -} - -export default { - /** - * This function will be called to determine if the component will be - * rendered. Currently spawns when the result in the REPL is "test". - * @param {DebuggerContext} context - * @returns {boolean} - */ - toSpawn: (context: any) => context.result.value === 'test', - - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - * @param {DebuggerContext} context - */ - body: (context: any) => , - - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ - label: 'Sample Tab', - - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ - iconName: 'build', -}; \ No newline at end of file diff --git a/scripts/src/templates/utilities.ts b/scripts/src/templates/utilities.ts deleted file mode 100644 index 449d6e4287..0000000000 --- a/scripts/src/templates/utilities.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Snake case regex has been changed from `/\b[a-z]+(?:_[a-z]+)*\b/u` to `/\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u` -// to be consistent with the naming of the `arcade_2d` and `physics_2d` modules. -// This change should not affect other modules, since the set of possible names is only expanded. -const snakeCaseRegex = /\b[a-z0-9]+(?:_[a-z0-9]+)*\b/u; -const pascalCaseRegex = /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/u; - -export function isSnakeCase(string: string) { - return snakeCaseRegex.test(string); -} - -export function isPascalCase(string: string) { - return pascalCaseRegex.test(string); -} -export type Options = { - srcDir: string; - manifest: string; -}; diff --git a/scripts/src/testing/__tests__/runner.test.ts b/scripts/src/testing/__tests__/runner.test.ts deleted file mode 100644 index e6d93c3cf1..0000000000 --- a/scripts/src/testing/__tests__/runner.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { MockedFunction } from 'jest-mock'; -import getTestCommand from '..'; -import * as runner from '../runner'; - -jest.spyOn(runner, 'runJest') - .mockImplementation(jest.fn()); - -const runCommand = (...args: string[]) => getTestCommand() - .parseAsync(args, { from: 'user' }); -const mockRunJest = runner.runJest as MockedFunction; - -test('Check that the test command properly passes options to jest', async () => { - await runCommand('-u', '-w', '--srcDir', 'gg', './src/folder'); - - const [call] = mockRunJest.mock.calls; - expect(call[0]) - .toEqual(['-u', '-w', './src/folder']); - expect(call[1]) - .toEqual('gg'); -}); - -test('Check that the test command handles windows paths as posix paths', async () => { - await runCommand('.\\src\\folder'); - - const [call] = mockRunJest.mock.calls; - expect(call[0]) - .toEqual(['./src/folder']); -}); diff --git a/scripts/src/testing/index.ts b/scripts/src/testing/index.ts deleted file mode 100644 index 7d6517429f..0000000000 --- a/scripts/src/testing/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import pathlib from 'path'; -import { Command } from '@commander-js/extra-typings'; -import lodash from 'lodash'; -import { srcDirOption } from '@src/commandUtils'; - -import { runJest } from './runner'; - -export type TestCommandOptions = { - srcDir: string -}; - -const getTestCommand = () => new Command('test') - .description('Run jest') - .addOption(srcDirOption) - .allowExcessArguments() - .allowUnknownOption() - .action(({ srcDir }, command) => { - const [args, filePatterns] = lodash.partition(command.args, arg => arg.startsWith('-')); - - // command.args automatically includes the source directory option - // which is not supported by Jest, so we need to remove it - const toRemove = args.findIndex(arg => arg.startsWith('--srcDir')); - if (toRemove !== -1) { - args.splice(toRemove, 1); - } - - const jestArgs = args.concat(filePatterns.map(pattern => pattern.split(pathlib.win32.sep) - .join(pathlib.posix.sep))); - return runJest(jestArgs, srcDir); - }); - -export default getTestCommand; diff --git a/scripts/src/testing/runner.ts b/scripts/src/testing/runner.ts deleted file mode 100644 index e2fa198644..0000000000 --- a/scripts/src/testing/runner.ts +++ /dev/null @@ -1,6 +0,0 @@ -import pathlib from 'path'; -import jest from 'jest'; - -export function runJest(jestArgs: string[], srcDir: string) { - return jest.run(jestArgs, pathlib.join(srcDir, 'jest.config.js')); -} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json deleted file mode 100644 index 6a3de93b43..0000000000 --- a/scripts/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "strict": false, - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "noEmit": true, - "paths": { - "@src/*": ["./src/*"] - }, - "target": "ESNext", - "skipLibCheck": true - }, - "include": ["./src", "jest.setup.ts"], - "exclude": [ - "./src/templates/templates/**", - "./src/build/docs/__tests__/test_mocks" - ] -} diff --git a/src/bundles/communication/src/__tests__/index.ts b/src/bundles/communication/src/__tests__/index.ts index ff8b8cdb7b..3d82e10581 100644 --- a/src/bundles/communication/src/__tests__/index.ts +++ b/src/bundles/communication/src/__tests__/index.ts @@ -1,3 +1,4 @@ +import { expect, test } from 'vitest'; import { GlobalStateController } from '../GlobalStateController'; import { MultiUserController } from '../MultiUserController'; diff --git a/src/bundles/curve/src/__tests__/curve.ts b/src/bundles/curve/src/__tests__/curve.ts index 935b40d5b4..af743f0110 100644 --- a/src/bundles/curve/src/__tests__/curve.ts +++ b/src/bundles/curve/src/__tests__/curve.ts @@ -1,4 +1,5 @@ import { stringify } from 'js-slang/dist/utils/stringify'; +import { test, expect } from 'vitest'; import { generateCurve, type Curve } from '../curves_webgl'; import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected, make_point } from '../functions'; @@ -8,17 +9,17 @@ function evalCurve(curve: Curve, numPoints: number) { test('Ensure that invalid curves error gracefully', () => { expect(() => evalCurve(() => 1 as any, 200)) - .toThrowErrorMatchingInlineSnapshot('"Expected curve to return a point, got \'1\' at t=0"'); + .toThrow('Expected curve to return a point, got \'1\' at t=0'); }); test('Using 3D render functions with animate_curve should throw errors', () => { expect(() => animate_curve(1, 60, draw_3D_connected(200), (t0) => (t1) => make_point(t0, t1))) - .toThrowErrorMatchingInlineSnapshot('"animate_curve cannot be used with 3D draw function!"'); + .toThrow('animate_curve cannot be used with 3D draw function!'); }); test('Using 2D render functions with animate_3D_curve should throw errors', () => { expect(() => animate_3D_curve(1, 60, draw_connected(200), (t0) => (t1) => make_point(t0, t1))) - .toThrowErrorMatchingInlineSnapshot('"animate_3D_curve cannot be used with 2D draw function!"'); + .toThrow('animate_3D_curve cannot be used with 2D draw function!'); }); test('Render functions have nice string representations', () => { diff --git a/src/bundles/package.json b/src/bundles/package.json index cdb1d0352e..bd0f86213f 100644 --- a/src/bundles/package.json +++ b/src/bundles/package.json @@ -6,12 +6,9 @@ "./*" ], "devDependencies": { - "@types/jest": "^29.0.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.4.1", - "ts-jest": "^29.1.2" + "vitest": "^3.1.4" }, "scripts": { - "test": "jest -c ./jest.config.js" + "test": "vitest --config ./vitest.config.ts" } } diff --git a/src/bundles/repeat/src/__tests__/index.ts b/src/bundles/repeat/src/__tests__/index.ts index e4e5537636..e3cb82a214 100644 --- a/src/bundles/repeat/src/__tests__/index.ts +++ b/src/bundles/repeat/src/__tests__/index.ts @@ -1,3 +1,5 @@ +import { test, expect } from 'vitest'; + import { repeat, twice, thrice } from '../functions'; // Test functions diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts index 7579d71e70..05c2a3dd3f 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts @@ -1,31 +1,31 @@ import * as THREE from 'three'; +import { beforeEach, describe, expect, it, vi, type Mocked } from 'vitest'; import { Physics, Renderer, EntityFactory , MeshFactory } from '../../../../engine'; import { ChassisWrapper } from '../../../ev3/components/Chassis'; -jest.mock('../../../../engine', () => ({ - Physics: jest.fn(), - Renderer: jest.fn(), - EntityFactory: { addCuboid: jest.fn() }, - MeshFactory: { addCuboid: jest.fn() } +vi.mock('../../../../engine', () => ({ + Physics: vi.fn(), + Renderer: vi.fn(), + EntityFactory: { addCuboid: vi.fn() }, + MeshFactory: { addCuboid: vi.fn() } })); -jest.mock('../../../../engine/Entity/EntityFactory'); +vi.mock('../../../../engine/Entity/EntityFactory'); -jest.mock('three', () => { - const three = jest.requireActual('three'); +vi.mock('three', async importOriginal => { return { - ...three, - Mesh: jest.fn().mockImplementation(() => ({ - position: { copy: jest.fn() }, - quaternion: { copy: jest.fn() }, + ...await importOriginal(), + Mesh: vi.fn().mockImplementation(() => ({ + position: { copy: vi.fn() }, + quaternion: { copy: vi.fn() }, visible: false, })), - Color: jest.fn() + Color: vi.fn() }; }); -const mockedMeshFactory = MeshFactory as jest.Mocked; -const mockedEntityFactory = EntityFactory as jest.Mocked; +const mockedMeshFactory = MeshFactory as Mocked; +const mockedEntityFactory = EntityFactory as Mocked; describe('ChassisWrapper', () => { let physicsMock; @@ -34,8 +34,8 @@ describe('ChassisWrapper', () => { let config; beforeEach(() => { - physicsMock = jest.fn() as unknown as Physics; - rendererMock = {add:jest.fn()} as unknown as Renderer; + physicsMock = vi.fn() as unknown as Physics; + rendererMock = {add:vi.fn()} as unknown as Renderer; config = { dimension: { width: 1, height: 1, depth: 1 }, orientation: { x: 0, y: 0, z: 0, w: 1 }, @@ -64,7 +64,7 @@ describe('ChassisWrapper', () => { }); it('should correctly initialize the chassis entity on start', async () => { - const mockEntity = { getTranslation: jest.fn(), getRotation: jest.fn() }; + const mockEntity = { getTranslation: vi.fn(), getRotation: vi.fn() }; mockedEntityFactory.addCuboid.mockReturnValue(mockEntity as any); await chassisWrapper.start(); @@ -74,8 +74,8 @@ describe('ChassisWrapper', () => { it('should update the position and orientation of the debug mesh to match the chassis entity', () => { const mockEntity = { - getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), - getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()) + getTranslation: vi.fn().mockReturnValue(new THREE.Vector3()), + getRotation: vi.fn().mockReturnValue(new THREE.Quaternion()) }; mockedEntityFactory.addCuboid.mockReturnValue(mockEntity as any); chassisWrapper.chassis = mockEntity; diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Mesh.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Mesh.ts index 082e14986c..d202499786 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Mesh.ts @@ -1,44 +1,44 @@ import * as THREE from 'three'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Renderer } from '../../../../engine'; import { loadGLTF } from '../../../../engine/Render/helpers/GLTF'; import { ChassisWrapper } from '../../../ev3/components/Chassis'; import { Mesh } from '../../../ev3/components/Mesh'; -jest.mock('three', () => { - const three = jest.requireActual('three'); +vi.mock('three', async importOriginal => { return { - ...three, - GLTF: jest.fn().mockImplementation(() => ({ + ...await importOriginal(), + GLTF: vi.fn().mockImplementation(() => ({ scene: {}, })), }; }); -jest.mock('../../../../engine/Render/helpers/GLTF', () => ({ - loadGLTF: jest.fn().mockResolvedValue({ +vi.mock('../../../../engine/Render/helpers/GLTF', () => ({ + loadGLTF: vi.fn().mockResolvedValue({ scene: { position: { - copy: jest.fn(), + copy: vi.fn(), }, quaternion: { - copy: jest.fn(), + copy: vi.fn(), }, }, }), })); -jest.mock('../../../ev3/components/Chassis', () => ({ - ChassisWrapper: jest.fn().mockImplementation(() => ({ - getEntity: jest.fn().mockReturnValue({ - getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), - getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()), +vi.mock('../../../ev3/components/Chassis', () => ({ + ChassisWrapper: vi.fn().mockImplementation(() => ({ + getEntity: vi.fn().mockReturnValue({ + getTranslation: vi.fn().mockReturnValue(new THREE.Vector3()), + getRotation: vi.fn().mockReturnValue(new THREE.Quaternion()), }), })), })); -jest.mock('../../../../engine', () => ({ - Renderer: jest.fn().mockImplementation(() => ({ - add: jest.fn(), +vi.mock('../../../../engine', () => ({ + Renderer: vi.fn().mockImplementation(() => ({ + add: vi.fn(), })), })); @@ -49,11 +49,11 @@ describe('Mesh', () => { let mockConfig; beforeEach(() => { - mockRenderer = { add: jest.fn() } as unknown as Renderer; + mockRenderer = { add: vi.fn() } as unknown as Renderer; mockChassisWrapper = { - getEntity: jest.fn().mockReturnValue({ - getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), - getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()), + getEntity: vi.fn().mockReturnValue({ + getTranslation: vi.fn().mockReturnValue(new THREE.Vector3()), + getRotation: vi.fn().mockReturnValue(new THREE.Quaternion()), }), config: { orientation: { diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts index f50f13848d..7148a4ba41 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts @@ -1,48 +1,42 @@ import * as THREE from 'three'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Renderer, Physics } from '../../../../engine'; import { loadGLTF } from '../../../../engine/Render/helpers/GLTF'; import { ChassisWrapper } from '../../../ev3/components/Chassis'; import { Motor } from '../../../ev3/components/Motor'; import { ev3Config } from '../../../ev3/ev3/default/config'; -jest.mock('three', () => { - const originalModule = jest.requireActual('three'); - return { - ...originalModule, - }; -}); - -jest.mock('../../../../engine/Render/helpers/GLTF', () => ({ - loadGLTF: jest.fn().mockResolvedValue({ +vi.mock('../../../../engine/Render/helpers/GLTF', () => ({ + loadGLTF: vi.fn().mockResolvedValue({ scene: { position: { - copy: jest.fn(), + copy: vi.fn(), }, quaternion: { - copy: jest.fn(), + copy: vi.fn(), }, - rotateX: jest.fn(), - rotateZ: jest.fn() + rotateX: vi.fn(), + rotateZ: vi.fn() } }), })); -jest.mock('../../../../engine', () => ({ - Physics: jest.fn(), - Renderer: jest.fn().mockImplementation(() => ({ - add: jest.fn(), +vi.mock('../../../../engine', () => ({ + Physics: vi.fn(), + Renderer: vi.fn().mockImplementation(() => ({ + add: vi.fn(), })), })); -jest.mock('../../../ev3/components/Chassis', () => ({ - ChassisWrapper: jest.fn().mockImplementation(() => ({ - getEntity: jest.fn().mockReturnValue({ - transformDirection: jest.fn().mockImplementation((v) => v), - worldVelocity: jest.fn().mockReturnValue(new THREE.Vector3()), - worldTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), - applyImpulse: jest.fn(), - getMass: jest.fn().mockReturnValue(1), - getRotation: jest.fn(), +vi.mock('../../../ev3/components/Chassis', () => ({ + ChassisWrapper: vi.fn().mockImplementation(() => ({ + getEntity: vi.fn().mockReturnValue({ + transformDirection: vi.fn().mockImplementation((v) => v), + worldVelocity: vi.fn().mockReturnValue(new THREE.Vector3()), + worldTranslation: vi.fn().mockReturnValue(new THREE.Vector3()), + applyImpulse: vi.fn(), + getMass: vi.fn().mockReturnValue(1), + getRotation: vi.fn(), }), })), })); @@ -56,9 +50,9 @@ describe('Motor', () => { beforeEach(() => { mockPhysics = { - applyImpulse: jest.fn(), + applyImpulse: vi.fn(), } as unknown as Physics; - mockRenderer = { add: jest.fn() } as unknown as Renderer; + mockRenderer = { add: vi.fn() } as unknown as Renderer; mockConfig = { displacement: { x: 1, y: 0, z: 0 }, pid: { diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Wheel.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Wheel.ts index c35a58d11a..47ac0954d9 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Wheel.ts @@ -1,20 +1,14 @@ import * as THREE from 'three'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Wheel } from '../../../ev3/components/Wheel'; -jest.mock('../../../../engine/Render/debug/DebugArrow', () => ({ - DebugArrow: jest.fn().mockImplementation(() => ({ - getMesh: jest.fn().mockReturnValue({}), - update: jest.fn(), +vi.mock('../../../../engine/Render/debug/DebugArrow', () => ({ + DebugArrow: vi.fn().mockImplementation(() => ({ + getMesh: vi.fn().mockReturnValue({}), + update: vi.fn(), })), })); -jest.mock('three', () => { - const originalModule = jest.requireActual('three'); - return { - ...originalModule, - }; -}); - describe('Wheel', () => { let wheel; let mockChassisWrapper; @@ -24,18 +18,18 @@ describe('Wheel', () => { beforeEach(() => { mockPhysics = { - castRay: jest.fn(), + castRay: vi.fn(), }; mockRenderer = { - add: jest.fn(), + add: vi.fn(), }; mockChassisWrapper = { - getEntity: jest.fn().mockReturnValue({ - worldTranslation: jest.fn().mockImplementation(() => new THREE.Vector3()), - transformDirection: jest.fn().mockImplementation(() => new THREE.Vector3()), - applyImpulse: jest.fn(), - getMass: jest.fn().mockReturnValue(1), - getCollider: jest.fn().mockReturnValue({}), + getEntity: vi.fn().mockReturnValue({ + worldTranslation: vi.fn().mockImplementation(() => new THREE.Vector3()), + transformDirection: vi.fn().mockImplementation(() => new THREE.Vector3()), + applyImpulse: vi.fn(), + getMass: vi.fn().mockReturnValue(1), + getCollider: vi.fn().mockReturnValue({}), }), }; mockConfig = { diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts index 133a0dcbd4..1abbb5325d 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts @@ -1,3 +1,5 @@ +import { vi, describe, expect, it } from 'vitest'; + import { Physics, Renderer, ControllerMap } from '../../../../../engine'; import { ChassisWrapper } from '../../../../ev3/components/Chassis'; import { Mesh } from '../../../../ev3/components/Mesh'; @@ -8,37 +10,37 @@ import { createDefaultEv3 } from '../../../../ev3/ev3/default/ev3'; import { ColorSensor } from '../../../../ev3/sensor/ColorSensor'; import { UltrasonicSensor } from '../../../../ev3/sensor/UltrasonicSensor'; -jest.mock('../../../../ev3/components/Chassis', () => { - return { ChassisWrapper: jest.fn() }; +vi.mock('../../../../ev3/components/Chassis', () => { + return { ChassisWrapper: vi.fn() }; }); -jest.mock('../../../../ev3/components/Mesh', () => { - return { Mesh: jest.fn() }; +vi.mock('../../../../ev3/components/Mesh', () => { + return { Mesh: vi.fn() }; }); -jest.mock('../../../../ev3/components/Motor', () => { - return { Motor: jest.fn() }; +vi.mock('../../../../ev3/components/Motor', () => { + return { Motor: vi.fn() }; }); -jest.mock('../../../../ev3/components/Wheel', () => { - return { Wheel: jest.fn() }; +vi.mock('../../../../ev3/components/Wheel', () => { + return { Wheel: vi.fn() }; }); -jest.mock('../../../../ev3/sensor/ColorSensor', () => { - return { ColorSensor: jest.fn() }; +vi.mock('../../../../ev3/sensor/ColorSensor', () => { + return { ColorSensor: vi.fn() }; }); -jest.mock('../../../../ev3/sensor/UltrasonicSensor', () => { - return { UltrasonicSensor: jest.fn() }; +vi.mock('../../../../ev3/sensor/UltrasonicSensor', () => { + return { UltrasonicSensor: vi.fn() }; }); -jest.mock('../../../../../engine', () => { +vi.mock('../../../../../engine', () => { return { - Physics: jest.fn(), - Renderer: jest.fn(), - ControllerMap: jest.fn().mockImplementation(() => { - return { add: jest.fn() }; + Physics: vi.fn(), + Renderer: vi.fn(), + ControllerMap: vi.fn().mockImplementation(() => { + return { add: vi.fn() }; }) }; }); describe('createDefaultEv3', () => { const mockPhysics = new Physics({gravity:{x:0, y:-1, z:0}, timestep: 0.01}); - const mockRenderer = jest.fn() as unknown as Renderer; + const mockRenderer = vi.fn() as unknown as Renderer; const mockConfig =ev3Config; it('should correctly create all components and return a controller map', () => { diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts index f23f747d1b..2b4de1695b 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts @@ -1,13 +1,7 @@ import * as THREE from 'three'; +import { beforeEach, describe, it, expect } from 'vitest'; import { NumberPidController, VectorPidController } from '../../../ev3/feedback_control/PidController'; -jest.mock('three', () => { - const three = jest.requireActual('three'); - return { - ...three, - }; -}); - const resetPid = (pidController:NumberPidController) => { pidController.errorsSum = 0; pidController.previousError = 0; diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/ColorSensor.ts index dfd45cdb91..7931ac1de3 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/ColorSensor.ts @@ -1,23 +1,17 @@ import * as THREE from 'three'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ColorSensor } from '../../../ev3/sensor/ColorSensor'; -jest.mock('three', () => { - const three = jest.requireActual('three'); - return { - ...three, - }; -}); - -jest.mock('../../../../engine', () => ({ - Renderer: jest.fn().mockImplementation(() => ({ - scene: jest.fn(), - render: jest.fn(), - getElement: jest.fn(() => document.createElement('canvas')), +vi.mock('../../../../engine', () => ({ + Renderer: vi.fn().mockImplementation(() => ({ + scene: vi.fn(), + render: vi.fn(), + getElement: vi.fn(() => document.createElement('canvas')), })), })); -jest.mock('../../../../engine/Render/helpers/Camera', () => ({ - getCamera: jest.fn().mockImplementation(() => { +vi.mock('../../../../engine/Render/helpers/Camera', () => ({ + getCamera: vi.fn().mockImplementation(() => { return new THREE.PerspectiveCamera(); }), })); @@ -30,15 +24,15 @@ describe('ColorSensor', () => { beforeEach(() => { mockChassisWrapper = { - getEntity: jest.fn(() => ({ - worldTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + getEntity: vi.fn(() => ({ + worldTranslation: vi.fn().mockReturnValue(new THREE.Vector3()), })), }; mockRenderer = { - add: jest.fn(), - scene: jest.fn(), - render: jest.fn(), - getElement: jest.fn(() => document.createElement('canvas')), + add: vi.fn(), + scene: vi.fn(), + render: vi.fn(), + getElement: vi.fn(() => document.createElement('canvas')), }; mockConfig = { tickRateInSeconds: 0.1, @@ -64,17 +58,17 @@ describe('ColorSensor', () => { sensor = new ColorSensor(mockChassisWrapper, mockRenderer, mockConfig); const mockCtx = { - getImageData: jest.fn(() => ({ + getImageData: vi.fn(() => ({ data: new Uint8ClampedArray([255, 255, 255, 255]), })), - putImageData: jest.fn(), - drawImage: jest.fn(), - fillRect: jest.fn(), - clearRect: jest.fn(), + putImageData: vi.fn(), + drawImage: vi.fn(), + fillRect: vi.fn(), + clearRect: vi.fn(), canvas: {}, }; - HTMLCanvasElement.prototype.getContext = jest + HTMLCanvasElement.prototype.getContext = vi .fn() .mockImplementation((_) => { return mockCtx; diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts index 3b2df33b2d..eabb120bc6 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts @@ -1,18 +1,19 @@ import * as THREE from 'three'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { UltrasonicSensor } from '../../../ev3/sensor/UltrasonicSensor'; -jest.mock('three', () => ({ - Vector3: jest.fn().mockImplementation(() => ({ - clone: jest.fn().mockReturnThis(), - normalize: jest.fn().mockReturnThis(), - copy: jest.fn() +vi.mock('three', () => ({ + Vector3: vi.fn().mockImplementation(() => ({ + clone: vi.fn().mockReturnThis(), + normalize: vi.fn().mockReturnThis(), + copy: vi.fn() })), - ArrowHelper: jest.fn().mockImplementation(() => ({ + ArrowHelper: vi.fn().mockImplementation(() => ({ visible: false, position: { - copy: jest.fn() + copy: vi.fn() }, - setDirection: jest.fn() + setDirection: vi.fn() })) })); @@ -25,17 +26,17 @@ describe('UltrasonicSensor', () => { beforeEach(() => { mockChassisWrapper = { - getEntity: jest.fn(() => ({ - worldTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), - transformDirection: jest.fn().mockReturnValue(new THREE.Vector3()), - getCollider: jest.fn() + getEntity: vi.fn(() => ({ + worldTranslation: vi.fn().mockReturnValue(new THREE.Vector3()), + transformDirection: vi.fn().mockReturnValue(new THREE.Vector3()), + getCollider: vi.fn() })) }; mockPhysics = { - castRay: jest.fn().mockReturnValue({ distance: 5 }) + castRay: vi.fn().mockReturnValue({ distance: 5 }) }; mockRenderer = { - add: jest.fn() + add: vi.fn() }; mockConfig = { displacement: { x: 1, y: 1, z: 1 }, diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts b/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts index eae138830a..4bf41b3e05 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts @@ -1,12 +1,13 @@ +import { vi, beforeEach, describe, it, expect, type MockedFunction, type MockedClass } from 'vitest'; import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; import { Program, program_controller_identifier } from '../../program/Program'; import { runECEvaluator } from '../../program/evaluate'; -jest.mock('../../../engine/Core/CallbackHandler'); -jest.mock('../../program/evaluate'); +vi.mock('../../../engine/Core/CallbackHandler'); +vi.mock('../../program/evaluate'); -const mockedRunECEvaluator = runECEvaluator as jest.MockedFunction; -const mockedCallbackHandler = CallbackHandler as jest.MockedClass; +const mockedRunECEvaluator = runECEvaluator as MockedFunction; +const mockedCallbackHandler = CallbackHandler as MockedClass; describe('Program', () => { let program: Program; @@ -18,7 +19,7 @@ describe('Program', () => { program = new Program(mockCode); - jest.spyOn(console, 'error').mockImplementation(jest.fn()); + vi.spyOn(console, 'error').mockImplementation(vi.fn()); }); it('should initialize with default configuration if none provided', () => { @@ -33,7 +34,7 @@ describe('Program', () => { }); it('should start the evaluator with correct options', () => { - const mockIterator = { next: jest.fn() } as any; + const mockIterator = { next: vi.fn() } as any; mockedRunECEvaluator.mockReturnValue(mockIterator); program.start(); @@ -43,7 +44,7 @@ describe('Program', () => { }); it('should handle pause and resume correctly', () => { - const mockIterator = { next: jest.fn() } as any; + const mockIterator = { next: vi.fn() } as any; mockedRunECEvaluator.mockReturnValue(mockIterator); program.start(); @@ -58,7 +59,7 @@ describe('Program', () => { }); it('should process fixed number of steps per tick', () => { - const mockIterator = { next: jest.fn() } as any; + const mockIterator = { next: vi.fn() } as any; mockedRunECEvaluator.mockReturnValue(mockIterator); program.start(); diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/utils/mergeConfig.ts b/src/bundles/robot_simulation/src/controllers/__tests__/utils/mergeConfig.ts index 4ad579dd6e..220eaf024a 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/utils/mergeConfig.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/utils/mergeConfig.ts @@ -1,3 +1,4 @@ +import { describe, expect, it } from 'vitest'; import { mergeConfig } from '../../utils/mergeConfig'; // Update the path accordingly describe('mergeConfig function', () => { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/CallbackHandler.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/CallbackHandler.ts index c263000ab2..c8b83d6188 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/CallbackHandler.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/CallbackHandler.ts @@ -1,3 +1,4 @@ +import { describe, expect, test, vi } from 'vitest'; import { CallbackHandler } from '../../Core/CallbackHandler'; import { PhysicsTimingInfo } from '../../Physics'; @@ -17,7 +18,7 @@ const createTimingInfo = ({ describe('CallbackHandler', () => { test('adds callbacks correctly', () => { const handler = new CallbackHandler(); - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); handler.addCallback(mockCallback, 100); @@ -27,9 +28,9 @@ describe('CallbackHandler', () => { }); test('executes callback after correct delay', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const handler = new CallbackHandler(); - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); handler.addCallback(mockCallback, 100); handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 100 })); @@ -39,7 +40,7 @@ describe('CallbackHandler', () => { test('removes callback after execution', () => { const handler = new CallbackHandler(); - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); handler.addCallback(mockCallback, 100); handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 100 })); @@ -49,8 +50,8 @@ describe('CallbackHandler', () => { test('handles multiple callbacks correctly', () => { const handler = new CallbackHandler(); - const mockCallback1 = jest.fn(); - const mockCallback2 = jest.fn(); + const mockCallback1 = vi.fn(); + const mockCallback2 = vi.fn(); handler.addCallback(mockCallback1, 50); handler.addCallback(mockCallback2, 100); @@ -65,7 +66,7 @@ describe('CallbackHandler', () => { test('does not execute callback before its time', () => { const handler = new CallbackHandler(); - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); handler.addCallback(mockCallback, 100); handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 50 })); @@ -75,7 +76,7 @@ describe('CallbackHandler', () => { test('correctly handles step count changes', () => { const handler = new CallbackHandler(); - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); handler.addCallback(mockCallback, 100); // Simulate no step count change diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts index a1c71753b3..5e5653a0cc 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts @@ -1,3 +1,4 @@ +import { describe, expect, test, vi, type Mock } from 'vitest'; import { Controller, ControllerGroup, @@ -15,18 +16,18 @@ const createTimingInfo = () => { describe('ControllerMap methods', () => { // Define test cases in an array of arrays. Each inner array represents parameters for a single test case. const methodsTestData: Array< - [string, jest.Mock, { async: boolean; args?: any[] }] + [string, Mock, { async: boolean; args?: any[] }] > = [ - ['start', jest.fn(), { async: true }], - ['update', jest.fn(), { async: false, args: [createTimingInfo()] }], - ['fixedUpdate', jest.fn(), { async: false, args: [createTimingInfo()] }], - ['onDestroy', jest.fn(), { async: false }], + ['start', vi.fn(), { async: true }], + ['update', vi.fn(), { async: false, args: [createTimingInfo()] }], + ['fixedUpdate', vi.fn(), { async: false, args: [createTimingInfo()] }], + ['onDestroy', vi.fn(), { async: false }], ]; test.each(methodsTestData)( '%s calls %s on all contained controllers', async (methodName, mockMethod, { async, args = [] }) => { - const notCalledMethod = jest.fn(); + const notCalledMethod = vi.fn(); const controllerMap = new ControllerMap( { first: { [methodName]: mockMethod }, @@ -71,13 +72,13 @@ describe('ControllerMap methods', () => { // Setup: Create a couple of mock controllers const mockController1 = { name: 'Controller1', - start: jest.fn(), - update: jest.fn(), + start: vi.fn(), + update: vi.fn(), }; const mockController2 = { name: 'Controller2', - start: jest.fn(), - update: jest.fn(), + start: vi.fn(), + update: vi.fn(), }; const controllerMap = new ControllerMap({ @@ -105,8 +106,8 @@ describe('ControllerGroup', () => { test.each(methodsTestData)( '%s method behavior', async (methodName, { async, args }) => { - const mockMethod = jest.fn(); - const notCalledMethod = jest.fn(); + const mockMethod = vi.fn(); + const notCalledMethod = vi.fn(); const controller: Controller = { [methodName]: mockMethod, // @ts-expect-error This test checks for a method that does not exist @@ -140,7 +141,7 @@ describe('ControllerGroup', () => { test.each(methodsTestData)( '%s no method calls if controller does not have method name', async (methodName, { async, args }) => { - const notCalledMethod = jest.fn(); + const notCalledMethod = vi.fn(); const controller: Controller = { // @ts-expect-error This test checks for a method that does not exist 'notMethodName': notCalledMethod, diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts index b3d38448d1..2818665c51 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts @@ -1,3 +1,4 @@ +import { beforeEach, expect, describe, test, vi } from 'vitest'; import { TypedEventTarget } from '../../Core/Events'; class StringEvent extends Event { @@ -31,7 +32,7 @@ describe('TypedEventTarget', () => { }); test('addEventListener adds a listener for the specified event type', () => { - const listener = jest.fn(); + const listener = vi.fn(); eventTarget.addEventListener('event1', listener); const event = new StringEvent('event1', 'Hello'); eventTarget.dispatchEvent('event1', event); @@ -39,8 +40,8 @@ describe('TypedEventTarget', () => { }); test('addEventListener adds multiple listeners for the same event type', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); + const listener1 = vi.fn(); + const listener2 = vi.fn(); eventTarget.addEventListener('event1', listener1); eventTarget.addEventListener('event1', listener2); const event = new StringEvent('event1', 'Hello'); @@ -50,8 +51,8 @@ describe('TypedEventTarget', () => { }); test('addEventListener adds listeners for different event types', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); + const listener1 = vi.fn(); + const listener2 = vi.fn(); eventTarget.addEventListener('event1', listener1); eventTarget.addEventListener('event2', listener2); const event1 = new StringEvent('event1', 'Hello'); @@ -63,8 +64,8 @@ describe('TypedEventTarget', () => { }); test('dispatchEvent dispatches the event to the registered listeners', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); + const listener1 = vi.fn(); + const listener2 = vi.fn(); eventTarget.addEventListener('event1', listener1); eventTarget.addEventListener('event2', listener2); const event1 = new StringEvent('event1', 'Hello'); @@ -76,7 +77,7 @@ describe('TypedEventTarget', () => { }); test('dispatchEvent returns true if there are listeners for the event type', () => { - const listener = jest.fn(); + const listener = vi.fn(); eventTarget.addEventListener('event1', listener); const event = new StringEvent('event1', 'Hello'); const result = eventTarget.dispatchEvent('event1', event); diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts index 765e6a2de0..9b92f5af41 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts @@ -1,3 +1,4 @@ +import { describe, expect, test, beforeEach } from 'vitest'; import { RobotConsole } from '../../Core/RobotConsole'; // Adjust the import path as necessary describe('RobotConsole', () => { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/Timer.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Timer.ts index 7eb057ab71..8f7f71c40e 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/Timer.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/Timer.ts @@ -1,3 +1,4 @@ +import { beforeEach, describe, expect, it } from 'vitest'; import { Timer } from '../../Core/Timer'; // Adjust the import path as per your project structure describe('Timer', () => { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Entity/Entity.ts b/src/bundles/robot_simulation/src/engine/__tests__/Entity/Entity.ts index fdb6209064..0b4478d328 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Entity/Entity.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Entity/Entity.ts @@ -1,4 +1,5 @@ import type Rapier from '@dimforge/rapier3d-compat'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { Entity } from '../../Entity/Entity'; import { vec3 } from '../../Math/Convert'; import { SimpleQuaternion, SimpleVector } from '../../Math/Vector'; @@ -8,21 +9,21 @@ const createRigidBodyMock = ( rotation: SimpleQuaternion ) => { const rigidBodyMock = { - translation: jest.fn().mockReturnValue(translation), - rotation: jest.fn().mockReturnValue(rotation), - setTranslation: jest.fn(), - setRotation: jest.fn(), - applyImpulseAtPoint: jest.fn(), - linvel: jest.fn().mockReturnValue({ x: 0, y: 0, z: 0 }), - angvel: jest.fn().mockReturnValue({ x: 0, y: 0, z: 0 }), + translation: vi.fn().mockReturnValue(translation), + rotation: vi.fn().mockReturnValue(rotation), + setTranslation: vi.fn(), + setRotation: vi.fn(), + applyImpulseAtPoint: vi.fn(), + linvel: vi.fn().mockReturnValue({ x: 0, y: 0, z: 0 }), + angvel: vi.fn().mockReturnValue({ x: 0, y: 0, z: 0 }), }; return rigidBodyMock as unknown as Rapier.RigidBody; }; const createCollider = (mass: number) => { const colliderMock = { - mass: jest.fn().mockReturnValue(mass), - setMass: jest.fn(), + mass: vi.fn().mockReturnValue(mass), + setMass: vi.fn(), }; return colliderMock as unknown as Rapier.Collider; }; diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts b/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts index 566cedfc16..350842d571 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts @@ -1,4 +1,5 @@ import { Quaternion, Vector3, Euler } from 'three'; +import { describe, it, expect } from 'vitest'; import { quat, vec3, euler } from '../../Math/Convert'; // Adjust the import path as necessary describe('Three.js utility functions', () => { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/src/engine/__tests__/Physics.ts index 67db39fc48..a1cf6817cc 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Physics.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Physics.ts @@ -1,22 +1,25 @@ // physics.test.js import rapier from '@dimforge/rapier3d-compat'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { Physics } from '../Physics'; // Mock rapier -jest.mock('@dimforge/rapier3d-compat', () => { +vi.mock('@dimforge/rapier3d-compat', () => { return { - init: jest.fn(), - World: jest.fn().mockImplementation(() => ({ - timestep: jest.fn(), - createRigidBody: jest.fn(), - createCollider: jest.fn(), - castRayAndGetNormal: jest.fn(), - step: jest.fn(), - castRay: jest.fn(), - })), - Ray: jest.fn(), - RigidBodyDesc: jest.fn(), - ColliderDesc: jest.fn(), + default: { + init: vi.fn(), + World: vi.fn().mockImplementation(() => ({ + timestep: vi.fn(), + createRigidBody: vi.fn(), + createCollider: vi.fn(), + castRayAndGetNormal: vi.fn(), + step: vi.fn(), + castRay: vi.fn(), + })), + Ray: vi.fn(), + RigidBodyDesc: vi.fn(), + ColliderDesc: vi.fn(), + } }; }); diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts index 3944de81f5..20d2feae11 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Render/MeshFactory.ts @@ -1,9 +1,10 @@ import * as THREE from 'three'; +import { describe, expect, it, vi } from 'vitest'; import { addCuboid } from '../../Render/helpers/MeshFactory'; // Mock the necessary Three.js methods and classes -jest.mock('three', () => { - const originalModule = jest.requireActual('three'); +vi.mock('three', async importOriginal => { + const originalModule: any = await importOriginal(); class Vector3 { x: number; @@ -47,9 +48,9 @@ jest.mock('three', () => { return { ...originalModule, - BoxGeometry: jest.fn(), - MeshPhysicalMaterial: jest.fn(), - Mesh: jest.fn().mockImplementation(function (this: any, geometry, material) { + BoxGeometry: vi.fn(), + MeshPhysicalMaterial: vi.fn(), + Mesh: vi.fn().mockImplementation(function (this: any, geometry, material) { this.geometry = geometry; this.material = material; this.position = new Vector3(); @@ -57,7 +58,7 @@ jest.mock('three', () => { }), Vector3: Vector3, Quaternion: Quaternion, - Color: jest.fn().mockImplementation(function (color) { + Color: vi.fn().mockImplementation(function (color) { return { color }; }), }; diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts b/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts index 22d5877812..9ab9ba157d 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts @@ -1,4 +1,5 @@ import * as THREE from 'three'; +import { describe, expect, test } from 'vitest'; import { CameraOptions, getCamera } from '../../../Render/helpers/Camera'; diff --git a/src/bundles/scrabble/src/__tests__/__snapshots__/index.ts.snap b/src/bundles/scrabble/src/__tests__/__snapshots__/index.ts.snap index 8a97ac48a8..4e0e53b0e7 100644 --- a/src/bundles/scrabble/src/__tests__/__snapshots__/index.ts.snap +++ b/src/bundles/scrabble/src/__tests__/__snapshots__/index.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports['scrabble_letters matches snapshot 1'] = ` +exports[`scrabble_letters matches snapshot 1`] = ` [ [ "a", @@ -1916153,7 +1916153,7 @@ exports['scrabble_letters matches snapshot 1'] = ` ] `; -exports['scrabble_letters_tiny matches snapshot 1'] = ` +exports[`scrabble_letters_tiny matches snapshot 1`] = ` [ [ "a", @@ -1935269,7 +1935269,7 @@ exports['scrabble_letters_tiny matches snapshot 1'] = ` ] `; -exports['scrabble_words matches snapshot 1'] = ` +exports[`scrabble_words matches snapshot 1`] = ` [ "aa", "aah", @@ -2108094,7 +2108094,7 @@ exports['scrabble_words matches snapshot 1'] = ` ] `; -exports['scrabble_words_tiny matches snapshot 1'] = ` +exports[`scrabble_words_tiny matches snapshot 1`] = ` [ "aa", "abbreviates", diff --git a/src/bundles/scrabble/src/__tests__/index.ts b/src/bundles/scrabble/src/__tests__/index.ts index 7d3dd7ba46..e70a15bfbb 100644 --- a/src/bundles/scrabble/src/__tests__/index.ts +++ b/src/bundles/scrabble/src/__tests__/index.ts @@ -1,3 +1,4 @@ +import { test, expect } from 'vitest'; import { scrabble_letters, scrabble_letters_tiny, diff --git a/src/bundles/sound/src/__tests__/sound.test.ts b/src/bundles/sound/src/__tests__/sound.test.ts index c7980e5be9..0a64e9cea3 100644 --- a/src/bundles/sound/src/__tests__/sound.test.ts +++ b/src/bundles/sound/src/__tests__/sound.test.ts @@ -1,10 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import { describe, expect, test } from 'vitest'; import { make_sound, play, play_in_tab } from '../functions'; describe('Test make_sound', () => { test('Should error gracefully when duration is negative', () => { expect(() => make_sound(() => 0, -1)) - .toThrowErrorMatchingInlineSnapshot('"Sound duration must be greater than or equal to 0"'); + .toThrow('Sound duration must be greater than or equal to 0'); }); test('Should not error when duration is zero', () => { @@ -16,7 +17,7 @@ describe('Test play', () => { test('Should error gracefully when duration is negative', () => { const sound = [(t) => 0, -1]; expect(() => play(sound as any)) - .toThrowErrorMatchingInlineSnapshot('"play: duration of sound is negative"'); + .toThrow('play: duration of sound is negative'); }); test('Should not error when duration is zero', () => { @@ -29,7 +30,7 @@ describe('Test play_in_tab', () => { test('Should error gracefully when duration is negative', () => { const sound = [(t) => 0, -1]; expect(() => play_in_tab(sound as any)) - .toThrowErrorMatchingInlineSnapshot('"play_in_tab: duration of sound is negative"'); + .toThrow('play_in_tab: duration of sound is negative'); }); test('Should not error when duration is zero', () => { diff --git a/src/bundles/unittest/src/__tests__/index.ts b/src/bundles/unittest/src/__tests__/index.ts index 1d8c2a380d..f344ac04ed 100644 --- a/src/bundles/unittest/src/__tests__/index.ts +++ b/src/bundles/unittest/src/__tests__/index.ts @@ -1,4 +1,5 @@ import { list } from 'js-slang/dist/stdlib/list'; +import { test, expect, vi, beforeEach } from 'vitest'; import * as asserts from '../asserts'; import * as testing from '../functions'; @@ -15,7 +16,7 @@ beforeEach(() => { }); test('context is created correctly', () => { - const mockTestFn = jest.fn(); + const mockTestFn = vi.fn(); testing.describe('Testing 321', () => { testing.it('Testing 123', mockTestFn); }); diff --git a/src/bundles/vitest.config.ts b/src/bundles/vitest.config.ts new file mode 100644 index 0000000000..4115807de1 --- /dev/null +++ b/src/bundles/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + resolve: { + alias: [{ + find: /^js-slang\/context/, + replacement: `${import.meta.dirname}/__mocks__/context.ts` + }] + }, + test: { + name: 'Bundles', + environment: 'jsdom', + include: ['**/__tests__/**/*.ts'] + } +}); diff --git a/src/modules-lib/package.json b/src/modules-lib/package.json index 94862461f5..97dc65d25d 100644 --- a/src/modules-lib/package.json +++ b/src/modules-lib/package.json @@ -6,9 +6,8 @@ "devDependencies": { "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", - "jest": "^29.7.0", - "ts-jest": "^29.1.2", - "typescript": "^5.8.2" + "typescript": "^5.8.2", + "vitest": "^3.1.4" }, "exports": { "./*": "./dist/*.js", @@ -24,16 +23,6 @@ "scripts": { "build": "tsc --project ./tsconfig.prod.json", "tsc": "tsc --project ./tsconfig.json", - "test": "jest" - }, - "jest": { - "displayName": "Modules Library", - "extensionsToTreatAsEsm": [ - ".ts" - ], - "preset": "ts-jest/presets/default-esm", - "testMatch": [ - "/src/**/__tests__/**/*.ts" - ] + "test": "vitest --config ./vitest.config.ts" } } diff --git a/buildtools/src/commands/__tests__/hextocolor.ts b/src/modules-lib/src/__tests__/hextocolor.test.ts similarity index 100% rename from buildtools/src/commands/__tests__/hextocolor.ts rename to src/modules-lib/src/__tests__/hextocolor.test.ts diff --git a/src/modules-lib/src/__tests__/hextocolor.ts b/src/modules-lib/src/__tests__/hextocolor.ts deleted file mode 100644 index 835095350a..0000000000 --- a/src/modules-lib/src/__tests__/hextocolor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { hexToColor } from '../utilities'; - -describe('Test hexToColor', () => { - test.each([ - ['#FFFFFF', [1, 1, 1]], - ['ffffff', [1, 1, 1]], - ['0088ff', [0, 0.53, 1]], - ['#000000', [0, 0, 0]], - ['#GGGGGG', [0, 0, 0]], - ['888888', [0.53, 0.53, 0.53]] - ])('Testing %s', (c, expected) => { - const result = hexToColor(c); - for (let i = 0; i < expected.length; i++) { - expect(result[i]).toBeCloseTo(expected[i]); - } - }); -}); diff --git a/src/modules-lib/vitest.config.ts b/src/modules-lib/vitest.config.ts new file mode 100644 index 0000000000..f7bba3c1dd --- /dev/null +++ b/src/modules-lib/vitest.config.ts @@ -0,0 +1,9 @@ +// Modules-lib test config +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + name: 'Modules Library', + globals: true + } +}); diff --git a/src/tabs/Curve/src/__tests__/Curve.tsx b/src/tabs/Curve/src/__tests__/Curve.tsx index fbfc411932..ecca735a1d 100644 --- a/src/tabs/Curve/src/__tests__/Curve.tsx +++ b/src/tabs/Curve/src/__tests__/Curve.tsx @@ -2,6 +2,7 @@ import { animate_3D_curve, animate_curve, draw_3D_connected, draw_connected } from '@sourceacademy/bundle-curve'; import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; import { mockDebuggerContext } from '@sourceacademy/modules-lib/utilities'; +import { expect, test } from 'vitest'; import { CurveTab } from '..'; test('Curve animations error gracefully', () => { diff --git a/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap b/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap index 6d644b1623..43e7cad2a2 100644 --- a/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap +++ b/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap @@ -1,25 +1,23 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports['Curve 3D animations error gracefully 1'] = ` +exports[`Curve 3D animations error gracefully 1`] = ` `; -exports['Curve animations error gracefully 1'] = ` +exports[`Curve animations error gracefully 1`] = ` { diff --git a/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap b/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap index f14bda65a0..29f22fb8f0 100644 --- a/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap +++ b/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap @@ -1,22 +1,20 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports['Ensure that rune animations error gracefully 1'] = ` +exports[`Ensure that rune animations error gracefully 1`] = ` { - const env = loadEnv(mode, process.cwd()); - return { - plugins: [react()], - root: 'devserver', - resolve: { - preserveSymlinks: true, - alias:[{ - find: /^js-slang\/context/, - replacement: pathlib.resolve('./devserver/src/mockModuleContext') - }] - }, - define: { - 'process.env': env - }, - optimizeDeps: { - esbuildOptions: { - // Node.js global to browser globalThis - define: { - global: 'globalThis' - }, - } - }, - }; -}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000..2a27edd466 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +// Root vitest config +import { defineConfig } from 'vitest/config'; + +// TODO: Migrate this configuration to vitest 3.2 when it becomes stable +export default defineConfig({ + test: { + workspace: [ + 'lib/*/vitest.config.ts', + './devserver/vite.config.ts', + './src/bundles', + './src/tabs' + ] + } +}); diff --git a/yarn.lock b/yarn.lock index fe1232fed4..f9f91a6a25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,220 +15,210 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.26.2": - version: 7.26.2 - resolution: "@babel/code-frame@npm:7.26.2" +"@babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - "@babel/helper-validator-identifier": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.27.1" js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10c0/7d79621a6849183c415486af99b1a20b84737e8c11cd55b6544f688c51ce1fd710e6d869c3dd21232023da272a79b91efb3e83b5bc2dc65c1187c5fcd1b72ea8 + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 languageName: node linkType: hard -"@babel/compat-data@npm:^7.26.8": - version: 7.26.8 - resolution: "@babel/compat-data@npm:7.26.8" - checksum: 10c0/66408a0388c3457fff1c2f6c3a061278dd7b3d2f0455ea29bb7b187fa52c60ae8b4054b3c0a184e21e45f0eaac63cf390737bc7504d1f4a088a6e7f652c068ca +"@babel/compat-data@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/compat-data@npm:7.27.2" + checksum: 10c0/077c9e01af3b90decee384a6a44dcf353898e980cee22ec7941f9074655dbbe97ec317345536cdc7ef7391521e1497930c522a3816af473076dd524be7fccd32 languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.10, @babel/core@npm:^7.23.9, @babel/core@npm:^7.26.10": - version: 7.26.10 - resolution: "@babel/core@npm:7.26.10" +"@babel/core@npm:^7.1.0, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.10, @babel/core@npm:^7.26.10": + version: 7.27.1 + resolution: "@babel/core@npm:7.27.1" dependencies: "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.10" - "@babel/helper-compilation-targets": "npm:^7.26.5" - "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helpers": "npm:^7.26.10" - "@babel/parser": "npm:^7.26.10" - "@babel/template": "npm:^7.26.9" - "@babel/traverse": "npm:^7.26.10" - "@babel/types": "npm:^7.26.10" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.1" + "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helpers": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.1" + "@babel/template": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/e046e0e988ab53841b512ee9d263ca409f6c46e2a999fe53024688b92db394346fa3aeae5ea0866331f62133982eee05a675d22922a4603c3f603aa09a581d62 + checksum: 10c0/0fc31f87f5401ac5d375528cb009f4ea5527fc8c5bb5b64b5b22c033b60fd0ad723388933a5f3f5db14e1edd13c958e9dd7e5c68f9b68c767aeb496199c8a4bb languageName: node linkType: hard -"@babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0, @babel/generator@npm:^7.7.2": - version: 7.27.0 - resolution: "@babel/generator@npm:7.27.0" +"@babel/generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/generator@npm:7.27.1" dependencies: - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" + "@babel/parser": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^3.0.2" - checksum: 10c0/7cb10693d2b365c278f109a745dc08856cae139d262748b77b70ce1d97da84627f79648cab6940d847392c0e5d180441669ed958b3aee98d9c7d274b37c553bd + checksum: 10c0/c4156434b21818f558ebd93ce45f027c53ee570ce55a84fd2d9ba45a79ad204c17e0bff753c886fb6c07df3385445a9e34dc7ccb070d0ac7e80bb91c8b57f423 languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" +"@babel/helper-annotate-as-pure@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-annotate-as-pure@npm:7.27.1" dependencies: - "@babel/types": "npm:^7.25.9" - checksum: 10c0/095b6ba50489d797733abebc4596a81918316a99e3632755c9f02508882912b00c2ae5e468532a25a5c2108d109ddbe9b7da78333ee7cc13817fc50c00cf06fe + "@babel/types": "npm:^7.27.1" + checksum: 10c0/fc4751b59c8f5417e1acb0455d6ffce53fa5e79b3aca690299fbbf73b1b65bfaef3d4a18abceb190024c5836bb6cfbc3711e83888648df93df54e18152a1196c languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.26.5": - version: 7.27.0 - resolution: "@babel/helper-compilation-targets@npm:7.27.0" +"@babel/helper-compilation-targets@npm:^7.27.1": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: - "@babel/compat-data": "npm:^7.26.8" - "@babel/helper-validator-option": "npm:^7.25.9" + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10c0/375c9f80e6540118f41bd53dd54d670b8bf91235d631bdead44c8b313b26e9cd89aed5c6df770ad13a87a464497b5346bb72b9462ba690473da422f5402618b6 + checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.9" +"@babel/helper-create-class-features-plugin@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-create-class-features-plugin@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-member-expression-to-functions": "npm:^7.25.9" - "@babel/helper-optimise-call-expression": "npm:^7.25.9" - "@babel/helper-replace-supers": "npm:^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-member-expression-to-functions": "npm:^7.27.1" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/b2bdd39f38056a76b9ba00ec5b209dd84f5c5ebd998d0f4033cf0e73d5f2c357fbb49d1ce52db77a2709fb29ee22321f84a5734dc9914849bdfee9ad12ce8caf + checksum: 10c0/4ee199671d6b9bdd4988aa2eea4bdced9a73abfc831d81b00c7634f49a8fc271b3ceda01c067af58018eb720c6151322015d463abea7072a368ee13f35adbb4c languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9" +"@babel/helper-member-expression-to-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-member-expression-to-functions@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10c0/e08c7616f111e1fb56f398365e78858e26e466d4ac46dff25921adc5ccae9b232f66e952a2f4162bbe336627ba336c7fd9eca4835b6548935973d3380d77eaff + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/5762ad009b6a3d8b0e6e79ff6011b3b8fdda0fefad56cfa8bfbe6aa02d5a8a8a9680a45748fe3ac47e735a03d2d88c0a676e3f9f59f20ae9fadcc8d51ccd5a53 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-module-imports@npm:7.25.9" +"@babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10c0/078d3c2b45d1f97ffe6bb47f61961be4785d2342a4156d8b42c92ee4e1b7b9e365655dd6cb25329e8fe1a675c91eeac7e3d04f0c518b67e417e29d6e27b6aa70 + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.25.9, @babel/helper-module-transforms@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/helper-module-transforms@npm:7.26.0" +"@babel/helper-module-transforms@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-transforms@npm:7.27.1" dependencies: - "@babel/helper-module-imports": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/ee111b68a5933481d76633dad9cdab30c41df4479f0e5e1cc4756dc9447c1afd2c9473b5ba006362e35b17f4ebddd5fca090233bef8dfc84dca9d9127e56ec3a + checksum: 10c0/196ab29635fe6eb5ba6ead2972d41b1c0d40f400f99bd8fc109cef21440de24c26c972fabf932585e618694d590379ab8d22def8da65a54459d38ec46112ead7 languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-optimise-call-expression@npm:7.25.9" +"@babel/helper-optimise-call-expression@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" dependencies: - "@babel/types": "npm:^7.25.9" - checksum: 10c0/90203e6607edeadd2a154940803fd616c0ed92c1013d6774c4b8eb491f1a5a3448b68faae6268141caa5c456e55e3ee49a4ed2bd7ddaf2365daea321c435914c + "@babel/types": "npm:^7.27.1" + checksum: 10c0/6b861e7fcf6031b9c9fc2de3cd6c005e94a459d6caf3621d93346b52774925800ca29d4f64595a5ceacf4d161eb0d27649ae385110ed69491d9776686fa488e6 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.25.9 - resolution: "@babel/helper-plugin-utils@npm:7.25.9" - checksum: 10c0/483066a1ba36ff16c0116cd24f93de05de746a603a777cd695ac7a1b034928a65a4ecb35f255761ca56626435d7abdb73219eba196f9aa83b6c3c3169325599d +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-replace-supers@npm:7.25.9" +"@babel/helper-replace-supers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-replace-supers@npm:7.27.1" dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.25.9" - "@babel/helper-optimise-call-expression": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-member-expression-to-functions": "npm:^7.27.1" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10c0/0b40d7d2925bd3ba4223b3519e2e4d2456d471ad69aa458f1c1d1783c80b522c61f8237d3a52afc9e47c7174129bbba650df06393a6787d5722f2ec7f223c3f4 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-simple-access@npm:7.25.9" - dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10c0/3f1bcdb88ee3883ccf86959869a867f6bbf8c4737cd44fb9f799c38e54f67474590bc66802500ae9fe18161792875b2cfb7ec15673f48ed6c8663f6d09686ca8 + checksum: 10c0/4f2eaaf5fcc196580221a7ccd0f8873447b5d52745ad4096418f6101a1d2e712e9f93722c9a32bc9769a1dc197e001f60d6f5438d4dfde4b9c6a9e4df719354c languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.9" +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10c0/09ace0c6156961624ac9524329ce7f45350bab94bbe24335cbe0da7dfaa1448e658771831983cb83fe91cf6635b15d0a3cab57c03b92657480bfb49fb56dd184 + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/f625013bcdea422c470223a2614e90d2c1cc9d832e97f32ca1b4f82b34bb4aa67c3904cb4b116375d3b5b753acfb3951ed50835a1e832e7225295c7b0c24dff7 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-string-parser@npm:7.25.9" - checksum: 10c0/7244b45d8e65f6b4338a6a68a8556f2cb161b782343e97281a5f2b9b93e420cad0d9f5773a59d79f61d0c448913d06f6a2358a87f2e203cf112e3c5b53522ee6 +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-validator-identifier@npm:7.25.9" - checksum: 10c0/4fc6f830177b7b7e887ad3277ddb3b91d81e6c4a24151540d9d1023e8dc6b1c0505f0f0628ae653601eb4388a8db45c1c14b2c07a9173837aef7e4116456259d +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-validator-option@npm:7.25.9" - checksum: 10c0/27fb195d14c7dcb07f14e58fe77c44eea19a6a40a74472ec05c441478fa0bb49fa1c32b2d64be7a38870ee48ef6601bdebe98d512f0253aea0b39756c4014f3e +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 languageName: node linkType: hard -"@babel/helpers@npm:^7.26.10": - version: 7.27.0 - resolution: "@babel/helpers@npm:7.27.0" +"@babel/helpers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helpers@npm:7.27.1" dependencies: - "@babel/template": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10c0/a3c64fd2d8b164c041808826cc00769d814074ea447daaacaf2e3714b66d3f4237ef6e420f61d08f463d6608f3468c2ac5124ab7c68f704e20384def5ade95f4 + "@babel/template": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e078257b9342dae2c041ac050276c5a28701434ad09478e6dc6976abd99f721a5a92e4bebddcbca6b1c3a7e8acace56a946340c701aad5e7507d2c87446459ba languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.19.4, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": - version: 7.27.0 - resolution: "@babel/parser@npm:7.27.0" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.19.4, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.27.1, @babel/parser@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/parser@npm:7.27.2" dependencies: - "@babel/types": "npm:^7.27.0" + "@babel/types": "npm:^7.27.1" bin: parser: ./bin/babel-parser.js - checksum: 10c0/ba2ed3f41735826546a3ef2a7634a8d10351df221891906e59b29b0a0cd748f9b0e7a6f07576858a9de8e77785aad925c8389ddef146de04ea2842047c9d2859 + checksum: 10c0/3c06692768885c2f58207fc8c2cbdb4a44df46b7d93135a083f6eaa49310f7ced490ce76043a2a7606cdcc13f27e3d835e141b692f2f6337a2e7f43c1dbb04b4 languageName: node linkType: hard @@ -277,13 +267,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.25.9 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.25.9" + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/bbdf97ba088c3d482492f6c3376422752b1723ce32e3ac11b000faf3c942d68e418c8a911431cb05d5e300d008cc37cd5518e89807a5813c2ac8fdd82d171f8d + checksum: 10c0/e66f7a761b8360419bbb93ab67d87c8a97465ef4637a985ff682ce7ba6918b34b29d81190204cf908d0933058ee7b42737423cd8a999546c21b3aabad4affa9a languageName: node linkType: hard @@ -309,14 +299,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.25.9, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.25.9 - resolution: "@babel/plugin-syntax-jsx@npm:7.25.9" +"@babel/plugin-syntax-jsx@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/d56597aff4df39d3decda50193b6dfbe596ca53f437ff2934622ce19a743bf7f43492d3fb3308b0289f5cee2b825d99ceb56526a2b9e7b68bf04901546c5618c + checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 languageName: node linkType: hard @@ -408,131 +398,121 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.25.9, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.25.9 - resolution: "@babel/plugin-syntax-typescript@npm:7.25.9" +"@babel/plugin-syntax-typescript@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/5192ebe11bd46aea68b7a60fd9555465c59af7e279e71126788e59121b86e00b505816685ab4782abe159232b0f73854e804b54449820b0d950b397ee158caa2 + checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.13.8, @babel/plugin-transform-modules-commonjs@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.25.9" +"@babel/plugin-transform-modules-commonjs@npm:^7.13.8, @babel/plugin-transform-modules-commonjs@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-simple-access": "npm:^7.25.9" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/6ce771fb04d4810257fc8900374fece877dacaed74b05eaa16ad9224b390f43795c4d046cbe9ae304e1eb5aad035d37383895e3c64496d647c2128d183916e74 + checksum: 10c0/4def972dcd23375a266ea1189115a4ff61744b2c9366fc1de648b3fab2c650faf1a94092de93a33ff18858d2e6c4dddeeee5384cb42ba0129baeab01a5cdf1e2 languageName: node linkType: hard "@babel/plugin-transform-react-jsx-self@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-react-jsx-self@npm:7.25.9" + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/ce0e289f6af93d7c4dc6b385512199c5bb138ae61507b4d5117ba88b6a6b5092f704f1bdf80080b7d69b1b8c36649f2a0b250e8198667d4d30c08bbb1546bd99 + checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 languageName: node linkType: hard "@babel/plugin-transform-react-jsx-source@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-react-jsx-source@npm:7.25.9" + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/fc9ee08efc9be7cbd2cc6788bbf92579adf3cab37912481f1b915221be3d22b0613b5b36a721df5f4c0ab65efe8582fcf8673caab83e6e1ce4cc04ceebf57dfa + checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/plugin-transform-typescript@npm:7.25.9" +"@babel/plugin-transform-typescript@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-typescript@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.25.9" - "@babel/helper-create-class-features-plugin": "npm:^7.25.9" - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.9" - "@babel/plugin-syntax-typescript": "npm:^7.25.9" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/c607ddb45f7e33cfcb928aad05cb1b18b1ecb564d2329d8f8e427f75192511aa821dee42d26871f1bdffbd883853e150ba81436664646c6e6b13063e65ce1475 + checksum: 10c0/48f1db5de17a0f9fc365ff4fb046010aedc7aad813a7aa42fb73fcdab6442f9e700dde2cc0481086e01b0dae662ae4d3e965a52cde154f0f146d243a8ac68e93 languageName: node linkType: hard "@babel/preset-typescript@npm:^7.13.0": - version: 7.25.9 - resolution: "@babel/preset-typescript@npm:7.25.9" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" - "@babel/helper-validator-option": "npm:^7.25.9" - "@babel/plugin-syntax-jsx": "npm:^7.25.9" - "@babel/plugin-transform-modules-commonjs": "npm:^7.25.9" - "@babel/plugin-transform-typescript": "npm:^7.25.9" + version: 7.27.1 + resolution: "@babel/preset-typescript@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-option": "npm:^7.27.1" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" + "@babel/plugin-transform-typescript": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/cbcc5b4bf2891e367627338961113febbe58d361e9a03bd2c8340ede914870f74db35ee367cfd8d0fca0872149bfb58b090d0a4815de7c05d0a8abb3d961eead + checksum: 10c0/cba6ca793d915f8aff9fe2f13b0dfbf5fd3f2e9a17f17478ec9878e9af0d206dcfe93154b9fd353727f16c1dca7c7a3ceb4943f8d28b216235f106bc0fbbcaa3 languageName: node linkType: hard -"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": - version: 7.27.0 - resolution: "@babel/runtime@npm:7.27.0" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10c0/35091ea9de48bd7fd26fb177693d64f4d195eb58ab2b142b893b7f3fa0f1d7c677604d36499ae0621a3703f35ba0c6a8f6c572cc8f7dc0317213841e493cf663 +"@babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.27.1 + resolution: "@babel/runtime@npm:7.27.1" + checksum: 10c0/530a7332f86ac5a7442250456823a930906911d895c0b743bf1852efc88a20a016ed4cd26d442d0ca40ae6d5448111e02a08dd638a4f1064b47d080e2875dc05 languageName: node linkType: hard -"@babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": - version: 7.27.0 - resolution: "@babel/template@npm:7.27.0" +"@babel/template@npm:^7.27.1, @babel/template@npm:^7.3.3": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10c0/13af543756127edb5f62bf121f9b093c09a2b6fe108373887ccffc701465cfbcb17e07cf48aa7f440415b263f6ec006e9415c79dfc2e8e6010b069435f81f340 + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 languageName: node linkType: hard -"@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10": - version: 7.27.0 - resolution: "@babel/traverse@npm:7.27.0" +"@babel/traverse@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/traverse@npm:7.27.1" dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.27.0" - "@babel/parser": "npm:^7.27.0" - "@babel/template": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.1" + "@babel/template": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10c0/c7af29781960dacaae51762e8bc6c4b13d6ab4b17312990fbca9fc38e19c4ad7fecaae24b1cf52fb844e8e6cdc76c70ad597f90e496bcb3cc0a1d66b41a0aa5b + checksum: 10c0/d912110037b03b1d70a2436cfd51316d930366a5f54252da2bced1ba38642f644f848240a951e5caf12f1ef6c40d3d96baa92ea6e84800f2e891c15e97b25d50 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3": - version: 7.27.0 - resolution: "@babel/types@npm:7.27.0" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.3.3": + version: 7.27.1 + resolution: "@babel/types@npm:7.27.1" dependencies: - "@babel/helper-string-parser": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10c0/6f1592eabe243c89a608717b07b72969be9d9d2fce1dee21426238757ea1fa60fdfc09b29de9e48d8104311afc6e6fb1702565a9cc1e09bc1e76f2b2ddb0f6e1 - languageName: node - linkType: hard - -"@bcoe/v8-coverage@npm:^0.2.3": - version: 0.2.3 - resolution: "@bcoe/v8-coverage@npm:0.2.3" - checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/ed736f14db2fdf0d36c539c8e06b6bb5e8f9649a12b5c0e1c516fed827f27ef35085abe08bf4d1302a4e20c9a254e762eed453bce659786d4a6e01ba26a91377 languageName: node linkType: hard @@ -644,6 +624,20 @@ __metadata: languageName: node linkType: hard +"@dimforge/rapier3d-compat@npm:^0.12.0": + version: 0.12.0 + resolution: "@dimforge/rapier3d-compat@npm:0.12.0" + checksum: 10c0/c66c24f90649c0fc870679c12e7fec1a111080d44450169b57561f957d7b6b284ad8a3ceeba95533e213176ea171351acebd3dd43885fafb33f18bfbd9d507db + languageName: node + linkType: hard + +"@discoveryjs/json-ext@npm:0.5.7, @discoveryjs/json-ext@npm:^0.5.7": + version: 0.5.7 + resolution: "@discoveryjs/json-ext@npm:0.5.7" + checksum: 10c0/e10f1b02b78e4812646ddf289b7d9f2cb567d336c363b266bd50cd223cf3de7c2c74018d91cd2613041568397ef3a4a2b500aba588c6e5bd78c38374ba68f38c + languageName: node + linkType: hard + "@epilot/esbuild-jest@npm:^0.5.2": version: 0.5.2 resolution: "@epilot/esbuild-jest@npm:0.5.2" @@ -658,189 +652,203 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/aix-ppc64@npm:0.25.0" +"@es-joy/jsdoccomment@npm:^0.50.1": + version: 0.50.1 + resolution: "@es-joy/jsdoccomment@npm:0.50.1" + dependencies: + "@types/eslint": "npm:^9.6.1" + "@types/estree": "npm:^1.0.6" + "@typescript-eslint/types": "npm:^8.11.0" + comment-parser: "npm:1.4.1" + esquery: "npm:^1.6.0" + jsdoc-type-pratt-parser: "npm:~4.1.0" + checksum: 10c0/45152672acb866b30b47e1b6d97e0a1e8d40d522e60d5acc1d1c5eb24190426d49f8aa51da903433b64cba583d92e238b0394bcc609938211c2519bcfe623203 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/aix-ppc64@npm:0.25.4" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm64@npm:0.25.0" +"@esbuild/android-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm64@npm:0.25.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm@npm:0.25.0" +"@esbuild/android-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm@npm:0.25.4" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-x64@npm:0.25.0" +"@esbuild/android-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-x64@npm:0.25.4" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-arm64@npm:0.25.0" +"@esbuild/darwin-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-arm64@npm:0.25.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-x64@npm:0.25.0" +"@esbuild/darwin-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-x64@npm:0.25.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-arm64@npm:0.25.0" +"@esbuild/freebsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-arm64@npm:0.25.4" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-x64@npm:0.25.0" +"@esbuild/freebsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-x64@npm:0.25.4" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm64@npm:0.25.0" +"@esbuild/linux-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm64@npm:0.25.4" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm@npm:0.25.0" +"@esbuild/linux-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm@npm:0.25.4" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ia32@npm:0.25.0" +"@esbuild/linux-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ia32@npm:0.25.4" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-loong64@npm:0.25.0" +"@esbuild/linux-loong64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-loong64@npm:0.25.4" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-mips64el@npm:0.25.0" +"@esbuild/linux-mips64el@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-mips64el@npm:0.25.4" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ppc64@npm:0.25.0" +"@esbuild/linux-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ppc64@npm:0.25.4" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-riscv64@npm:0.25.0" +"@esbuild/linux-riscv64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-riscv64@npm:0.25.4" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-s390x@npm:0.25.0" +"@esbuild/linux-s390x@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-s390x@npm:0.25.4" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-x64@npm:0.25.0" +"@esbuild/linux-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-x64@npm:0.25.4" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-arm64@npm:0.25.0" +"@esbuild/netbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-arm64@npm:0.25.4" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-x64@npm:0.25.0" +"@esbuild/netbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-x64@npm:0.25.4" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-arm64@npm:0.25.0" +"@esbuild/openbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-arm64@npm:0.25.4" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-x64@npm:0.25.0" +"@esbuild/openbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-x64@npm:0.25.4" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/sunos-x64@npm:0.25.0" +"@esbuild/sunos-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/sunos-x64@npm:0.25.4" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-arm64@npm:0.25.0" +"@esbuild/win32-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-arm64@npm:0.25.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-ia32@npm:0.25.0" +"@esbuild/win32-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-ia32@npm:0.25.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-x64@npm:0.25.0" +"@esbuild/win32-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-x64@npm:0.25.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": + version: 4.7.0 + resolution: "@eslint-community/eslint-utils@npm:4.7.0" dependencies: - eslint-visitor-keys: "npm:^3.3.0" + eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e + checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf languageName: node linkType: hard @@ -862,19 +870,19 @@ __metadata: languageName: node linkType: hard -"@eslint/config-helpers@npm:^0.2.0": - version: 0.2.1 - resolution: "@eslint/config-helpers@npm:0.2.1" - checksum: 10c0/3e829a78b0bb4f7c44384ba1df3986e5de24b7f440ad5c6bb3cfc366ded773a869ca9ee8d212b5a563ae94596c5940dea6fd2ea1ee53a84c6241ac953dcb8bb7 +"@eslint/config-helpers@npm:^0.2.1": + version: 0.2.2 + resolution: "@eslint/config-helpers@npm:0.2.2" + checksum: 10c0/98f7cefe484bb754674585d9e73cf1414a3ab4fd0783c385465288d13eb1a8d8e7d7b0611259fc52b76b396c11a13517be5036d1f48eeb877f6f0a6b9c4f03ad languageName: node linkType: hard -"@eslint/core@npm:^0.12.0": - version: 0.12.0 - resolution: "@eslint/core@npm:0.12.0" +"@eslint/core@npm:^0.13.0": + version: 0.13.0 + resolution: "@eslint/core@npm:0.13.0" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/d032af81195bb28dd800c2b9617548c6c2a09b9490da3c5537fd2a1201501666d06492278bb92cfccac1f7ac249e58601dd87f813ec0d6a423ef0880434fa0c3 + checksum: 10c0/ba724a7df7ed9dab387481f11d0d0f708180f40be93acce2c21dacca625c5867de3528760c42f1c457ccefe6a669d525ff87b779017eabc0d33479a36300797b languageName: node linkType: hard @@ -895,10 +903,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.24.0": - version: 9.24.0 - resolution: "@eslint/js@npm:9.24.0" - checksum: 10c0/efe22e29469e4140ac3e2916be8143b1bcfd1084a6edf692b7a58a3e54949d53c67f7f979bc0a811db134d9cc1e7bff8aa71ef1376b47eecd7e226b71206bb36 +"@eslint/js@npm:9.26.0": + version: 9.26.0 + resolution: "@eslint/js@npm:9.26.0" + checksum: 10c0/89fa45b7ff7f3c2589ea1f04a31b4f6d41ad85ecac98e519195e8b3a908b103c892ac19c4aec0629cfeccefd9e5b63c2f1269183d63016e7de722b97a085dcf4 languageName: node linkType: hard @@ -909,13 +917,26 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.2.7": - version: 0.2.7 - resolution: "@eslint/plugin-kit@npm:0.2.7" +"@eslint/plugin-kit@npm:^0.2.8": + version: 0.2.8 + resolution: "@eslint/plugin-kit@npm:0.2.8" dependencies: - "@eslint/core": "npm:^0.12.0" + "@eslint/core": "npm:^0.13.0" levn: "npm:^0.4.1" - checksum: 10c0/0a1aff1ad63e72aca923217e556c6dfd67d7cd121870eb7686355d7d1475d569773528a8b2111b9176f3d91d2ea81f7413c34600e8e5b73d59e005d70780b633 + checksum: 10c0/554847c8f2b6bfe0e634f317fc43d0b54771eea0015c4f844f75915fdb9e6170c830c004291bad57db949d61771732e459f36ed059f45cf750af223f77357c5c + languageName: node + linkType: hard + +"@gerrit0/mini-shiki@npm:^3.2.2": + version: 3.4.0 + resolution: "@gerrit0/mini-shiki@npm:3.4.0" + dependencies: + "@shikijs/engine-oniguruma": "npm:^3.4.0" + "@shikijs/langs": "npm:^3.4.0" + "@shikijs/themes": "npm:^3.4.0" + "@shikijs/types": "npm:^3.4.0" + "@shikijs/vscode-textmate": "npm:^10.0.2" + checksum: 10c0/8426cef64510e6174e010885a133cb2f5a2cd9fb2a033ea527d14e73ab5c03a5053ba87e7dcd4cfbfeab0bd22a69bb9354e9e15c83ea053f5590c369699bf4ef languageName: node linkType: hard @@ -951,9 +972,9 @@ __metadata: linkType: hard "@humanwhocodes/retry@npm:^0.4.2": - version: 0.4.2 - resolution: "@humanwhocodes/retry@npm:0.4.2" - checksum: 10c0/0235525d38f243bee3bf8b25ed395fbf957fb51c08adae52787e1325673071abe856c7e18e530922ed2dd3ce12ed82ba01b8cee0279ac52a3315fcdc3a69ef0c + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 languageName: node linkType: hard @@ -993,206 +1014,13 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": +"@istanbuljs/schema@npm:^0.1.2": version: 0.1.3 resolution: "@istanbuljs/schema@npm:0.1.3" checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a languageName: node linkType: hard -"@jest/console@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/console@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - slash: "npm:^3.0.0" - checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c - languageName: node - linkType: hard - -"@jest/core@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/core@npm:29.7.0" - dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/reporters": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-changed-files: "npm:^29.7.0" - jest-config: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-resolve-dependencies: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-ansi: "npm:^6.0.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 - languageName: node - linkType: hard - -"@jest/environment@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/environment@npm:29.7.0" - dependencies: - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 - languageName: node - linkType: hard - -"@jest/expect-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect-utils@npm:29.7.0" - dependencies: - jest-get-type: "npm:^29.6.3" - checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a - languageName: node - linkType: hard - -"@jest/expect@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect@npm:29.7.0" - dependencies: - expect: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e - languageName: node - linkType: hard - -"@jest/fake-timers@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/fake-timers@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@sinonjs/fake-timers": "npm:^10.0.2" - "@types/node": "npm:*" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c - languageName: node - linkType: hard - -"@jest/globals@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/globals@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - jest-mock: "npm:^29.7.0" - checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea - languageName: node - linkType: hard - -"@jest/reporters@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/reporters@npm:29.7.0" - dependencies: - "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - collect-v8-coverage: "npm:^1.0.0" - exit: "npm:^0.1.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - istanbul-lib-coverage: "npm:^3.0.0" - istanbul-lib-instrument: "npm:^6.0.0" - istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^4.0.0" - istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - slash: "npm:^3.0.0" - string-length: "npm:^4.0.1" - strip-ansi: "npm:^6.0.0" - v8-to-istanbul: "npm:^9.0.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 - languageName: node - linkType: hard - -"@jest/schemas@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/schemas@npm:29.6.3" - dependencies: - "@sinclair/typebox": "npm:^0.27.8" - checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be - languageName: node - linkType: hard - -"@jest/source-map@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/source-map@npm:29.6.3" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.18" - callsites: "npm:^3.0.0" - graceful-fs: "npm:^4.2.9" - checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 - languageName: node - linkType: hard - -"@jest/test-result@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-result@npm:29.7.0" - dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - collect-v8-coverage: "npm:^1.0.0" - checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 - languageName: node - linkType: hard - -"@jest/test-sequencer@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-sequencer@npm:29.7.0" - dependencies: - "@jest/test-result": "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - slash: "npm:^3.0.0" - checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b - languageName: node - linkType: hard - "@jest/transform@npm:^26.6.2": version: 26.6.2 resolution: "@jest/transform@npm:26.6.2" @@ -1216,29 +1044,6 @@ __metadata: languageName: node linkType: hard -"@jest/transform@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/transform@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" - babel-plugin-istanbul: "npm:^6.1.1" - chalk: "npm:^4.0.0" - convert-source-map: "npm:^2.0.0" - fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pirates: "npm:^4.0.4" - slash: "npm:^3.0.0" - write-file-atomic: "npm:^4.0.2" - checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 - languageName: node - linkType: hard - "@jest/types@npm:^26.6.2": version: 26.6.2 resolution: "@jest/types@npm:26.6.2" @@ -1252,20 +1057,6 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/types@npm:29.6.3" - dependencies: - "@jest/schemas": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - "@types/istanbul-reports": "npm:^3.0.0" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.8" - chalk: "npm:^4.0.0" - checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 - languageName: node - linkType: hard - "@joeychenofficial/alt-ergo-modified@npm:^2.4.0": version: 2.4.0 resolution: "@joeychenofficial/alt-ergo-modified@npm:2.4.0" @@ -1274,13 +1065,13 @@ __metadata: linkType: hard "@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" + version: 0.3.8 + resolution: "@jridgewell/gen-mapping@npm:0.3.8" dependencies: "@jridgewell/set-array": "npm:^1.2.1" "@jridgewell/sourcemap-codec": "npm:^1.4.10" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a languageName: node linkType: hard @@ -1298,14 +1089,14 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": version: 1.5.0 resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -1352,21 +1143,133 @@ __metadata: languageName: node linkType: hard -"@mediapipe/tasks-vision@npm:0.10.8": - version: 0.10.8 - resolution: "@mediapipe/tasks-vision@npm:0.10.8" - checksum: 10c0/17b54eac84c820a39740893ba732ad9913a90cc1911b1573893b63f48671d2651a5249e13914efbde90af71089b35a9fac765113d82c9d0765ec00ccb0e5b156 +"@jsonjoy.com/base64@npm:^1.1.1": + version: 1.1.2 + resolution: "@jsonjoy.com/base64@npm:1.1.2" + peerDependencies: + tslib: 2 + checksum: 10c0/88717945f66dc89bf58ce75624c99fe6a5c9a0c8614e26d03e406447b28abff80c69fb37dabe5aafef1862cf315071ae66e5c85f6018b437d95f8d13d235e6eb languageName: node linkType: hard -"@monogrid/gainmap-js@npm:^3.0.5": - version: 3.0.6 - resolution: "@monogrid/gainmap-js@npm:3.0.6" +"@jsonjoy.com/json-pack@npm:^1.0.3": + version: 1.2.0 + resolution: "@jsonjoy.com/json-pack@npm:1.2.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.1" + "@jsonjoy.com/util": "npm:^1.1.2" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^1.20.0" + peerDependencies: + tslib: 2 + checksum: 10c0/0744cfe2f54d896003ad240f0f069b41a152feb53b6134c5e65961126b9e5fdfc74a46f63b1dfa280e80a3d176c57e06de072bf03d749ec1982e41677a1ce5d5 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.1.2, @jsonjoy.com/util@npm:^1.3.0": + version: 1.6.0 + resolution: "@jsonjoy.com/util@npm:1.6.0" + peerDependencies: + tslib: 2 + checksum: 10c0/98182d8a5a0f5e04cdf755dacb523ba5e3e6a81e4941cbfeb157f8954c0e90e2e972fc7237c2378995fc3fa9f2b2649d28b197f556da3b5a80e56c6966c559e3 + languageName: node + linkType: hard + +"@leichtgewicht/ip-codec@npm:^2.0.1": + version: 2.0.5 + resolution: "@leichtgewicht/ip-codec@npm:2.0.5" + checksum: 10c0/14a0112bd59615eef9e3446fea018045720cd3da85a98f801a685a818b0d96ef2a1f7227e8d271def546b2e2a0fe91ef915ba9dc912ab7967d2317b1a051d66b + languageName: node + linkType: hard + +"@mediapipe/tasks-vision@npm:0.10.17": + version: 0.10.17 + resolution: "@mediapipe/tasks-vision@npm:0.10.17" + checksum: 10c0/f2f5dd9ca39d562b902a6d964f8b786f17a143f42776d0d26b8c79632b10cf210e28d98ec92de757b7bb2e6595ed4f9bbf5e49f1709b09dc08709bd276ced442 + languageName: node + linkType: hard + +"@modelcontextprotocol/sdk@npm:^1.8.0": + version: 1.11.2 + resolution: "@modelcontextprotocol/sdk@npm:1.11.2" + dependencies: + content-type: "npm:^1.0.5" + cors: "npm:^2.8.5" + cross-spawn: "npm:^7.0.3" + eventsource: "npm:^3.0.2" + express: "npm:^5.0.1" + express-rate-limit: "npm:^7.5.0" + pkce-challenge: "npm:^5.0.0" + raw-body: "npm:^3.0.0" + zod: "npm:^3.23.8" + zod-to-json-schema: "npm:^3.24.1" + checksum: 10c0/18e49f42138303075e710b744e8ee958033b29e9a75852def3a320788bfe79128743a439189fad71ed4fd8fcc6c291ae7e38d41a22ed82585569cc6f5965e938 + languageName: node + linkType: hard + +"@module-federation/error-codes@npm:0.13.1": + version: 0.13.1 + resolution: "@module-federation/error-codes@npm:0.13.1" + checksum: 10c0/335b3df2412be7e71a8d1bb97e88904ebfa52d2dbc65f4dee244d50fa7e2a9b7398be1c7c6932e6f0821958cc9aa3409c43401f2c213a47bbeba46f600faeacd + languageName: node + linkType: hard + +"@module-federation/runtime-core@npm:0.13.1": + version: 0.13.1 + resolution: "@module-federation/runtime-core@npm:0.13.1" + dependencies: + "@module-federation/error-codes": "npm:0.13.1" + "@module-federation/sdk": "npm:0.13.1" + checksum: 10c0/bb23849c9f688ab3eadb07b48f194fbf745a947744dec2589d4307efaa2cb6a49b393b20a03537b174fe573ef1981fbba964723d174974a8cf584a4303015f4b + languageName: node + linkType: hard + +"@module-federation/runtime-tools@npm:0.13.1": + version: 0.13.1 + resolution: "@module-federation/runtime-tools@npm:0.13.1" + dependencies: + "@module-federation/runtime": "npm:0.13.1" + "@module-federation/webpack-bundler-runtime": "npm:0.13.1" + checksum: 10c0/5390d1b6bae2cc926db184cb8243c2204f469c7bb3ad697dfc8258b0ecb77311291cfdd7a54cafc36f11559da2c2c6fdfdfd1ef1919afd236c973641c2d53b0a + languageName: node + linkType: hard + +"@module-federation/runtime@npm:0.13.1": + version: 0.13.1 + resolution: "@module-federation/runtime@npm:0.13.1" + dependencies: + "@module-federation/error-codes": "npm:0.13.1" + "@module-federation/runtime-core": "npm:0.13.1" + "@module-federation/sdk": "npm:0.13.1" + checksum: 10c0/6daf50f5f426b4f6addbf2ed7285b25dc8f7d74c43d94c67bb51fe989437b663b2f08e6289bc2698b07409081207d7b19328b98b347b947ecb4a8439a1350202 + languageName: node + linkType: hard + +"@module-federation/sdk@npm:0.13.1": + version: 0.13.1 + resolution: "@module-federation/sdk@npm:0.13.1" + checksum: 10c0/575810856499dbe058dd350ed739f41b7c93593f673b431c2f524ba1aaf000d4b5b8b3fda95d249b733daf335fb75a7cc0f0fce7e40997ff8625f0d2cb4ca2b6 + languageName: node + linkType: hard + +"@module-federation/webpack-bundler-runtime@npm:0.13.1": + version: 0.13.1 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.13.1" + dependencies: + "@module-federation/runtime": "npm:0.13.1" + "@module-federation/sdk": "npm:0.13.1" + checksum: 10c0/84f2840390b7aa3034e239136bfce920ab3b09436f5a72e912bd4709141a452b2d21c5f9378c765513870eb0b98452ba88b07fd8c80fa07c3adfc2f99d0628a5 + languageName: node + linkType: hard + +"@monogrid/gainmap-js@npm:^3.0.6": + version: 3.1.0 + resolution: "@monogrid/gainmap-js@npm:3.1.0" dependencies: promise-worker-transferable: "npm:^1.0.4" peerDependencies: three: ">= 0.159.0" - checksum: 10c0/35f25e50b15fb55c4d38d08334337bbeff784a2b4a6c4069c1eaee49d1c4c27b2119e938efaa1417cfd9c99902355b0aaf8d9b9fc296748eb4cd26de81dd43f3 + checksum: 10c0/0afae39fdca31456a57803d5751c0e5b742d8dfac53aa95b21b43fbf02ad250cd80f5ae0368071128729754b89ff1be6317e1e23be86706c0d3e227bb5f82db2 languageName: node linkType: hard @@ -1419,106 +1322,114 @@ __metadata: languageName: node linkType: hard -"@parcel/watcher-android-arm64@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-android-arm64@npm:2.4.1" +"@parcel/watcher-android-arm64@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-android-arm64@npm:2.5.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@parcel/watcher-darwin-arm64@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-darwin-arm64@npm:2.4.1" +"@parcel/watcher-darwin-arm64@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-darwin-arm64@npm:2.5.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@parcel/watcher-darwin-x64@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-darwin-x64@npm:2.4.1" +"@parcel/watcher-darwin-x64@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-darwin-x64@npm:2.5.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@parcel/watcher-freebsd-x64@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-freebsd-x64@npm:2.4.1" +"@parcel/watcher-freebsd-x64@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-freebsd-x64@npm:2.5.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@parcel/watcher-linux-arm-glibc@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-linux-arm-glibc@npm:2.4.1" +"@parcel/watcher-linux-arm-glibc@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-linux-arm-glibc@npm:2.5.1" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@parcel/watcher-linux-arm64-glibc@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.4.1" +"@parcel/watcher-linux-arm-musl@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-linux-arm-musl@npm:2.5.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-glibc@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.5.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@parcel/watcher-linux-arm64-musl@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-linux-arm64-musl@npm:2.4.1" +"@parcel/watcher-linux-arm64-musl@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-linux-arm64-musl@npm:2.5.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@parcel/watcher-linux-x64-glibc@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-linux-x64-glibc@npm:2.4.1" +"@parcel/watcher-linux-x64-glibc@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-linux-x64-glibc@npm:2.5.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@parcel/watcher-linux-x64-musl@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-linux-x64-musl@npm:2.4.1" +"@parcel/watcher-linux-x64-musl@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-linux-x64-musl@npm:2.5.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@parcel/watcher-win32-arm64@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-win32-arm64@npm:2.4.1" +"@parcel/watcher-win32-arm64@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-win32-arm64@npm:2.5.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@parcel/watcher-win32-ia32@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-win32-ia32@npm:2.4.1" +"@parcel/watcher-win32-ia32@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-win32-ia32@npm:2.5.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@parcel/watcher-win32-x64@npm:2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher-win32-x64@npm:2.4.1" +"@parcel/watcher-win32-x64@npm:2.5.1": + version: 2.5.1 + resolution: "@parcel/watcher-win32-x64@npm:2.5.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@parcel/watcher@npm:^2.4.1": - version: 2.4.1 - resolution: "@parcel/watcher@npm:2.4.1" - dependencies: - "@parcel/watcher-android-arm64": "npm:2.4.1" - "@parcel/watcher-darwin-arm64": "npm:2.4.1" - "@parcel/watcher-darwin-x64": "npm:2.4.1" - "@parcel/watcher-freebsd-x64": "npm:2.4.1" - "@parcel/watcher-linux-arm-glibc": "npm:2.4.1" - "@parcel/watcher-linux-arm64-glibc": "npm:2.4.1" - "@parcel/watcher-linux-arm64-musl": "npm:2.4.1" - "@parcel/watcher-linux-x64-glibc": "npm:2.4.1" - "@parcel/watcher-linux-x64-musl": "npm:2.4.1" - "@parcel/watcher-win32-arm64": "npm:2.4.1" - "@parcel/watcher-win32-ia32": "npm:2.4.1" - "@parcel/watcher-win32-x64": "npm:2.4.1" + version: 2.5.1 + resolution: "@parcel/watcher@npm:2.5.1" + dependencies: + "@parcel/watcher-android-arm64": "npm:2.5.1" + "@parcel/watcher-darwin-arm64": "npm:2.5.1" + "@parcel/watcher-darwin-x64": "npm:2.5.1" + "@parcel/watcher-freebsd-x64": "npm:2.5.1" + "@parcel/watcher-linux-arm-glibc": "npm:2.5.1" + "@parcel/watcher-linux-arm-musl": "npm:2.5.1" + "@parcel/watcher-linux-arm64-glibc": "npm:2.5.1" + "@parcel/watcher-linux-arm64-musl": "npm:2.5.1" + "@parcel/watcher-linux-x64-glibc": "npm:2.5.1" + "@parcel/watcher-linux-x64-musl": "npm:2.5.1" + "@parcel/watcher-win32-arm64": "npm:2.5.1" + "@parcel/watcher-win32-ia32": "npm:2.5.1" + "@parcel/watcher-win32-x64": "npm:2.5.1" detect-libc: "npm:^1.0.3" is-glob: "npm:^4.0.3" micromatch: "npm:^4.0.5" @@ -1535,6 +1446,8 @@ __metadata: optional: true "@parcel/watcher-linux-arm-glibc": optional: true + "@parcel/watcher-linux-arm-musl": + optional: true "@parcel/watcher-linux-arm64-glibc": optional: true "@parcel/watcher-linux-arm64-musl": @@ -1549,7 +1462,7 @@ __metadata: optional: true "@parcel/watcher-win32-x64": optional: true - checksum: 10c0/33b7112094b9eb46c234d824953967435b628d3d93a0553255e9910829b84cab3da870153c3a870c31db186dc58f3b2db81382fcaee3451438aeec4d786a6211 + checksum: 10c0/8f35073d0c0b34a63d4c8d2213482f0ebc6a25de7b2cdd415d19cb929964a793cb285b68d1d50bfb732b070b3c82a2fdb4eb9c250eab709a1cd9d63345455a82 languageName: node linkType: hard @@ -1560,6 +1473,13 @@ __metadata: languageName: node linkType: hard +"@polka/url@npm:^1.0.0-next.24": + version: 1.0.0-next.29 + resolution: "@polka/url@npm:1.0.0-next.29" + checksum: 10c0/0d58e081844095cb029d3c19a659bfefd09d5d51a2f791bc61eba7ea826f13d6ee204a8a448c2f5a855c17df07b37517373ff916dd05801063c0568ae9937684 + languageName: node + linkType: hard + "@popperjs/core@npm:^2.11.8": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" @@ -1579,6 +1499,18 @@ __metadata: languageName: node linkType: hard +"@react-spring/animated@npm:~9.7.5": + version: 9.7.5 + resolution: "@react-spring/animated@npm:9.7.5" + dependencies: + "@react-spring/shared": "npm:~9.7.5" + "@react-spring/types": "npm:~9.7.5" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/f8c2473c60f39a878c7dd0fdfcfcdbc720521e1506aa3f63c9de64780694a0a73d5ccc535a5ccec3520ddb70a71cf43b038b32c18e99531522da5388c510ecd7 + languageName: node + linkType: hard + "@react-spring/core@npm:~9.6.1": version: 9.6.1 resolution: "@react-spring/core@npm:9.6.1" @@ -1593,6 +1525,19 @@ __metadata: languageName: node linkType: hard +"@react-spring/core@npm:~9.7.5": + version: 9.7.5 + resolution: "@react-spring/core@npm:9.7.5" + dependencies: + "@react-spring/animated": "npm:~9.7.5" + "@react-spring/shared": "npm:~9.7.5" + "@react-spring/types": "npm:~9.7.5" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/5bfd83dfe248cd91889f215f015d908c7714ef445740fd5afa054b27ebc7d5a456abf6c309e2459d9b5b436e78d6fda16b62b9601f96352e9130552c02270830 + languageName: node + linkType: hard + "@react-spring/rafz@npm:~9.6.1": version: 9.6.1 resolution: "@react-spring/rafz@npm:9.6.1" @@ -1600,6 +1545,13 @@ __metadata: languageName: node linkType: hard +"@react-spring/rafz@npm:~9.7.5": + version: 9.7.5 + resolution: "@react-spring/rafz@npm:9.7.5" + checksum: 10c0/8bdad180feaa9a0e870a513043a5e98a4e9b7292a9f887575b7e6fadab2677825bc894b7ff16c38511b35bfe6cc1072df5851c5fee64448d67551559578ca759 + languageName: node + linkType: hard + "@react-spring/shared@npm:~9.6.1": version: 9.6.1 resolution: "@react-spring/shared@npm:9.6.1" @@ -1612,7 +1564,19 @@ __metadata: languageName: node linkType: hard -"@react-spring/three@npm:9.6.1, @react-spring/three@npm:~9.6.1": +"@react-spring/shared@npm:~9.7.5": + version: 9.7.5 + resolution: "@react-spring/shared@npm:9.7.5" + dependencies: + "@react-spring/rafz": "npm:~9.7.5" + "@react-spring/types": "npm:~9.7.5" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/0207eacccdedd918a2fc55e78356ce937f445ce27ad9abd5d3accba8f9701a39349b55115641dc2b39bb9d3a155b058c185b411d292dc8cc5686bfa56f73b94f + languageName: node + linkType: hard + +"@react-spring/three@npm:9.6.1": version: 9.6.1 resolution: "@react-spring/three@npm:9.6.1" dependencies: @@ -1628,6 +1592,22 @@ __metadata: languageName: node linkType: hard +"@react-spring/three@npm:~9.7.5": + version: 9.7.5 + resolution: "@react-spring/three@npm:9.7.5" + dependencies: + "@react-spring/animated": "npm:~9.7.5" + "@react-spring/core": "npm:~9.7.5" + "@react-spring/shared": "npm:~9.7.5" + "@react-spring/types": "npm:~9.7.5" + peerDependencies: + "@react-three/fiber": ">=6.0" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + three: ">=0.126" + checksum: 10c0/793f2c27a55060b015880e661ede045f240e4be9b85f448616fb0f8f282bed21d08fb2389a70c73addffbdb868c38bc26d8c6f6f55dd2b0ef67ba6c5a11a55d9 + languageName: node + linkType: hard + "@react-spring/types@npm:~9.6.1": version: 9.6.1 resolution: "@react-spring/types@npm:9.6.1" @@ -1635,58 +1615,63 @@ __metadata: languageName: node linkType: hard +"@react-spring/types@npm:~9.7.5": + version: 9.7.5 + resolution: "@react-spring/types@npm:9.7.5" + checksum: 10c0/85c05121853cacb64f7cf63a4855e9044635e1231f70371cd7b8c78bc10be6f4dd7c68f592f92a2607e8bb68051540989b4677a2ccb525dba937f5cd95dc8bc1 + languageName: node + linkType: hard + "@react-three/drei@npm:^9.97.0": - version: 9.114.6 - resolution: "@react-three/drei@npm:9.114.6" - dependencies: - "@babel/runtime": "npm:^7.11.2" - "@mediapipe/tasks-vision": "npm:0.10.8" - "@monogrid/gainmap-js": "npm:^3.0.5" - "@react-spring/three": "npm:~9.6.1" - "@use-gesture/react": "npm:^10.2.24" - camera-controls: "npm:^2.4.2" + version: 9.122.0 + resolution: "@react-three/drei@npm:9.122.0" + dependencies: + "@babel/runtime": "npm:^7.26.0" + "@mediapipe/tasks-vision": "npm:0.10.17" + "@monogrid/gainmap-js": "npm:^3.0.6" + "@react-spring/three": "npm:~9.7.5" + "@use-gesture/react": "npm:^10.3.1" + camera-controls: "npm:^2.9.0" cross-env: "npm:^7.0.3" - detect-gpu: "npm:^5.0.28" + detect-gpu: "npm:^5.0.56" glsl-noise: "npm:^0.0.0" hls.js: "npm:^1.5.17" - maath: "npm:^0.10.7" - meshline: "npm:^3.1.6" + maath: "npm:^0.10.8" + meshline: "npm:^3.3.1" react-composer: "npm:^5.0.3" - stats-gl: "npm:^2.0.0" + stats-gl: "npm:^2.2.8" stats.js: "npm:^0.17.0" suspend-react: "npm:^0.1.3" three-mesh-bvh: "npm:^0.7.8" - three-stdlib: "npm:^2.29.9" - troika-three-text: "npm:^0.49.0" + three-stdlib: "npm:^2.35.6" + troika-three-text: "npm:^0.52.0" tunnel-rat: "npm:^0.1.2" - utility-types: "npm:^3.10.0" - uuid: "npm:^9.0.1" - zustand: "npm:^3.7.1" + utility-types: "npm:^3.11.0" + zustand: "npm:^5.0.1" peerDependencies: - "@react-three/fiber": ">=8.0" - react: ">=18.0" - react-dom: ">=18.0" + "@react-three/fiber": ^8 + react: ^18 + react-dom: ^18 three: ">=0.137" peerDependenciesMeta: react-dom: optional: true - checksum: 10c0/28bfa2131c7fe9854be5edce3fe014c7918b84655e6b781fee49857522e81ff38f7fcaea9af0e0a912f0f042d000f0d4ee19497eaf1edd084aeaaa0f035d7fdc + checksum: 10c0/549844539bc22aa2a748e5ae77582ae7027f56110b0da3145f302a83ae3813cca158ba03e02a9f644787ae2d17f331bc47051c1e4ec798bb7234e8eb2eaf076a languageName: node linkType: hard "@react-three/fiber@npm:^8.15.16": - version: 8.17.10 - resolution: "@react-three/fiber@npm:8.17.10" + version: 8.18.0 + resolution: "@react-three/fiber@npm:8.18.0" dependencies: "@babel/runtime": "npm:^7.17.8" - "@types/debounce": "npm:^1.2.1" "@types/react-reconciler": "npm:^0.26.7" "@types/webxr": "npm:*" base64-js: "npm:^1.5.1" buffer: "npm:^6.0.3" - debounce: "npm:^1.2.1" its-fine: "npm:^1.0.6" react-reconciler: "npm:^0.27.0" + react-use-measure: "npm:^2.1.7" scheduler: "npm:^0.21.0" suspend-react: "npm:^0.1.3" zustand: "npm:^3.7.1" @@ -1695,8 +1680,8 @@ __metadata: expo-asset: ">=8.4" expo-file-system: ">=11.0" expo-gl: ">=11.0" - react: ">=18.0" - react-dom: ">=18.0" + react: ">=18 <19" + react-dom: ">=18 <19" react-native: ">=0.64" three: ">=0.133" peerDependenciesMeta: @@ -1712,7 +1697,7 @@ __metadata: optional: true react-native: optional: true - checksum: 10c0/650f1dac4e275e05757297e58b1413b46a322fd9b05ea381c94118ed2be582f132a905890aa41fca73a43d96752470859aa3f93c8b0b95175807d70aaef634a6 + checksum: 10c0/3203a8a36a47824755759813e1e5db7f2ab5de15891645d29eb352d599950141e8e8d24a1baf26670191d03a4e751d8941ab4d57f6fd697d94e030d0f17e1a40 languageName: node linkType: hard @@ -1731,177 +1716,1053 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.40.0" +"@rollup/rollup-android-arm-eabi@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.40.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-android-arm64@npm:4.40.0" +"@rollup/rollup-android-arm64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-android-arm64@npm:4.40.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-darwin-arm64@npm:4.40.0" +"@rollup/rollup-darwin-arm64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.40.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-darwin-x64@npm:4.40.0" +"@rollup/rollup-darwin-x64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.40.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.40.0" +"@rollup/rollup-freebsd-arm64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.40.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-freebsd-x64@npm:4.40.0" +"@rollup/rollup-freebsd-x64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.40.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.40.0" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.40.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.40.0" +"@rollup/rollup-linux-arm-musleabihf@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.40.2" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.40.0" +"@rollup/rollup-linux-arm64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.40.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.40.0" +"@rollup/rollup-linux-arm64-musl@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.40.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.40.0" +"@rollup/rollup-linux-loongarch64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.40.2" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.0" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.40.0" +"@rollup/rollup-linux-riscv64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.40.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.40.0" +"@rollup/rollup-linux-riscv64-musl@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.40.2" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.40.0" +"@rollup/rollup-linux-s390x-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.40.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.40.0" +"@rollup/rollup-linux-x64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.40.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.40.0" +"@rollup/rollup-linux-x64-musl@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.40.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.40.0" +"@rollup/rollup-win32-arm64-msvc@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.40.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.40.0" +"@rollup/rollup-win32-ia32-msvc@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.40.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.40.0": - version: 4.40.0 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.40.0" +"@rollup/rollup-win32-x64-msvc@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.40.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rtsao/scc@npm:^1.1.0": +"@rspack/binding-darwin-arm64@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-darwin-arm64@npm:1.3.10" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rspack/binding-darwin-x64@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-darwin-x64@npm:1.3.10" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding-linux-arm64-gnu@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.3.10" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rspack/binding-linux-arm64-musl@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.3.10" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rspack/binding-linux-x64-gnu@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.3.10" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rspack/binding-linux-x64-musl@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-linux-x64-musl@npm:1.3.10" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rspack/binding-win32-arm64-msvc@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.3.10" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rspack/binding-win32-ia32-msvc@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.3.10" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rspack/binding-win32-x64-msvc@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.3.10" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding@npm:1.3.10": + version: 1.3.10 + resolution: "@rspack/binding@npm:1.3.10" + dependencies: + "@rspack/binding-darwin-arm64": "npm:1.3.10" + "@rspack/binding-darwin-x64": "npm:1.3.10" + "@rspack/binding-linux-arm64-gnu": "npm:1.3.10" + "@rspack/binding-linux-arm64-musl": "npm:1.3.10" + "@rspack/binding-linux-x64-gnu": "npm:1.3.10" + "@rspack/binding-linux-x64-musl": "npm:1.3.10" + "@rspack/binding-win32-arm64-msvc": "npm:1.3.10" + "@rspack/binding-win32-ia32-msvc": "npm:1.3.10" + "@rspack/binding-win32-x64-msvc": "npm:1.3.10" + dependenciesMeta: + "@rspack/binding-darwin-arm64": + optional: true + "@rspack/binding-darwin-x64": + optional: true + "@rspack/binding-linux-arm64-gnu": + optional: true + "@rspack/binding-linux-arm64-musl": + optional: true + "@rspack/binding-linux-x64-gnu": + optional: true + "@rspack/binding-linux-x64-musl": + optional: true + "@rspack/binding-win32-arm64-msvc": + optional: true + "@rspack/binding-win32-ia32-msvc": + optional: true + "@rspack/binding-win32-x64-msvc": + optional: true + checksum: 10c0/a23f69709b7a70cbf9c15222ccfeba9064faf2831575ff020fd44d9310311efa5a401f20b43608888c2ad6b1638128deb4965442d5f621a8000f0aa6b82c03ff + languageName: node + linkType: hard + +"@rspack/cli@npm:^1.3.9": + version: 1.3.10 + resolution: "@rspack/cli@npm:1.3.10" + dependencies: + "@discoveryjs/json-ext": "npm:^0.5.7" + "@rspack/dev-server": "npm:1.1.1" + colorette: "npm:2.0.20" + exit-hook: "npm:^4.0.0" + interpret: "npm:^3.1.1" + rechoir: "npm:^0.8.0" + webpack-bundle-analyzer: "npm:4.10.2" + yargs: "npm:17.7.2" + peerDependencies: + "@rspack/core": ^1.0.0-alpha || ^1.x + bin: + rspack: bin/rspack.js + checksum: 10c0/241c0ca9fa602dd96a4e9eb150de235f2b36cd03cdc1924b520d169ab45616a316710c6236f238d270943f12aefee27e11cdf2e58658ab84b20d4ec79af85273 + languageName: node + linkType: hard + +"@rspack/core@npm:^1.3.9": + version: 1.3.10 + resolution: "@rspack/core@npm:1.3.10" + dependencies: + "@module-federation/runtime-tools": "npm:0.13.1" + "@rspack/binding": "npm:1.3.10" + "@rspack/lite-tapable": "npm:1.0.1" + caniuse-lite: "npm:^1.0.30001717" + peerDependencies: + "@swc/helpers": ">=0.5.1" + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10c0/71d11b824743d3c5c3bd203f2b020a89342899d3f0ce9d895ab9d89271c5bd9b05212de57c2eff1b1c431f485e70707c1678e28a7e1b069fe88002e40cb3226b + languageName: node + linkType: hard + +"@rspack/dev-server@npm:1.1.1": + version: 1.1.1 + resolution: "@rspack/dev-server@npm:1.1.1" + dependencies: + chokidar: "npm:^3.6.0" + express: "npm:^4.21.2" + http-proxy-middleware: "npm:^2.0.7" + mime-types: "npm:^2.1.35" + p-retry: "npm:^6.2.0" + webpack-dev-middleware: "npm:^7.4.2" + webpack-dev-server: "npm:5.2.0" + ws: "npm:^8.18.0" + peerDependencies: + "@rspack/core": "*" + checksum: 10c0/54599cddd510aa4ca5ec3ab0082ab20f0d9b104bd49332a2a411cc487ba1b74d59d4f640311dac21e0b4109fd420d54553372d3d5c0e5d0604697f3f2318e14b + languageName: node + linkType: hard + +"@rspack/lite-tapable@npm:1.0.1": + version: 1.0.1 + resolution: "@rspack/lite-tapable@npm:1.0.1" + checksum: 10c0/90bb1bc414dc51ea2d0933e09f78d25584f3f50a85f4cb8228930bd29e5931bf55eff4f348a06c51dd3149fc73b8ae3920bf0ae5ae8a0c9fe1d9b404e6ecf5b7 + languageName: node + linkType: hard + +"@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" checksum: 10c0/b5bcfb0d87f7d1c1c7c0f7693f53b07866ed9fec4c34a97a8c948fb9a7c0082e416ce4d3b60beb4f5e167cbe04cdeefbf6771320f3ede059b9ce91188c409a5b languageName: node linkType: hard -"@sinclair/typebox@npm:^0.27.8": - version: 0.27.8 - resolution: "@sinclair/typebox@npm:0.27.8" - checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e - languageName: node - linkType: hard +"@shikijs/engine-oniguruma@npm:^3.4.0": + version: 3.4.1 + resolution: "@shikijs/engine-oniguruma@npm:3.4.1" + dependencies: + "@shikijs/types": "npm:3.4.1" + "@shikijs/vscode-textmate": "npm:^10.0.2" + checksum: 10c0/2c3d928c57ce1a50b6d33d5968d50eb8e5290230b62295365aca7b2e147a6791551c437a858ba57dc0ed55fcf8c10c8e59f553909867d63cdaa6f3c1f0dd63dc + languageName: node + linkType: hard + +"@shikijs/langs@npm:^3.4.0": + version: 3.4.1 + resolution: "@shikijs/langs@npm:3.4.1" + dependencies: + "@shikijs/types": "npm:3.4.1" + checksum: 10c0/2eea12a6ece9808e1a9910f551267310cdd42dfd4f6495cb39032543de21140ac0b6a5a07e59801cf2563b3e62ea57c78ee1822115f86d0a6091f4b03e66fca5 + languageName: node + linkType: hard + +"@shikijs/themes@npm:^3.4.0": + version: 3.4.1 + resolution: "@shikijs/themes@npm:3.4.1" + dependencies: + "@shikijs/types": "npm:3.4.1" + checksum: 10c0/8e2830161d3dfa387136aca0045e9a62aae2b4e2b43f988d3ef6028c850ceba15c214822fdfb5c2d8cbef06b1aa8512143ccf8de9def2c3852a4b9970443decd + languageName: node + linkType: hard + +"@shikijs/types@npm:3.4.1, @shikijs/types@npm:^3.4.0": + version: 3.4.1 + resolution: "@shikijs/types@npm:3.4.1" + dependencies: + "@shikijs/vscode-textmate": "npm:^10.0.2" + "@types/hast": "npm:^3.0.4" + checksum: 10c0/5537d4e032b4c39826cfe08c4dec34a482e096f62b6b29dd453f74c2af3c66829cde966e2eb63f3871a51ae8c6f45ed21d9eebcb8464e38ab568845eb1b05788 + languageName: node + linkType: hard + +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10c0/36b682d691088ec244de292dc8f91b808f95c89466af421cf84cbab92230f03c8348649c14b3251991b10ce632b0c715e416e992dd5f28ff3221dc2693fd9462 + languageName: node + linkType: hard + +"@sourceacademy/bundle-ar@workspace:^, @sourceacademy/bundle-ar@workspace:src/bundles/ar": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-ar@workspace:src/bundles/ar" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + saar: "npm:^1.0.4" + typescript: "npm:^5.8.2" + uniqid: "npm:^5.4.0" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-arcade_2d@workspace:src/bundles/arcade_2d": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-arcade_2d@workspace:src/bundles/arcade_2d" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + phaser: "npm:^3.54.0" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-binary_tree@workspace:src/bundles/binary_tree": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-binary_tree@workspace:src/bundles/binary_tree" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-communication@workspace:src/bundles/communication": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-communication@workspace:src/bundles/communication" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + mqtt: "npm:^4.3.7" + typescript: "npm:^5.8.2" + uniqid: "npm:^5.4.0" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-copy_gc@workspace:src/bundles/copy_gc": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-copy_gc@workspace:src/bundles/copy_gc" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-csg@workspace:^, @sourceacademy/bundle-csg@workspace:src/bundles/csg": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-csg@workspace:src/bundles/csg" + dependencies: + "@jscad/modeling": "npm:2.9.6" + "@jscad/regl-renderer": "npm:^2.6.1" + "@jscad/stl-serializer": "npm:2.1.11" + "@sourceacademy/modules-buildtools": "workspace:^" + save-file: "npm:^2.3.1" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-curve@workspace:^, @sourceacademy/bundle-curve@workspace:src/bundles/curve": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-curve@workspace:src/bundles/curve" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + gl-matrix: "npm:^3.3.0" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-game@workspace:src/bundles/game": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-game@workspace:src/bundles/game" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + phaser: "npm:^3.54.0" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-mark_sweep@workspace:src/bundles/mark_sweep": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-mark_sweep@workspace:src/bundles/mark_sweep" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-nbody@workspace:src/bundles/nbody": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-nbody@workspace:src/bundles/nbody" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + nbody: "npm:^0.2.0" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-painter@workspace:^, @sourceacademy/bundle-painter@workspace:src/bundles/painter": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-painter@workspace:src/bundles/painter" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@types/plotly.js": "npm:^2.35.4" + plotly.js-dist: "npm:^2.17.1" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-physics_2d@workspace:^, @sourceacademy/bundle-physics_2d@workspace:src/bundles/physics_2d": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-physics_2d@workspace:src/bundles/physics_2d" + dependencies: + "@box2d/core": "npm:^0.10.0" + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-pix_n_flix@workspace:^, @sourceacademy/bundle-pix_n_flix@workspace:src/bundles/pix_n_flix": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-pix_n_flix@workspace:src/bundles/pix_n_flix" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-plotly@workspace:^, @sourceacademy/bundle-plotly@workspace:src/bundles/plotly": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-plotly@workspace:src/bundles/plotly" + dependencies: + "@sourceacademy/bundle-sound": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@types/plotly.js": "npm:^2.35.4" + js-slang: "npm:^1.0.81" + plotly.js-dist: "npm:^2.17.1" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-remote_execution@workspace:src/bundles/remote_execution": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-remote_execution@workspace:src/bundles/remote_execution" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-repeat@workspace:src/bundles/repeat": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-repeat@workspace:src/bundles/repeat" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-repl@workspace:src/bundles/repl": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-repl@workspace:src/bundles/repl" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-robot_simulation@workspace:^, @sourceacademy/bundle-robot_simulation@workspace:src/bundles/robot_simulation": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-robot_simulation@workspace:src/bundles/robot_simulation" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@types/three": "npm:^0.175.0" + three: "npm:^0.175.0" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-rune@workspace:^, @sourceacademy/bundle-rune@workspace:src/bundles/rune": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-rune@workspace:src/bundles/rune" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + gl-matrix: "npm:^3.3.0" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-rune_in_words@workspace:src/bundles/rune_in_words": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-rune_in_words@workspace:src/bundles/rune_in_words" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-scrabble@workspace:src/bundles/scrabble": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-scrabble@workspace:src/bundles/scrabble" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-sound@workspace:^, @sourceacademy/bundle-sound@workspace:src/bundles/sound": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-sound@workspace:src/bundles/sound" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-sound_matrix@workspace:src/bundles/sound_matrix": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-sound_matrix@workspace:src/bundles/sound_matrix" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-stereo_sound@workspace:^, @sourceacademy/bundle-stereo_sound@workspace:src/bundles/stereo_sound": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-stereo_sound@workspace:src/bundles/stereo_sound" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-unittest@workspace:^, @sourceacademy/bundle-unittest@workspace:src/bundles/unittest": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-unittest@workspace:src/bundles/unittest" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + js-slang: "npm:^1.0.81" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-unity_academy@workspace:^, @sourceacademy/bundle-unity_academy@workspace:src/bundles/unity_academy": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-unity_academy@workspace:src/bundles/unity_academy" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@sourceacademy/modules-buildtools": "workspace:^" + "@types/react": "npm:^18.3.2" + "@types/react-dom": "npm:^18.3.0" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundle-wasm@workspace:src/bundles/wasm": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundle-wasm@workspace:src/bundles/wasm" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + source-academy-utils: "npm:^1.0.0" + source-academy-wabt: "npm:^1.0.4" + typescript: "npm:^5.8.2" + languageName: unknown + linkType: soft + +"@sourceacademy/bundles@workspace:src/bundles": + version: 0.0.0-use.local + resolution: "@sourceacademy/bundles@workspace:src/bundles" + dependencies: + vitest: "npm:^3.1.4" + languageName: unknown + linkType: soft + +"@sourceacademy/lint-plugin@workspace:^, @sourceacademy/lint-plugin@workspace:lib/lintplugin": + version: 0.0.0-use.local + resolution: "@sourceacademy/lint-plugin@workspace:lib/lintplugin" + dependencies: + "@es-joy/jsdoccomment": "npm:^0.50.1" + "@stylistic/eslint-plugin": "npm:^4.2.0" + "@typescript-eslint/rule-tester": "npm:^8.32.1" + "@typescript-eslint/utils": "npm:^8.32.1" + eslint-plugin-import: "npm:^2.31.0" + globals: "npm:^15.11.0" + typescript: "npm:^5.8.2" + typescript-eslint: "npm:^8.24.1" + vitest: "npm:^3.1.4" + peerDependencies: + eslint: ^9.21.0 + languageName: unknown + linkType: soft + +"@sourceacademy/modules-buildtools@workspace:^, @sourceacademy/modules-buildtools@workspace:lib/buildtools": + version: 0.0.0-use.local + resolution: "@sourceacademy/modules-buildtools@workspace:lib/buildtools" + dependencies: + "@commander-js/extra-typings": "npm:^13.0.0" + "@types/estree": "npm:^1.0.0" + "@types/lodash": "npm:^4.14.198" + "@types/node": "npm:^20.12.12" + acorn: "npm:^8.8.1" + astring: "npm:^1.8.6" + chalk: "npm:^5.0.1" + commander: "npm:^13.0.0" + console-table-printer: "npm:^2.11.1" + esbuild: "npm:^0.25.0" + eslint: "npm:^9.21.0" + jsonschema: "npm:^1.5.0" + lodash: "npm:^4.17.21" + typedoc: "npm:^0.28.4" + typescript: "npm:^5.8.2" + vitest: "npm:^3.1.4" + bin: + buildtools: ./bin/index.js + languageName: unknown + linkType: soft + +"@sourceacademy/modules-devserver@workspace:devserver": + version: 0.0.0-use.local + resolution: "@sourceacademy/modules-devserver@workspace:devserver" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@types/react": "npm:^18.3.1" + "@types/react-dom": "npm:^18.3.1" + "@vitejs/plugin-react": "npm:^4.3.4" + ace-builds: "npm:^1.25.1" + classnames: "npm:^2.3.1" + re-resizable: "npm:^6.9.11" + react: "npm:^18.3.1" + react-ace: "npm:^10.1.0" + react-dom: "npm:^18.3.1" + sass: "npm:^1.85.0" + vite: "npm:^6.3.4" + languageName: unknown + linkType: soft + +"@sourceacademy/modules-lib@workspace:^, @sourceacademy/modules-lib@workspace:src/modules-lib": + version: 0.0.0-use.local + resolution: "@sourceacademy/modules-lib@workspace:src/modules-lib" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@types/react": "npm:^18.3.1" + "@types/react-dom": "npm:^18.3.1" + js-slang: "npm:^1.0.81" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + typescript: "npm:^5.8.2" + vitest: "npm:^3.1.4" + languageName: unknown + linkType: soft + +"@sourceacademy/modules@workspace:.": + version: 0.0.0-use.local + resolution: "@sourceacademy/modules@workspace:." + dependencies: + "@rspack/cli": "npm:^1.3.9" + "@rspack/core": "npm:^1.3.9" + "@sourceacademy/lint-plugin": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@stylistic/eslint-plugin": "npm:^4.2.0" + "@types/node": "npm:^20.12.12" + "@types/react": "npm:^18.3.2" + "@types/react-dom": "npm:^18.3.0" + "@yarnpkg/types": "npm:^4.0.1" + esbuild: "npm:^0.25.0" + eslint: "npm:^9.21.0" + eslint-plugin-import: "npm:^2.31.0" + eslint-plugin-jest: "npm:^28.11.0" + eslint-plugin-jsx-a11y: "npm:^6.10.2" + eslint-plugin-react: "npm:^7.37.4" + eslint-plugin-react-hooks: "npm:^5.1.0" + http-server: "npm:^0.13.0" + husky: "npm:^9.0.11" + js-slang: "npm:^1.0.81" + lodash: "npm:^4.17.21" + patch-package: "npm:^6.5.1" + postinstall-postinstall: "npm:^2.1.0" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + typescript: "npm:^5.8.2" + typescript-eslint: "npm:^8.24.1" + vitest: "npm:^3.1.4" + yarnhook: "npm:^0.6.0" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-ArcadeTwod@workspace:src/tabs/ArcadeTwod": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-ArcadeTwod@workspace:src/tabs/ArcadeTwod" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-AugmentedReality@workspace:src/tabs/AugmentedReality": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-AugmentedReality@workspace:src/tabs/AugmentedReality" + dependencies: + "@blueprintjs/icons": "npm:^5.9.0" + "@sourceacademy/bundle-ar": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + saar: "npm:^1.0.4" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-CopyGc@workspace:src/tabs/CopyGc": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-CopyGc@workspace:src/tabs/CopyGc" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Csg@workspace:src/tabs/Csg": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Csg@workspace:src/tabs/Csg" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@sourceacademy/bundle-csg": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Curve@workspace:src/tabs/Curve": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Curve@workspace:src/tabs/Curve" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@sourceacademy/bundle-curve": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + "@types/react-dom": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Game@workspace:src/tabs/Game": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Game@workspace:src/tabs/Game" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-MarkSweep@workspace:src/tabs/MarkSweep": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-MarkSweep@workspace:src/tabs/MarkSweep" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Nbody@workspace:src/tabs/Nbody": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Nbody@workspace:src/tabs/Nbody" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + nbody: "npm:^0.2.0" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Painter@workspace:src/tabs/Painter": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Painter@workspace:src/tabs/Painter" + dependencies: + "@sourceacademy/bundle-painter": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Physics2D@workspace:src/tabs/Physics2D": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Physics2D@workspace:src/tabs/Physics2D" + dependencies: + "@box2d/debug-draw": "npm:^0.10.0" + "@sourceacademy/bundle-physics_2d": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Pixnflix@workspace:src/tabs/Pixnflix": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Pixnflix@workspace:src/tabs/Pixnflix" + dependencies: + "@sourceacademy/bundle-pix_n_flix": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Plotly@workspace:src/tabs/Plotly": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Plotly@workspace:src/tabs/Plotly" + dependencies: + "@sourceacademy/bundle-plotly": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Repeat@workspace:src/tabs/Repeat": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Repeat@workspace:src/tabs/Repeat" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Repl@workspace:src/tabs/Repl": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Repl@workspace:src/tabs/Repl" + dependencies: + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + ace-builds: "npm:^1.25.1" + react: "npm:^18.3.1" + react-ace: "npm:^10.1.0" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-RobotSimulation@workspace:src/tabs/RobotSimulation": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-RobotSimulation@workspace:src/tabs/RobotSimulation" + dependencies: + "@dimforge/rapier3d-compat": "npm:^0.11.2" + "@sourceacademy/bundle-robot_simulation": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Rune@workspace:src/tabs/Rune": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Rune@workspace:src/tabs/Rune" + dependencies: + "@sourceacademy/bundle-rune": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Sound@workspace:src/tabs/Sound": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Sound@workspace:src/tabs/Sound" + dependencies: + "@sourceacademy/bundle-sound": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft -"@sinonjs/commons@npm:^3.0.0": - version: 3.0.1 - resolution: "@sinonjs/commons@npm:3.0.1" +"@sourceacademy/tab-SoundMatrix@workspace:src/tabs/SoundMatrix": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-SoundMatrix@workspace:src/tabs/SoundMatrix" dependencies: - type-detect: "npm:4.0.8" - checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 - languageName: node - linkType: hard + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + classnames: "npm:^2.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft -"@sinonjs/fake-timers@npm:^10.0.2": - version: 10.3.0 - resolution: "@sinonjs/fake-timers@npm:10.3.0" +"@sourceacademy/tab-StereoSound@workspace:src/tabs/StereoSound": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-StereoSound@workspace:src/tabs/StereoSound" dependencies: - "@sinonjs/commons": "npm:^3.0.0" - checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 - languageName: node - linkType: hard + "@sourceacademy/bundle-stereo_sound": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-Unittest@workspace:src/tabs/Unittest": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-Unittest@workspace:src/tabs/Unittest" + dependencies: + "@sourceacademy/bundle-unittest": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tab-UnityAcademy@workspace:src/tabs/UnityAcademy": + version: 0.0.0-use.local + resolution: "@sourceacademy/tab-UnityAcademy@workspace:src/tabs/UnityAcademy" + dependencies: + "@blueprintjs/core": "npm:^5.10.2" + "@blueprintjs/icons": "npm:^5.9.0" + "@sourceacademy/bundle-unity_academy": "workspace:^" + "@sourceacademy/modules-buildtools": "workspace:^" + "@sourceacademy/modules-lib": "workspace:^" + "@types/react": "npm:^18.3.1" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + languageName: unknown + linkType: soft + +"@sourceacademy/tabs@workspace:src/tabs": + version: 0.0.0-use.local + resolution: "@sourceacademy/tabs@workspace:src/tabs" + dependencies: + vitest: "npm:^3.1.4" + languageName: unknown + linkType: soft "@stylistic/eslint-plugin@npm:^4.2.0": version: 4.2.0 @@ -1918,13 +2779,6 @@ __metadata: languageName: node linkType: hard -"@tootallnate/once@npm:2": - version: 2.0.0 - resolution: "@tootallnate/once@npm:2.0.0" - checksum: 10c0/073bfa548026b1ebaf1659eb8961e526be22fa77139b10d60e712f46d2f0f05f4e6c8bec62a087d41088ee9e29faa7f54838568e475ab2f776171003c3920858 - languageName: node - linkType: hard - "@ts-morph/bootstrap@npm:^0.18.0": version: 0.18.0 resolution: "@ts-morph/bootstrap@npm:0.18.0" @@ -1946,14 +2800,14 @@ __metadata: languageName: node linkType: hard -"@tweenjs/tween.js@npm:~23.1.1, @tweenjs/tween.js@npm:~23.1.3": +"@tweenjs/tween.js@npm:~23.1.3": version: 23.1.3 resolution: "@tweenjs/tween.js@npm:23.1.3" checksum: 10c0/811b30f5f0e7409fb41833401c501c2d6f600eb5f43039dd9067a7f70aff6dad5f5ce1233848e13f0b33a269a160d9c133f344d986cbff4f1f6b72ddecd06c89 languageName: node linkType: hard -"@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.1.7, @types/babel__core@npm:^7.20.5": +"@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.7, @types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -1967,11 +2821,11 @@ __metadata: linkType: hard "@types/babel__generator@npm:*": - version: 7.6.8 - resolution: "@types/babel__generator@npm:7.6.8" + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" dependencies: "@babel/types": "npm:^7.0.0" - checksum: 10c0/f0ba105e7d2296bf367d6e055bb22996886c114261e2cb70bf9359556d0076c7a57239d019dee42bb063f565bade5ccb46009bce2044b2952d964bf9a454d6d2 + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd languageName: node linkType: hard @@ -1986,18 +2840,49 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": - version: 7.20.6 - resolution: "@types/babel__traverse@npm:7.20.6" + version: 7.20.7 + resolution: "@types/babel__traverse@npm:7.20.7" dependencies: "@babel/types": "npm:^7.20.7" - checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888 + checksum: 10c0/5386f0af44f8746b063b87418f06129a814e16bb2686965a575e9d7376b360b088b89177778d8c426012abc43dd1a2d8ec3218bfc382280c898682746ce2ffbd languageName: node linkType: hard -"@types/debounce@npm:^1.2.1": - version: 1.2.4 - resolution: "@types/debounce@npm:1.2.4" - checksum: 10c0/89db97397312b1273be74b326133af4744e5f63b726016bb6eb5c31b94f07a2f3bd3fb5bc3a3667bce506f01ede46c9abbfb88a4745ee9f34641fb636ab51210 +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df + languageName: node + linkType: hard + +"@types/bonjour@npm:^3.5.13": + version: 3.5.13 + resolution: "@types/bonjour@npm:3.5.13" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/eebedbca185ac3c39dd5992ef18d9e2a9f99e7f3c2f52f5561f90e9ed482c5d224c7962db95362712f580ed5713264e777a98d8f0bd8747f4eadf62937baed16 + languageName: node + linkType: hard + +"@types/connect-history-api-fallback@npm:^1.5.4": + version: 1.5.4 + resolution: "@types/connect-history-api-fallback@npm:1.5.4" + dependencies: + "@types/express-serve-static-core": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/1b4035b627dcd714b05a22557f942e24a57ca48e7377dde0d2f86313fe685bc0a6566512a73257a55b5665b96c3041fb29228ac93331d8133011716215de8244 + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c languageName: node linkType: hard @@ -2008,14 +2893,71 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:1.0.7, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": +"@types/eslint@npm:^9.6.1": + version: 9.6.1 + resolution: "@types/eslint@npm:9.6.1" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:1.0.7, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": version: 1.0.7 resolution: "@types/estree@npm:1.0.7" checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c languageName: node linkType: hard -"@types/graceful-fs@npm:^4.1.2, @types/graceful-fs@npm:^4.1.3": +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^5.0.0": + version: 5.0.6 + resolution: "@types/express-serve-static-core@npm:5.0.6" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/aced8cc88c1718adbbd1fc488756b0f22d763368d9eff2ae21b350698fab4a77d8d13c3699056dc662a887e43a8b67a3e8f6289ff76102ecc6bad4a7710d31a6 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.6 + resolution: "@types/express-serve-static-core@npm:4.19.6" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/4281f4ead71723f376b3ddf64868ae26244d434d9906c101cf8d436d4b5c779d01bd046e4ea0ed1a394d3e402216fabfa22b1fa4dba501061cd7c81c54045983 + languageName: node + linkType: hard + +"@types/express@npm:*": + version: 5.0.1 + resolution: "@types/express@npm:5.0.1" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:*" + checksum: 10c0/e1385028c7251360ce916aab0e304187b613ca18cb9aa3fa90794a337e5b2e0c76330d467f41d3b3e936ce5336c4f3e63e323dc01192cf20f9686905daa6d00a + languageName: node + linkType: hard + +"@types/express@npm:^4.17.21": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10c0/12e562c4571da50c7d239e117e688dc434db1bac8be55613294762f84fd77fbd0658ccd553c7d3ab02408f385bc93980992369dd30e2ecd2c68c358e6af8fabf + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.2": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" dependencies: @@ -2024,7 +2966,32 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": +"@types/hast@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/3249781a511b38f1d330fd1e3344eed3c4e7ea8eff82e835d35da78e637480d36fad37a78be5a7aed8465d237ad0446abc1150859d0fde395354ea634decf9f7 + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 10c0/494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 + languageName: node + linkType: hard + +"@types/http-proxy@npm:^1.17.8": + version: 1.17.16 + resolution: "@types/http-proxy@npm:1.17.16" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/b71bbb7233b17604f1158bbbe33ebf8bb870179d2b6e15dc9483aa2a785ce0d19ffb6c2237225b558addf24211d1853c95e337ee496df058eb175b433418a941 + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 @@ -2049,28 +3016,7 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^29.0.0": - version: 29.5.14 - resolution: "@types/jest@npm:29.5.14" - dependencies: - expect: "npm:^29.0.0" - pretty-format: "npm:^29.0.0" - checksum: 10c0/18e0712d818890db8a8dab3d91e9ea9f7f19e3f83c2e50b312f557017dc81466207a71f3ed79cf4428e813ba939954fa26ffa0a9a7f153181ba174581b1c2aed - languageName: node - linkType: hard - -"@types/jsdom@npm:^20.0.0": - version: 20.0.1 - resolution: "@types/jsdom@npm:20.0.1" - dependencies: - "@types/node": "npm:*" - "@types/tough-cookie": "npm:*" - parse5: "npm:^7.0.0" - checksum: 10c0/3d4b2a3eab145674ee6da482607c5e48977869109f0f62560bf91ae1a792c9e847ac7c6aaf243ed2e97333cb3c51aef314ffa54a19ef174b8f9592dfcb836b25 - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.15": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -2091,21 +3037,37 @@ __metadata: languageName: node linkType: hard +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + +"@types/node-forge@npm:^1.3.0": + version: 1.3.11 + resolution: "@types/node-forge@npm:1.3.11" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/3d7d23ca0ba38ac0cf74028393bd70f31169ab9aba43f21deb787840170d307d662644bac07287495effe2812ddd7ac8a14dbd43f16c2936bbb06312e96fc3b9 + languageName: node + linkType: hard + "@types/node@npm:*": - version: 22.15.3 - resolution: "@types/node@npm:22.15.3" + version: 22.15.18 + resolution: "@types/node@npm:22.15.18" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/2879f012d1aeba0bfdb5fed80d165f4f2cb3d1f2e1f98a24b18d4a211b4ace7d64bf2622784c78355982ffc1081ba79d0934efc2fb8353913e5871a63609661f + checksum: 10c0/e23178c568e2dc6b93b6aa3b8dfb45f9556e527918c947fe7406a4c92d2184c7396558912400c3b1b8d0fa952ec63819aca2b8e4d3545455fc6f1e9623e09ca6 languageName: node linkType: hard "@types/node@npm:^20.12.12": - version: 20.17.32 - resolution: "@types/node@npm:20.17.32" + version: 20.17.47 + resolution: "@types/node@npm:20.17.47" dependencies: undici-types: "npm:~6.19.2" - checksum: 10c0/2461df36f67704f68db64d33abc5ad00b4b35ac94e996adff88c7322f9572e3e60ddaeed7e9f34ae203120d2ba36cc931fd3a8ddddf0c63943e8600c365c6396 + checksum: 10c0/df336ed7897177214d1c0f2c7e2f94bbc19da28bffd150ad67dfdb373ca55167479a266c836f99450ab6b6a71799a53583b3f4f159c5424255e9f3851cc9d431 languageName: node linkType: hard @@ -2117,20 +3079,34 @@ __metadata: linkType: hard "@types/plotly.js@npm:^2.35.4": - version: 2.35.5 - resolution: "@types/plotly.js@npm:2.35.5" - checksum: 10c0/02268e979c471a9dccd0e1a344ed69e8d71b3b1680c0f1dd08b9d421e2689315906d7010eb4011a6e0280929a9003efaad3319cd103a52f7ed2fc9807bc7bec1 + version: 2.35.6 + resolution: "@types/plotly.js@npm:2.35.6" + checksum: 10c0/9a01e9b46d408a763ce5882de5033c9e80801f09f4a66b24e66bb89f02ee98a67904522c2410bdf783f1aa46546f47bf71b307607db792a8a440a0f998695ed2 languageName: node linkType: hard "@types/prop-types@npm:*": - version: 15.7.13 - resolution: "@types/prop-types@npm:15.7.13" - checksum: 10c0/1b20fc67281902c6743379960247bc161f3f0406ffc0df8e7058745a85ea1538612109db0406290512947f9632fe9e10e7337bf0ce6338a91d6c948df16a7c61 + version: 15.7.14 + resolution: "@types/prop-types@npm:15.7.14" + checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.9.18 + resolution: "@types/qs@npm:6.9.18" + checksum: 10c0/790b9091348e06dde2c8e4118b5771ab386a8c22a952139a2eb0675360a2070d0b155663bf6f75b23f258fd0a1f7ffc0ba0f059d99a719332c03c40d9e9cd63b + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c languageName: node linkType: hard -"@types/react-dom@npm:^18.3.0": +"@types/react-dom@npm:^18.3.0, @types/react-dom@npm:^18.3.1": version: 18.3.7 resolution: "@types/react-dom@npm:18.3.7" peerDependencies: @@ -2149,69 +3125,110 @@ __metadata: linkType: hard "@types/react-reconciler@npm:^0.28.0": - version: 0.28.8 - resolution: "@types/react-reconciler@npm:0.28.8" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/ca95cffcdf58591679c6c87dcc6f2c50cef9c6b2772d089ec0c695567656f34a30a0f2592f391d99b0e877f94afd67347082c55eb1dc5cb8000e23c8efc0fafc + version: 0.28.9 + resolution: "@types/react-reconciler@npm:0.28.9" + peerDependencies: + "@types/react": "*" + checksum: 10c0/9fe71fa856aab2cd4742fe0416bdd4f5c82ecc05ef6451ee7fcb65a5efdf5fa588f5820fbe2a665b15371b0da3bfc4097f28bb6d450b9a834af2d0fc00f403bd languageName: node linkType: hard "@types/react@npm:*": - version: 19.1.0 - resolution: "@types/react@npm:19.1.0" + version: 19.1.4 + resolution: "@types/react@npm:19.1.4" dependencies: csstype: "npm:^3.0.2" - checksum: 10c0/632fd20ee176e55801a61c5f854141b043571a3e363ef106b047b766a813a12735cbb37abb3d61d126346979f530f2ed269a60c8ef3cdee54e5e9fe4174e5dad + checksum: 10c0/501350d4f9cef13c5dd1b1496fa70ebaff52f6fa359b623b51c9d817e5bc4333fa3c8b7a6a4cbc88c643385052d66a243c3ceccfd6926062f917a2dd0535f6b3 languageName: node linkType: hard -"@types/react@npm:^18.3.2": - version: 18.3.20 - resolution: "@types/react@npm:18.3.20" +"@types/react@npm:^18.3.1, @types/react@npm:^18.3.2": + version: 18.3.21 + resolution: "@types/react@npm:18.3.21" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10c0/65fa867c91357e4c4c646945c8b99044360f8973cb7f928ec4de115fe3207827985d45be236e6fd6c092b09f631c2126ce835c137be30718419e143d73300d8f + checksum: 10c0/b33424c62f01ab2404c60abef995e05aa1a696a636bdd77a34ef6c942d171c673b1e451d9dba7f9a169a83c9eef0ddfaf1ba08f6cef542df9404290199a73967 languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.0": - version: 2.0.3 - resolution: "@types/stack-utils@npm:2.0.3" - checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c +"@types/retry@npm:0.12.2": + version: 0.12.2 + resolution: "@types/retry@npm:0.12.2" + checksum: 10c0/07481551a988cc90b423351919928b9ddcd14e3f5591cac3ab950851bb20646e55a10e89141b38bc3093d2056d4df73700b22ff2612976ac86a6367862381884 + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c + languageName: node + linkType: hard + +"@types/serve-index@npm:^1.9.4": + version: 1.9.4 + resolution: "@types/serve-index@npm:1.9.4" + dependencies: + "@types/express": "npm:*" + checksum: 10c0/94c1b9e8f1ea36a229e098e1643d5665d9371f8c2658521718e259130a237c447059b903bac0dcc96ee2c15fd63f49aa647099b7d0d437a67a6946527a837438 + languageName: node + linkType: hard + +"@types/serve-static@npm:*, @types/serve-static@npm:^1.15.5": + version: 1.15.7 + resolution: "@types/serve-static@npm:1.15.7" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae + languageName: node + linkType: hard + +"@types/sockjs@npm:^0.3.36": + version: 0.3.36 + resolution: "@types/sockjs@npm:0.3.36" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/b20b7820ee813f22de4f2ce98bdd12c68c930e016a8912b1ed967595ac0d8a4cbbff44f4d486dd97f77f5927e7b5725bdac7472c9ec5b27f53a5a13179f0612f languageName: node linkType: hard "@types/stats.js@npm:*": - version: 0.17.3 - resolution: "@types/stats.js@npm:0.17.3" - checksum: 10c0/ccccc992c6dfe08fb85049aa3ce44ca7e428db8da4a3edd20298f1c8b72768021fa8bacdfbe8e9735a7552ee5d57f667c6f557050ad2d9a87b699b3566a6177a + version: 0.17.4 + resolution: "@types/stats.js@npm:0.17.4" + checksum: 10c0/4fe0429998519f0476f03a25b4900b4d4a1474606478657271e40a884f7936ba902ea564b1c95cfd33a8e84af46cef6e1e98bb23e86fd3b6676cd5b974987151 languageName: node linkType: hard -"@types/three@npm:^0.161.2": - version: 0.161.2 - resolution: "@types/three@npm:0.161.2" +"@types/three@npm:*": + version: 0.176.0 + resolution: "@types/three@npm:0.176.0" dependencies: + "@dimforge/rapier3d-compat": "npm:^0.12.0" + "@tweenjs/tween.js": "npm:~23.1.3" "@types/stats.js": "npm:*" "@types/webxr": "npm:*" - fflate: "npm:~0.6.10" + "@webgpu/types": "npm:*" + fflate: "npm:~0.8.2" meshoptimizer: "npm:~0.18.1" - checksum: 10c0/b8f49ee5eff1ec137444753e8f65a0d51fe493f6efeb157c2a7a0049300482d1fafef63f1c5bcfabab7568be65b569a3676d6bc9d718bc69143ab93e9638d7cd + checksum: 10c0/a06960a71987e717382de3a4d8d22059b40312d85febda8a9dd1aaa1a152d88ff8df12bd9b93c64a45ff10dc68b94b2f5f81ab251f9f542578149a768745e064 languageName: node linkType: hard -"@types/three@npm:^0.163.0": - version: 0.163.0 - resolution: "@types/three@npm:0.163.0" +"@types/three@npm:^0.161.2": + version: 0.161.2 + resolution: "@types/three@npm:0.161.2" dependencies: - "@tweenjs/tween.js": "npm:~23.1.1" "@types/stats.js": "npm:*" "@types/webxr": "npm:*" - fflate: "npm:~0.8.2" + fflate: "npm:~0.6.10" meshoptimizer: "npm:~0.18.1" - checksum: 10c0/715406a709bdcf567ba844c598c73dac07b679df942f85c578e82a33d00ae3ac67e8f6a3e030ec75ba0f639d33c65fb7ec6237c11a4a501c98569d99bc67a5b6 + checksum: 10c0/b8f49ee5eff1ec137444753e8f65a0d51fe493f6efeb157c2a7a0049300482d1fafef63f1c5bcfabab7568be65b569a3676d6bc9d718bc69143ab93e9638d7cd languageName: node linkType: hard @@ -2229,17 +3246,26 @@ __metadata: languageName: node linkType: hard -"@types/tough-cookie@npm:*": - version: 4.0.5 - resolution: "@types/tough-cookie@npm:4.0.5" - checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 +"@types/unist@npm:*": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 10c0/2b1e4adcab78388e088fcc3c0ae8700f76619dbcb4741d7d201f87e2cb346bfc29a89003cfea2d76c996e1061452e14fcd737e8b25aacf949c1f2d6b2bc3dd60 languageName: node linkType: hard "@types/webxr@npm:*, @types/webxr@npm:^0.5.2": - version: 0.5.20 - resolution: "@types/webxr@npm:0.5.20" - checksum: 10c0/f8bddda79a43bfc31ce92d9c4b6d324390c40382e4981262b6217199636b5b7cb77228dea35ce18a054a2d2e9c19d1c59e3f7b14f450527b72764db786c8c7b9 + version: 0.5.22 + resolution: "@types/webxr@npm:0.5.22" + checksum: 10c0/dd015cadd71b2cdd3b7bab18152e579d33ff66652b2eab26f371f3583ca607e9a1f1bd5af5e2f7ff861a50aa352f44b3be40f26b64561e365d1fec254c7dfdcd + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.10": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab languageName: node linkType: hard @@ -2259,184 +3285,132 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.8": - version: 17.0.33 - resolution: "@types/yargs@npm:17.0.33" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10c0/d16937d7ac30dff697801c3d6f235be2166df42e4a88bf730fa6dc09201de3727c0a9500c59a672122313341de5f24e45ee0ff579c08ce91928e519090b7906b - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.30.1" +"@typescript-eslint/eslint-plugin@npm:8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.32.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.30.1" - "@typescript-eslint/type-utils": "npm:8.30.1" - "@typescript-eslint/utils": "npm:8.30.1" - "@typescript-eslint/visitor-keys": "npm:8.30.1" + "@typescript-eslint/scope-manager": "npm:8.32.1" + "@typescript-eslint/type-utils": "npm:8.32.1" + "@typescript-eslint/utils": "npm:8.32.1" + "@typescript-eslint/visitor-keys": "npm:8.32.1" graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/e34e067c977a20fe927a30e5ffd5402b03eb12d1c9dc932e7c4a772e78fda9e34708fa2d12ace34bad2c51ecaf5b8cfaa4b372c0c5550fe06587b721f6eae57b + checksum: 10c0/29dbafc1f02e1167e6d1e92908de6bf7df1cc1fc9ae1de3f4d4abf5d2b537be16b173bcd05770270529eb2fd17a3ac63c2f40d308f7fbbf6d6f286ba564afd64 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/parser@npm:8.30.1" +"@typescript-eslint/parser@npm:8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/parser@npm:8.32.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.30.1" - "@typescript-eslint/types": "npm:8.30.1" - "@typescript-eslint/typescript-estree": "npm:8.30.1" - "@typescript-eslint/visitor-keys": "npm:8.30.1" + "@typescript-eslint/scope-manager": "npm:8.32.1" + "@typescript-eslint/types": "npm:8.32.1" + "@typescript-eslint/typescript-estree": "npm:8.32.1" + "@typescript-eslint/visitor-keys": "npm:8.32.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/add025d5cfca5cd4d1f74c9297e71de95c945f4efbe6cbfbc72e2cd794cd2684397c7d832bdb5177a1f54398111243d20bd0d2ffdb32a4d5230f1db7cd6fbfb6 + checksum: 10c0/01095f5b6e0a2e0631623be3f44be0f2960ceb24de33b64cb790e24a1468018d2b4d6874d1fa08a4928c2a02f208dd66cbc49735c7e8b54d564e420daabf84d1 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.29.0": - version: 8.29.0 - resolution: "@typescript-eslint/scope-manager@npm:8.29.0" +"@typescript-eslint/rule-tester@npm:^8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/rule-tester@npm:8.32.1" dependencies: - "@typescript-eslint/types": "npm:8.29.0" - "@typescript-eslint/visitor-keys": "npm:8.29.0" - checksum: 10c0/330d777043a99485b51197ad24927f1276d61e61adaf710f012b3fe7db2ab67c8925c0526f801715b498e7d8fa7cef390006b6f7ae40cee89abe22e8e438de9a + "@typescript-eslint/parser": "npm:8.32.1" + "@typescript-eslint/typescript-estree": "npm:8.32.1" + "@typescript-eslint/utils": "npm:8.32.1" + ajv: "npm:^6.12.6" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + lodash.merge: "npm:4.6.2" + semver: "npm:^7.6.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + checksum: 10c0/f211d5afbfc572986c096a2dd4cf6e31e3b27b7140a017e281e9743cc39b325db751aa40a8ac399d8a511522569ae40ab643b1eae555475f76db383f5acea776 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/scope-manager@npm:8.30.1" +"@typescript-eslint/scope-manager@npm:8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/scope-manager@npm:8.32.1" dependencies: - "@typescript-eslint/types": "npm:8.30.1" - "@typescript-eslint/visitor-keys": "npm:8.30.1" - checksum: 10c0/8560fd02bb2a73b56f79af1dfa311491926f3625a04c0f32777c7c0bdec47b4a677addf2d2e2cc313416bb59b7a6e0bff7837449816a5ec5ff81e923daa76ca7 + "@typescript-eslint/types": "npm:8.32.1" + "@typescript-eslint/visitor-keys": "npm:8.32.1" + checksum: 10c0/d2cb1f7736388972137d6e510b2beae4bac033fcab274e04de90ebba3ce466c71fe47f1795357e032e4a6c8b2162016b51b58210916c37212242c82d35352e9f languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/type-utils@npm:8.30.1" +"@typescript-eslint/type-utils@npm:8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/type-utils@npm:8.32.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.30.1" - "@typescript-eslint/utils": "npm:8.30.1" + "@typescript-eslint/typescript-estree": "npm:8.32.1" + "@typescript-eslint/utils": "npm:8.32.1" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/c233d2b0b06bd8eca4ee38aebb7544d4084143590328f38c00302f98a62b06868394d4ab1cd798af68d5a47efd84976cc14d415e9e519396dc89aa8d4d47c9ee - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.29.0": - version: 8.29.0 - resolution: "@typescript-eslint/types@npm:8.29.0" - checksum: 10c0/fc1e3f3071102973a9cfb8ae843c51398bd74b5583b7b0edad182ea605ef85e72ceac7987513581869958b3a65303af6b3471bfba5b7be1338e8add62019c858 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/types@npm:8.30.1" - checksum: 10c0/461e800bf911c24d9b61bdbeed897921454acc0c24b4e8a79f943c14234241828c13a31dce31dcce77511185f806a2fb94769075e122e3182ba5a32dd55573eb + checksum: 10c0/f10186340ce194681804d9a57feb6d8d6c3adbd059c70df58f4656b0d9efd412fb0c2d80c182f9db83bad1a301754e0c24fe26f3354bef3a1795ab9c835cb763 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.29.0": - version: 8.29.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.29.0" - dependencies: - "@typescript-eslint/types": "npm:8.29.0" - "@typescript-eslint/visitor-keys": "npm:8.29.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.1" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/61dd52229a0758e0bd29f732115c16e640a2418fb25488877c74ef03cdbeb43ddc592a37094abd794ef49812f33d6f814c5b662b95ea796ed0a6c6bfc849299b +"@typescript-eslint/types@npm:8.32.1, @typescript-eslint/types@npm:^8.11.0": + version: 8.32.1 + resolution: "@typescript-eslint/types@npm:8.32.1" + checksum: 10c0/86f59b29c12e7e8abe45a1659b6fae5e7b0cfaf09ab86dd596ed9d468aa61082bbccd509d25f769b197fbfdf872bbef0b323a2ded6ceaca351f7c679f1ba3bd3 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.30.1" +"@typescript-eslint/typescript-estree@npm:8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.32.1" dependencies: - "@typescript-eslint/types": "npm:8.30.1" - "@typescript-eslint/visitor-keys": "npm:8.30.1" + "@typescript-eslint/types": "npm:8.32.1" + "@typescript-eslint/visitor-keys": "npm:8.32.1" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/9eb0b1bc4b5df37c84ac411d77ce0edf934b5fdde021ed45c984aa7894132ff7a276d2b95e2d29ef84c411df8ecdf096eec3e07ec1ee5b1fa8c623d40a82ecf0 + checksum: 10c0/b5ae0d91ef1b46c9f3852741e26b7a14c28bb58ee8a283b9530ac484332ca58a7216b9d22eda23c5449b5fd69c6e4601ef3ebbd68e746816ae78269036c08cda languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/utils@npm:8.30.1" +"@typescript-eslint/utils@npm:8.32.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.23.0, @typescript-eslint/utils@npm:^8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/utils@npm:8.32.1" dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.30.1" - "@typescript-eslint/types": "npm:8.30.1" - "@typescript-eslint/typescript-estree": "npm:8.30.1" + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.32.1" + "@typescript-eslint/types": "npm:8.32.1" + "@typescript-eslint/typescript-estree": "npm:8.32.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/ad54aa386edc2e19957c73ef25eea3e263e7e15e941c72e91ca6c8ea2536979d343a6069de0e40b15f0e732ddaacbfcc3d5f25a1583e11a32120c42c471802ea - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.23.0": - version: 8.29.0 - resolution: "@typescript-eslint/utils@npm:8.29.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.29.0" - "@typescript-eslint/types": "npm:8.29.0" - "@typescript-eslint/typescript-estree": "npm:8.29.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/e259d7edd12946b2dc8e1aa3bbea10f66c5277f27dda71368aa2b2923487f28cd1c123681aaae22518a31c8aeabd60a5365f8a832d0f6e6efadb03745a2d8a31 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.29.0": - version: 8.29.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.29.0" - dependencies: - "@typescript-eslint/types": "npm:8.29.0" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/7f5452b137c4edd258b2289cddf5d92687780375db33421bc4f5e2e9b0c94064c7c5ed3a7b5d96dc9c2d09ca7842b4415b3f3ed3e3f1ae3ac2e625ecb5e87efc + checksum: 10c0/a2b90c0417cd3a33c6e22f9cc28c356f251bb8928ef1d25e057feda007d522d281bdc37a9a0d05b70312f00a7b3f350ca06e724867025ea85bba5a4c766732e7 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.30.1": - version: 8.30.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.30.1" +"@typescript-eslint/visitor-keys@npm:8.32.1": + version: 8.32.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.32.1" dependencies: - "@typescript-eslint/types": "npm:8.30.1" + "@typescript-eslint/types": "npm:8.32.1" eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/bdc182289c68a5c8f891f9aecf6ccb59743c3f2b1bbe57f57f8c7ce1688f4381182e301919895cefc929539eea914eeb847f7d351cdc3f685ed6c5ee67a10c9e + checksum: 10c0/9c05053dfd048f681eb96e09ceefa8841a617b8b5950eea05e0844b38fe3510a284eb936324caa899c3ceb4bc23efe56ac01437fab378ac1beeb1c6c00404978 languageName: node linkType: hard @@ -2447,7 +3421,7 @@ __metadata: languageName: node linkType: hard -"@use-gesture/react@npm:^10.2.24": +"@use-gesture/react@npm:^10.3.1": version: 10.3.1 resolution: "@use-gesture/react@npm:10.3.1" dependencies: @@ -2473,6 +3447,87 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:3.1.4": + version: 3.1.4 + resolution: "@vitest/expect@npm:3.1.4" + dependencies: + "@vitest/spy": "npm:3.1.4" + "@vitest/utils": "npm:3.1.4" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/9cfd7eb6d965a179b4ec0610a9c08b14dc97dbaf81925c8209a054f7a2a3d1eef59fa5e5cd4dd9bf8cb940d85aee5f5102555511a94be9933faf4cc734462a16 + languageName: node + linkType: hard + +"@vitest/mocker@npm:3.1.4": + version: 3.1.4 + resolution: "@vitest/mocker@npm:3.1.4" + dependencies: + "@vitest/spy": "npm:3.1.4" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/d0b89e3974830d3893e7b8324a77ffeb9436db0969b57c01e2508ebd5b374c9d01f73796c8df8f555a3b1e1b502d40e725f159cd85966eebd3145b2f52e605e2 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:3.1.4, @vitest/pretty-format@npm:^3.1.4": + version: 3.1.4 + resolution: "@vitest/pretty-format@npm:3.1.4" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/11e133640435822b8b8528be540b3d66c1de27ebc2dcf1de87608b7f01a44d15302c4d4bf8330fa848a435450d88a09d7e9442747a5739ae5f500ccdd1493159 + languageName: node + linkType: hard + +"@vitest/runner@npm:3.1.4": + version: 3.1.4 + resolution: "@vitest/runner@npm:3.1.4" + dependencies: + "@vitest/utils": "npm:3.1.4" + pathe: "npm:^2.0.3" + checksum: 10c0/efb7512eebd3d786baa617eab332ec9ca6ce62eb1c9dd3945019f7510d745b3cd0fc2978868d792050905aacbf158eefc132359c83e61f0398b46be566013ee6 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:3.1.4": + version: 3.1.4 + resolution: "@vitest/snapshot@npm:3.1.4" + dependencies: + "@vitest/pretty-format": "npm:3.1.4" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 10c0/ce9d51e1b03e4f91ffad160c570991a8a3c603cb7dc2a9020e58c012e62dccbe2c6ee45e1a1d8489e265b4485c6721eb73b5e91404d1c76da08dcd663f4e18d1 + languageName: node + linkType: hard + +"@vitest/spy@npm:3.1.4": + version: 3.1.4 + resolution: "@vitest/spy@npm:3.1.4" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 10c0/747914ac18efa82d75349b0fb0ad8a5e2af6e04f5bbb50a980c9270dd8958f9ddf84cee0849a54e1645af088fc1f709add94a35e99cb14aca2cdb322622ba501 + languageName: node + linkType: hard + +"@vitest/utils@npm:3.1.4": + version: 3.1.4 + resolution: "@vitest/utils@npm:3.1.4" + dependencies: + "@vitest/pretty-format": "npm:3.1.4" + loupe: "npm:^3.1.3" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/78f1691a2dd578862b236f4962815e7475e547f006e7303a149dc5f910cc1ce6e0bdcbd7b4fd618122d62ca2dcc28bae464d31543f3898f5d88fa35017e00a95 + languageName: node + linkType: hard + "@webgpu/types@npm:*": version: 0.1.60 resolution: "@webgpu/types@npm:0.1.60" @@ -2480,24 +3535,46 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/lockfile@npm:^1.1.0": - version: 1.1.0 - resolution: "@yarnpkg/lockfile@npm:1.1.0" - checksum: 10c0/0bfa50a3d756623d1f3409bc23f225a1d069424dbc77c6fd2f14fb377390cd57ec703dc70286e081c564be9051ead9ba85d81d66a3e68eeb6eb506d4e0c0fbda +"@yarnpkg/lockfile@npm:^1.1.0": + version: 1.1.0 + resolution: "@yarnpkg/lockfile@npm:1.1.0" + checksum: 10c0/0bfa50a3d756623d1f3409bc23f225a1d069424dbc77c6fd2f14fb377390cd57ec703dc70286e081c564be9051ead9ba85d81d66a3e68eeb6eb506d4e0c0fbda + languageName: node + linkType: hard + +"@yarnpkg/types@npm:^4.0.1": + version: 4.0.1 + resolution: "@yarnpkg/types@npm:4.0.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/90226789475680ba599833571dd76c0718dd5b4c5022481263ef309d6a628f6246671cd6ca86e49c966ddefa7aca6ccef82240dc1476d2cea702ea5bee2a6b72 + languageName: node + linkType: hard + +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf languageName: node linkType: hard -"abab@npm:^2.0.6": - version: 2.0.6 - resolution: "abab@npm:2.0.6" - checksum: 10c0/0b245c3c3ea2598fe0025abf7cc7bb507b06949d51e8edae5d12c1b847a0a0c09639abcb94788332b4e2044ac4491c1e8f571b51c7826fd4b0bda1685ad4a278 +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef languageName: node linkType: hard -"abbrev@npm:^3.0.0": - version: 3.0.0 - resolution: "abbrev@npm:3.0.0" - checksum: 10c0/049704186396f571650eb7b22ed3627b77a5aedf98bb83caf2eac81ca2a3e25e795394b0464cfb2d6076df3db6a5312139eac5b6a126ca296ac53c5008069c28 +"accepts@npm:~1.3.4, accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 languageName: node linkType: hard @@ -2519,16 +3596,6 @@ __metadata: languageName: node linkType: hard -"acorn-globals@npm:^7.0.0": - version: 7.0.1 - resolution: "acorn-globals@npm:7.0.1" - dependencies: - acorn: "npm:^8.1.0" - acorn-walk: "npm:^8.0.2" - checksum: 10c0/7437f58e92d99292dbebd0e79531af27d706c9f272f31c675d793da6c82d897e75302a8744af13c7f7978a8399840f14a353b60cf21014647f71012982456d2b - languageName: node - linkType: hard - "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -2539,11 +3606,11 @@ __metadata: linkType: hard "acorn-loose@npm:^8.0.0": - version: 8.4.0 - resolution: "acorn-loose@npm:8.4.0" + version: 8.5.0 + resolution: "acorn-loose@npm:8.5.0" dependencies: - acorn: "npm:^8.11.0" - checksum: 10c0/e62407bdc338059e4d552b9ed5ccd44f13c5a86f5304a117bb8513672f9eb976bbbde1839f540296062660cef6b162f59bdc16d9c3430b264081567ba9684699 + acorn: "npm:^8.14.0" + checksum: 10c0/221df4224ccad267d46775323d956636dd0e4ef263d18450258daff1a33d99e8e00e6214465bdc658fc961cc77fc60cef2f0f4710ec93543ce179f287da0ea80 languageName: node linkType: hard @@ -2556,7 +3623,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.0.2": +"acorn-walk@npm:^8.0.0": version: 8.3.4 resolution: "acorn-walk@npm:8.3.4" dependencies: @@ -2565,7 +3632,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2": +"acorn@npm:^8.0.4, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2": version: 8.14.1 resolution: "acorn@npm:8.14.1" bin: @@ -2574,15 +3641,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:6": - version: 6.0.2 - resolution: "agent-base@npm:6.0.2" - dependencies: - debug: "npm:4" - checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 - languageName: node - linkType: hard - "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.3 resolution: "agent-base@npm:7.1.3" @@ -2590,7 +3648,32 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.4": +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10c0/e43ba22e91b6a48d96224b83d260d3a3a561b42d391f8d3c6d2c1559f9aa5b253bfb306bc94bbeca1d967c014e15a6efe9a207309e95b3eaae07fcbcdc2af662 + languageName: node + linkType: hard + +"ajv-keywords@npm:^5.1.0": + version: 5.1.0 + resolution: "ajv-keywords@npm:5.1.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + peerDependencies: + ajv: ^8.8.2 + checksum: 10c0/18bec51f0171b83123ba1d8883c126e60c6f420cef885250898bf77a8d3e65e3bfb9e8564f497e30bdbe762a83e0d144a36931328616a973ee669dc74d4a9590 + languageName: node + linkType: hard + +"ajv@npm:^6.12.4, ajv@npm:^6.12.6": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -2602,12 +3685,24 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1": - version: 4.3.2 - resolution: "ansi-escapes@npm:4.3.2" +"ajv@npm:^8.0.0, ajv@npm:^8.9.0": + version: 8.17.1 + resolution: "ajv@npm:8.17.1" dependencies: - type-fest: "npm:^0.21.3" - checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/ec3ba10a573c6b60f94639ffc53526275917a2df6810e4ab5a6b959d87459f9ef3f00d5e7865b82677cb7d21590355b34da14d1d0b9c32d75f95a187e76fff35 + languageName: node + linkType: hard + +"ansi-html-community@npm:^0.0.8": + version: 0.0.8 + resolution: "ansi-html-community@npm:0.0.8" + bin: + ansi-html: bin/ansi-html + checksum: 10c0/45d3a6f0b4f10b04fdd44bef62972e2470bfd917bf00439471fa7473d92d7cbe31369c73db863cc45dda115cb42527f39e232e9256115534b8ee5806b0caeed4 languageName: node linkType: hard @@ -2625,13 +3720,6 @@ __metadata: languageName: node linkType: hard -"ansi-sequence-parser@npm:^1.1.0": - version: 1.1.1 - resolution: "ansi-sequence-parser@npm:1.1.1" - checksum: 10c0/ab2259ccf69f145ecf1418d4e71524158828f44afdf37c7536677871f4cebaa8b176fcb95de8f94a68129357dddc59586597da25f9d4ebf9968f6ef022bf0b31 - languageName: node - linkType: hard - "ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" @@ -2641,13 +3729,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^5.0.0": - version: 5.2.0 - resolution: "ansi-styles@npm:5.2.0" - checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df - languageName: node - linkType: hard - "ansi-styles@npm:^6.1.0": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" @@ -2665,7 +3746,7 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:^3.0.3": +"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" dependencies: @@ -2729,6 +3810,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 10c0/806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + "array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": version: 3.1.8 resolution: "array-includes@npm:3.1.8" @@ -2765,28 +3853,29 @@ __metadata: linkType: hard "array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/962189487728b034f3134802b421b5f39e42ee2356d13b42d2ddb0e52057ffdcc170b9524867f4f0611a6f638f4c19b31e14606e8bcbda67799e26685b195aa3 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10c0/82559310d2e57ec5f8fc53d7df420e3abf0ba497935de0a5570586035478ba7d07618cb18e2d4ada2da514c8fb98a034aaf5c06caa0a57e2f7f4c4adedef5956 languageName: node linkType: hard "array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flat@npm:1.3.2" + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10c0/a578ed836a786efbb6c2db0899ae80781b476200617f65a44846cb1ed8bd8b24c8821b83703375d8af639c689497b7b07277060024b9919db94ac3e10dc8a49b + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a languageName: node linkType: hard @@ -2830,6 +3919,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + "assign-symbols@npm:^1.0.0": version: 1.0.0 resolution: "assign-symbols@npm:1.0.0" @@ -2860,29 +3956,13 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.6.4": - version: 2.6.4 - resolution: "async@npm:2.6.4" - dependencies: - lodash: "npm:^4.17.14" - checksum: 10c0/0ebb3273ef96513389520adc88e0d3c45e523d03653cc9b66f5c46f4239444294899bfd13d2b569e7dbfde7da2235c35cf5fd3ece9524f935d41bbe4efccdad0 - languageName: node - linkType: hard - -"async@npm:^3.2.3": +"async@npm:^3.2.6": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 languageName: node linkType: hard -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d - languageName: node - linkType: hard - "at-least-node@npm:^1.0.0": version: 1.0.0 resolution: "at-least-node@npm:1.0.0" @@ -2916,9 +3996,9 @@ __metadata: linkType: hard "axe-core@npm:^4.10.0": - version: 4.10.1 - resolution: "axe-core@npm:4.10.1" - checksum: 10c0/53d865efb7284fd69bc95ced1a1709fd603ea07f06e272da06942e7cfeca1c823e09bde28f57178e3a1a4c9a089fe4b5d274c871e3e6522a3b1bffec8eaa7dd8 + version: 4.10.3 + resolution: "axe-core@npm:4.10.3" + checksum: 10c0/1b1c24f435b2ffe89d76eca0001cbfff42dbf012ad9bd37398b70b11f0d614281a38a28bc3069e8972e3c90ec929a8937994bd24b0ebcbaab87b8d1e241ab0c7 languageName: node linkType: hard @@ -2947,24 +4027,7 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "babel-jest@npm:29.7.0" - dependencies: - "@jest/transform": "npm:^29.7.0" - "@types/babel__core": "npm:^7.1.14" - babel-plugin-istanbul: "npm:^6.1.1" - babel-preset-jest: "npm:^29.6.3" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - slash: "npm:^3.0.0" - peerDependencies: - "@babel/core": ^7.8.0 - checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 - languageName: node - linkType: hard - -"babel-plugin-istanbul@npm:^6.0.0, babel-plugin-istanbul@npm:^6.1.1": +"babel-plugin-istanbul@npm:^6.0.0": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" dependencies: @@ -2989,18 +4052,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-jest-hoist@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-plugin-jest-hoist@npm:29.6.3" - dependencies: - "@babel/template": "npm:^7.3.3" - "@babel/types": "npm:^7.3.3" - "@types/babel__core": "npm:^7.1.14" - "@types/babel__traverse": "npm:^7.0.6" - checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e - languageName: node - linkType: hard - "babel-preset-current-node-syntax@npm:^1.0.0": version: 1.1.0 resolution: "babel-preset-current-node-syntax@npm:1.1.0" @@ -3038,18 +4089,6 @@ __metadata: languageName: node linkType: hard -"babel-preset-jest@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-preset-jest@npm:29.6.3" - dependencies: - babel-plugin-jest-hoist: "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 - languageName: node - linkType: hard - "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -3086,6 +4125,13 @@ __metadata: languageName: node linkType: hard +"batch@npm:0.6.1": + version: 0.6.1 + resolution: "batch@npm:0.6.1" + checksum: 10c0/925a13897b4db80d4211082fe287bcf96d297af38e26448c857cee3e095c9792e3b8f26b37d268812e7f38a589f694609de8534a018b1937d7dc9f84e6b387c5 + languageName: node + linkType: hard + "bidi-js@npm:^1.0.2": version: 1.0.3 resolution: "bidi-js@npm:1.0.3" @@ -3095,6 +4141,13 @@ __metadata: languageName: node linkType: hard +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + "bl@npm:^4.0.2": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -3106,6 +4159,53 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 + languageName: node + linkType: hard + +"body-parser@npm:^2.2.0": + version: 2.2.0 + resolution: "body-parser@npm:2.2.0" + dependencies: + bytes: "npm:^3.1.2" + content-type: "npm:^1.0.5" + debug: "npm:^4.4.0" + http-errors: "npm:^2.0.0" + iconv-lite: "npm:^0.6.3" + on-finished: "npm:^2.4.1" + qs: "npm:^6.14.0" + raw-body: "npm:^3.0.0" + type-is: "npm:^2.0.0" + checksum: 10c0/a9ded39e71ac9668e2211afa72e82ff86cc5ef94de1250b7d1ba9cc299e4150408aaa5f1e8b03dd4578472a3ce6d1caa2a23b27a6c18e526e48b4595174c116c + languageName: node + linkType: hard + +"bonjour-service@npm:^1.2.1": + version: 1.3.0 + resolution: "bonjour-service@npm:1.3.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + multicast-dns: "npm:^7.2.5" + checksum: 10c0/5721fd9f9bb968e9cc16c1e8116d770863dd2329cb1f753231de1515870648c225142b7eefa71f14a5c22bc7b37ddd7fdeb018700f28a8c936d50d4162d433c7 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -3143,7 +4243,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.3": +"braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -3153,25 +4253,16 @@ __metadata: linkType: hard "browserslist@npm:^4.24.0": - version: 4.24.2 - resolution: "browserslist@npm:4.24.2" + version: 4.24.5 + resolution: "browserslist@npm:4.24.5" dependencies: - caniuse-lite: "npm:^1.0.30001669" - electron-to-chromium: "npm:^1.5.41" - node-releases: "npm:^2.0.18" - update-browserslist-db: "npm:^1.1.1" + caniuse-lite: "npm:^1.0.30001716" + electron-to-chromium: "npm:^1.5.149" + node-releases: "npm:^2.0.19" + update-browserslist-db: "npm:^1.1.3" bin: browserslist: cli.js - checksum: 10c0/d747c9fb65ed7b4f1abcae4959405707ed9a7b835639f8a9ba0da2911995a6ab9b0648fd05baf2a4d4e3cf7f9fdbad56d3753f91881e365992c1d49c8d88ff7a - languageName: node - linkType: hard - -"bs-logger@npm:^0.2.6": - version: 0.2.6 - resolution: "bs-logger@npm:0.2.6" - dependencies: - fast-json-stable-stringify: "npm:2.x" - checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + checksum: 10c0/f4c1ce1a7d8fdfab5e5b88bb6e93d09e8a883c393f86801537a252da0362dbdcde4dbd97b318246c5d84c6607b2f6b47af732c1b000d6a8a881ee024bad29204 languageName: node linkType: hard @@ -3211,6 +4302,29 @@ __metadata: languageName: node linkType: hard +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + +"bytes@npm:3.1.2, bytes@npm:^3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + "cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -3258,7 +4372,7 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.2, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": version: 1.0.8 resolution: "call-bind@npm:1.0.8" dependencies: @@ -3270,17 +4384,7 @@ __metadata: languageName: node linkType: hard -"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3": - version: 1.0.3 - resolution: "call-bound@npm:1.0.3" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - get-intrinsic: "npm:^1.2.6" - checksum: 10c0/45257b8e7621067304b30dbd638e856cac913d31e8e00a80d6cf172911acd057846572d0b256b45e652d515db6601e2974a1b1a040e91b4fc36fb3dd86fa69cf - languageName: node - linkType: hard - -"call-bound@npm:^1.0.4": +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": version: 1.0.4 resolution: "call-bound@npm:1.0.4" dependencies: @@ -3314,19 +4418,12 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - -"camera-controls@npm:^2.4.2": - version: 2.9.0 - resolution: "camera-controls@npm:2.9.0" +"camera-controls@npm:^2.9.0": + version: 2.10.1 + resolution: "camera-controls@npm:2.10.1" peerDependencies: three: ">=0.126.1" - checksum: 10c0/a772881a7a290615f144dac35e9fae78db14472e96eeb6cdf8d328a40f76bf007e64fb4ed093b1256e5b3bb9b9da2a94b97f623bbbd5cbd9485e6e75dc84af75 + checksum: 10c0/1a161b9a5faa1572401f58e2cf83ab8dd9cfb14e0d889de6105c65ceab64a9eef0961752121a01a1a61692e7cc0d4deb2463a3119fc9bce968ef6e928418e530 languageName: node linkType: hard @@ -3337,10 +4434,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001669 - resolution: "caniuse-lite@npm:1.0.30001669" - checksum: 10c0/f125f23440d3dbb6c25ffb8d55f4ce48af36a84d0932b152b3b74f143a4170cbe92e02b0a9676209c86609bf7bf34119ff10cc2bc7c1b7ea40e936cc16598408 +"caniuse-lite@npm:^1.0.30001716, caniuse-lite@npm:^1.0.30001717": + version: 1.0.30001718 + resolution: "caniuse-lite@npm:1.0.30001718" + checksum: 10c0/67f9ad09bc16443e28d14f265d6e468480cd8dc1900d0d8b982222de80c699c4f2306599c3da8a3fa7139f110d4b30d49dbac78f215470f479abb6ffe141d5d3 languageName: node linkType: hard @@ -3364,7 +4461,20 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.2": +"chai@npm:^5.2.0": + version: 5.2.0 + resolution: "chai@npm:5.2.0" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/dfd1cb719c7cebb051b727672d382a35338af1470065cb12adb01f4ee451bbf528e0e0f9ab2016af5fc1eea4df6e7f4504dc8443f8f00bd8fb87ad32dc516f7d + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -3401,19 +4511,38 @@ __metadata: languageName: node linkType: hard -"char-regex@npm:^1.0.2": - version: 1.0.2 - resolution: "char-regex@npm:1.0.2" - checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 languageName: node linkType: hard "chokidar@npm:^4.0.0": - version: 4.0.1 - resolution: "chokidar@npm:4.0.1" + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" dependencies: readdirp: "npm:^4.0.1" - checksum: 10c0/4bb7a3adc304059810bb6c420c43261a15bb44f610d77c35547addc84faa0374265c3adc67f25d06f363d9a4571962b02679268c40de07676d260de1986efea9 + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad languageName: node linkType: hard @@ -3431,20 +4560,6 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.2.0": - version: 3.9.0 - resolution: "ci-info@npm:3.9.0" - checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a - languageName: node - linkType: hard - -"cjs-module-lexer@npm:^1.0.0": - version: 1.4.1 - resolution: "cjs-module-lexer@npm:1.4.1" - checksum: 10c0/5a7d8279629c9ba8ccf38078c2fed75b7737973ced22b9b5a54180efa57fb2fe2bb7bec6aec55e3b8f3f5044f5d7b240347ad9bd285e7c3d0ee5b0a1d0504dfc - languageName: node - linkType: hard - "class-transformer@npm:^0.5.1": version: 0.5.1 resolution: "class-transformer@npm:0.5.1" @@ -3482,20 +4597,6 @@ __metadata: languageName: node linkType: hard -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 - languageName: node - linkType: hard - -"collect-v8-coverage@npm:^1.0.0": - version: 1.0.2 - resolution: "collect-v8-coverage@npm:1.0.2" - checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 - languageName: node - linkType: hard - "collection-visit@npm:^1.0.0": version: 1.0.0 resolution: "collection-visit@npm:1.0.0" @@ -3522,6 +4623,13 @@ __metadata: languageName: node linkType: hard +"colorette@npm:2.0.20, colorette@npm:^2.0.10": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 + languageName: node + linkType: hard + "colors@npm:^1.4.0": version: 1.4.0 resolution: "colors@npm:1.4.0" @@ -3529,15 +4637,6 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: "npm:~1.0.0" - checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 - languageName: node - linkType: hard - "commander@npm:^12.0.0": version: 12.1.0 resolution: "commander@npm:12.1.0" @@ -3552,6 +4651,20 @@ __metadata: languageName: node linkType: hard +"commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + +"comment-parser@npm:1.4.1": + version: 1.4.1 + resolution: "comment-parser@npm:1.4.1" + checksum: 10c0/d6c4be3f5be058f98b24f2d557f745d8fe1cc9eb75bebbdccabd404a0e1ed41563171b16285f593011f8b6a5ec81f564fb1f2121418ac5cbf0f49255bf0840dd + languageName: node + linkType: hard + "commist@npm:^1.0.0": version: 1.1.0 resolution: "commist@npm:1.1.0" @@ -3569,6 +4682,30 @@ __metadata: languageName: node linkType: hard +"compressible@npm:~2.0.18": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" + dependencies: + mime-db: "npm:>= 1.43.0 < 2" + checksum: 10c0/8a03712bc9f5b9fe530cc5a79e164e665550d5171a64575d7dcf3e0395d7b4afa2d79ab176c61b5b596e28228b350dd07c1a2a6ead12fd81d1b6cd632af2fef7 + languageName: node + linkType: hard + +"compression@npm:^1.7.4": + version: 1.8.0 + resolution: "compression@npm:1.8.0" + dependencies: + bytes: "npm:3.1.2" + compressible: "npm:~2.0.18" + debug: "npm:2.6.9" + negotiator: "npm:~0.6.4" + on-headers: "npm:~1.0.2" + safe-buffer: "npm:5.2.1" + vary: "npm:~1.1.2" + checksum: 10c0/804d3c8430939f4fd88e5128333f311b4035f6425a7f2959d74cfb5c98ef3a3e3e18143208f3f9d0fcae4cd3bcf3d2fbe525e0fcb955e6e146e070936f025a24 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -3588,6 +4725,13 @@ __metadata: languageName: node linkType: hard +"connect-history-api-fallback@npm:^2.0.0": + version: 2.0.0 + resolution: "connect-history-api-fallback@npm:2.0.0" + checksum: 10c0/90fa8b16ab76e9531646cc70b010b1dbd078153730c510d3142f6cf07479ae8a812c5a3c0e40a28528dd1681a62395d0cfdef67da9e914c4772ac85d69a3ed87 + languageName: node + linkType: hard + "console-table-printer@npm:^2.11.1": version: 2.12.1 resolution: "console-table-printer@npm:2.12.1" @@ -3608,6 +4752,31 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-disposition@npm:^1.0.0": + version: 1.0.0 + resolution: "content-disposition@npm:1.0.0" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27 + languageName: node + linkType: hard + +"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + "convert-source-map@npm:^1.4.0": version: 1.9.0 resolution: "convert-source-map@npm:1.9.0" @@ -3622,6 +4791,34 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10c0/b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 + languageName: node + linkType: hard + +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6 + languageName: node + linkType: hard + +"cookie@npm:0.7.1": + version: 0.7.1 + resolution: "cookie@npm:0.7.1" + checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde + languageName: node + linkType: hard + +"cookie@npm:^0.7.1": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 + languageName: node + linkType: hard + "copy-descriptor@npm:^0.1.0": version: 0.1.1 resolution: "copy-descriptor@npm:0.1.1" @@ -3629,27 +4826,27 @@ __metadata: languageName: node linkType: hard -"corser@npm:^2.0.1": - version: 2.0.1 - resolution: "corser@npm:2.0.1" - checksum: 10c0/1f319a752a560342dd22d936e5a4c158bfcbc332524ef5b05a7277236dad8b0b2868fd5cf818559f29954ec4d777d82e797fccd76601fcfe431610e4143c8acc +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 languageName: node linkType: hard -"create-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "create-jest@npm:29.7.0" +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - prompts: "npm:^2.0.1" - bin: - create-jest: bin/create-jest.js - checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + languageName: node + linkType: hard + +"corser@npm:^2.0.1": + version: 2.0.1 + resolution: "corser@npm:2.0.1" + checksum: 10c0/1f319a752a560342dd22d936e5a4c158bfcbc332524ef5b05a7277236dad8b0b2868fd5cf818559f29954ec4d777d82e797fccd76601fcfe431610e4143c8acc languageName: node linkType: hard @@ -3689,29 +4886,6 @@ __metadata: languageName: node linkType: hard -"cssom@npm:^0.5.0": - version: 0.5.0 - resolution: "cssom@npm:0.5.0" - checksum: 10c0/8c4121c243baf0678c65dcac29b201ff0067dfecf978de9d5c83b2ff127a8fdefd2bfd54577f5ad8c80ed7d2c8b489ae01c82023545d010c4ecb87683fb403dd - languageName: node - linkType: hard - -"cssom@npm:~0.3.6": - version: 0.3.8 - resolution: "cssom@npm:0.3.8" - checksum: 10c0/d74017b209440822f9e24d8782d6d2e808a8fdd58fa626a783337222fe1c87a518ba944d4c88499031b4786e68772c99dfae616638d71906fe9f203aeaf14411 - languageName: node - linkType: hard - -"cssstyle@npm:^2.3.0": - version: 2.3.0 - resolution: "cssstyle@npm:2.3.0" - dependencies: - cssom: "npm:~0.3.6" - checksum: 10c0/863400da2a458f73272b9a55ba7ff05de40d850f22eb4f37311abebd7eff801cf1cd2fb04c4c92b8c3daed83fe766e52e4112afb7bc88d86c63a9c2256a7d178 - languageName: node - linkType: hard - "csstype@npm:^3.0.2": version: 3.1.3 resolution: "csstype@npm:3.1.3" @@ -3726,17 +4900,6 @@ __metadata: languageName: node linkType: hard -"data-urls@npm:^3.0.2": - version: 3.0.2 - resolution: "data-urls@npm:3.0.2" - dependencies: - abab: "npm:^2.0.6" - whatwg-mimetype: "npm:^3.0.0" - whatwg-url: "npm:^11.0.0" - checksum: 10c0/051c3aaaf3e961904f136aab095fcf6dff4db23a7fc759dd8ba7b3e6ba03fc07ef608086caad8ab910d864bd3b5e57d0d2f544725653d77c96a2c971567045f4 - languageName: node - linkType: hard - "data-view-buffer@npm:^1.0.2": version: 1.0.2 resolution: "data-view-buffer@npm:1.0.2" @@ -3777,19 +4940,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": - version: 4.3.7 - resolution: "debug@npm:4.3.7" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b - languageName: node - linkType: hard - -"debug@npm:^2.2.0, debug@npm:^2.3.3": +"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -3798,6 +4949,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 + languageName: node + linkType: hard + "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -3807,13 +4970,6 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.2": - version: 10.4.3 - resolution: "decimal.js@npm:10.4.3" - checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee - languageName: node - linkType: hard - "decode-uri-component@npm:^0.2.0": version: 0.2.2 resolution: "decode-uri-component@npm:0.2.2" @@ -3821,15 +4977,10 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.0.0": - version: 1.5.3 - resolution: "dedent@npm:1.5.3" - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - checksum: 10c0/d94bde6e6f780be4da4fd760288fcf755ec368872f4ac5218197200d86430aeb8d90a003a840bff1c20221188e3f23adced0119cb811c6873c70d0ac66d12832 +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 languageName: node linkType: hard @@ -3840,10 +4991,20 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2": - version: 4.3.1 - resolution: "deepmerge@npm:4.3.1" - checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 10c0/957fb886502594c8e645e812dfe93dba30ed82e8460d20ce39c53c5b0f3e2afb6ceaec2249083b90bdfbb4cb0f34e1f73fde3d68cac00becdbcfd894156b5ead + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/73f17dc3c58026c55bb5538749597db31f9561c0193cd98604144b704a981c95a466f8ecc3c2db63d8bfd04fb0d426904834cfc91ae510c6aeb97e13c5167c4d languageName: node linkType: hard @@ -3858,7 +5019,14 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" dependencies: @@ -3897,19 +5065,33 @@ __metadata: languageName: node linkType: hard -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 +"depd@npm:2.0.0, depd@npm:^2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"depd@npm:~1.1.2": + version: 1.1.2 + resolution: "depd@npm:1.1.2" + checksum: 10c0/acb24aaf936ef9a227b6be6d495f0d2eb20108a9a6ad40585c5bda1a897031512fef6484e4fdbb80bd249fdaa82841fa1039f416ece03188e677ba11bcfda249 + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 languageName: node linkType: hard -"detect-gpu@npm:^5.0.28": - version: 5.0.53 - resolution: "detect-gpu@npm:5.0.53" +"detect-gpu@npm:^5.0.56": + version: 5.0.70 + resolution: "detect-gpu@npm:5.0.70" dependencies: webgl-constants: "npm:^1.1.1" - checksum: 10c0/0a0726be038ce50a7668d81a0f81f64e8a2bbd3ad88ac36fc03fd199eac55085b9b837a4dee5c8e65326569b0ca7aafcd454a56201a8d226672d2cc21d0a6dde + checksum: 10c0/5a2053d4779edb3490821599b316b5362c8b55c30b82806c64bcfd21f40c67e2debfd677937899080aea2b27765471552bafeeae9c9f1cf63bdd07461a7e2163 languageName: node linkType: hard @@ -3922,10 +5104,10 @@ __metadata: languageName: node linkType: hard -"detect-newline@npm:^3.0.0": - version: 3.1.0 - resolution: "detect-newline@npm:3.1.0" - checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10c0/f039f601790f2e9d4654e499913259a798b1f5246ae24f86ab5e8bd4aaf3bce50484234c494f11fb00aecb0c6e2733aa7b1cf3f530865640b65fbbd65b2c4e09 languageName: node linkType: hard @@ -3936,10 +5118,12 @@ __metadata: languageName: node linkType: hard -"diff-sequences@npm:^29.6.3": - version: 29.6.3 - resolution: "diff-sequences@npm:29.6.3" - checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 +"dns-packet@npm:^5.2.2": + version: 5.6.1 + resolution: "dns-packet@npm:5.6.1" + dependencies: + "@leichtgewicht/ip-codec": "npm:^2.0.1" + checksum: 10c0/8948d3d03063fb68e04a1e386875f8c3bcc398fc375f535f2b438fad8f41bf1afa6f5e70893ba44f4ae884c089247e0a31045722fa6ff0f01d228da103f1811d languageName: node linkType: hard @@ -3962,15 +5146,6 @@ __metadata: languageName: node linkType: hard -"domexception@npm:^4.0.0": - version: 4.0.0 - resolution: "domexception@npm:4.0.0" - dependencies: - webidl-conversions: "npm:^7.0.0" - checksum: 10c0/774277cd9d4df033f852196e3c0077a34dbd15a96baa4d166e0e47138a80f4c0bdf0d94e4703e6ff5883cec56bb821a6fff84402d8a498e31de7c87eb932a294 - languageName: node - linkType: hard - "dot-case@npm:^3.0.4": version: 3.0.4 resolution: "dot-case@npm:3.0.4" @@ -4006,6 +5181,13 @@ __metadata: languageName: node linkType: hard +"duplexer@npm:^0.1.2": + version: 0.1.2 + resolution: "duplexer@npm:0.1.2" + checksum: 10c0/c57bcd4bdf7e623abab2df43a7b5b23d18152154529d166c1e0da6bee341d84c432d157d7e97b32fecb1bf3a8b8857dd85ed81a915789f550637ed25b8e64fc2 + languageName: node + linkType: hard + "duplexify@npm:^4.1.1": version: 4.1.3 resolution: "duplexify@npm:4.1.3" @@ -4025,28 +5207,17 @@ __metadata: languageName: node linkType: hard -"ejs@npm:^3.1.10": - version: 3.1.10 - resolution: "ejs@npm:3.1.10" - dependencies: - jake: "npm:^10.8.5" - bin: - ejs: bin/cli.js - checksum: 10c0/52eade9e68416ed04f7f92c492183340582a36482836b11eab97b159fcdcfdedc62233a1bf0bf5e5e1851c501f2dca0e2e9afd111db2599e4e7f53ee29429ae1 - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.41": - version: 1.5.42 - resolution: "electron-to-chromium@npm:1.5.42" - checksum: 10c0/5a3a206a35f5a0894626f6f2a001e3b56a4d94af3193260e2819e8ecb2ee8ba1176e639b833a03fff5af8bfaafb0c3931b26a4421f79e2a58e85fd5bf02221f8 +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 languageName: node linkType: hard -"emittery@npm:^0.13.1": - version: 0.13.1 - resolution: "emittery@npm:0.13.1" - checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 +"electron-to-chromium@npm:^1.5.149": + version: 1.5.152 + resolution: "electron-to-chromium@npm:1.5.152" + checksum: 10c0/99c58dc8fc6b22ea64f118599663a0d336aa28693fbd275d06f3e2c1d1a6c954fcb88f5b2390223267bb3487940d3e587b6acac8b1b2ebc4dc65c44cd7739c7c languageName: node linkType: hard @@ -4064,6 +5235,20 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -4082,7 +5267,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.5.0": +"entities@npm:^4.4.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 @@ -4103,16 +5288,7 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce - languageName: node - linkType: hard - -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": version: 1.23.9 resolution: "es-abstract@npm:1.23.9" dependencies: @@ -4209,6 +5385,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.7.0": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": version: 1.1.1 resolution: "es-object-atoms@npm:1.1.1" @@ -4230,12 +5413,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10c0/f495af7b4b7601a4c0cfb893581c352636e5c08654d129590386a33a0432cf13a7bdc7b6493801cadd990d838e2839b9013d1de3b880440cb537825e834fe783 + hasown: "npm:^2.0.2" + checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb languageName: node linkType: hard @@ -4251,34 +5434,34 @@ __metadata: linkType: hard "esbuild@npm:^0.25.0": - version: 0.25.0 - resolution: "esbuild@npm:0.25.0" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.0" - "@esbuild/android-arm": "npm:0.25.0" - "@esbuild/android-arm64": "npm:0.25.0" - "@esbuild/android-x64": "npm:0.25.0" - "@esbuild/darwin-arm64": "npm:0.25.0" - "@esbuild/darwin-x64": "npm:0.25.0" - "@esbuild/freebsd-arm64": "npm:0.25.0" - "@esbuild/freebsd-x64": "npm:0.25.0" - "@esbuild/linux-arm": "npm:0.25.0" - "@esbuild/linux-arm64": "npm:0.25.0" - "@esbuild/linux-ia32": "npm:0.25.0" - "@esbuild/linux-loong64": "npm:0.25.0" - "@esbuild/linux-mips64el": "npm:0.25.0" - "@esbuild/linux-ppc64": "npm:0.25.0" - "@esbuild/linux-riscv64": "npm:0.25.0" - "@esbuild/linux-s390x": "npm:0.25.0" - "@esbuild/linux-x64": "npm:0.25.0" - "@esbuild/netbsd-arm64": "npm:0.25.0" - "@esbuild/netbsd-x64": "npm:0.25.0" - "@esbuild/openbsd-arm64": "npm:0.25.0" - "@esbuild/openbsd-x64": "npm:0.25.0" - "@esbuild/sunos-x64": "npm:0.25.0" - "@esbuild/win32-arm64": "npm:0.25.0" - "@esbuild/win32-ia32": "npm:0.25.0" - "@esbuild/win32-x64": "npm:0.25.0" + version: 0.25.4 + resolution: "esbuild@npm:0.25.4" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.4" + "@esbuild/android-arm": "npm:0.25.4" + "@esbuild/android-arm64": "npm:0.25.4" + "@esbuild/android-x64": "npm:0.25.4" + "@esbuild/darwin-arm64": "npm:0.25.4" + "@esbuild/darwin-x64": "npm:0.25.4" + "@esbuild/freebsd-arm64": "npm:0.25.4" + "@esbuild/freebsd-x64": "npm:0.25.4" + "@esbuild/linux-arm": "npm:0.25.4" + "@esbuild/linux-arm64": "npm:0.25.4" + "@esbuild/linux-ia32": "npm:0.25.4" + "@esbuild/linux-loong64": "npm:0.25.4" + "@esbuild/linux-mips64el": "npm:0.25.4" + "@esbuild/linux-ppc64": "npm:0.25.4" + "@esbuild/linux-riscv64": "npm:0.25.4" + "@esbuild/linux-s390x": "npm:0.25.4" + "@esbuild/linux-x64": "npm:0.25.4" + "@esbuild/netbsd-arm64": "npm:0.25.4" + "@esbuild/netbsd-x64": "npm:0.25.4" + "@esbuild/openbsd-arm64": "npm:0.25.4" + "@esbuild/openbsd-x64": "npm:0.25.4" + "@esbuild/sunos-x64": "npm:0.25.4" + "@esbuild/win32-arm64": "npm:0.25.4" + "@esbuild/win32-ia32": "npm:0.25.4" + "@esbuild/win32-x64": "npm:0.25.4" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -4332,7 +5515,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/5767b72da46da3cfec51661647ec850ddbf8a8d0662771139f10ef0692a8831396a0004b2be7966cecdb08264fb16bdc16290dcecd92396fac5f12d722fa013d + checksum: 10c0/db9f51248f0560bc46ab219461d338047617f6caf373c95f643b204760bdfa10c95b48cfde948949f7e509599ae4ab61c3f112092a3534936c6abfb800c565b0 languageName: node linkType: hard @@ -4343,10 +5526,10 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 +"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 languageName: node linkType: hard @@ -4357,24 +5540,6 @@ __metadata: languageName: node linkType: hard -"escodegen@npm:^2.0.0": - version: 2.1.0 - resolution: "escodegen@npm:2.1.0" - dependencies: - esprima: "npm:^4.0.1" - estraverse: "npm:^5.2.0" - esutils: "npm:^2.0.2" - source-map: "npm:~0.6.1" - dependenciesMeta: - source-map: - optional: true - bin: - escodegen: bin/escodegen.js - esgenerate: bin/esgenerate.js - checksum: 10c0/e1450a1f75f67d35c061bf0d60888b15f62ab63aef9df1901cffc81cffbbb9e8b3de237c5502cf8613a017c1df3a3003881307c78835a1ab54d8c8d2206e01d3 - languageName: node - linkType: hard - "eslint-import-resolver-node@npm:^0.3.9": version: 0.3.9 resolution: "eslint-import-resolver-node@npm:0.3.9" @@ -4517,7 +5682,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0": +"eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 @@ -4532,20 +5697,21 @@ __metadata: linkType: hard "eslint@npm:^9.21.0": - version: 9.24.0 - resolution: "eslint@npm:9.24.0" + version: 9.26.0 + resolution: "eslint@npm:9.26.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" "@eslint/config-array": "npm:^0.20.0" - "@eslint/config-helpers": "npm:^0.2.0" - "@eslint/core": "npm:^0.12.0" + "@eslint/config-helpers": "npm:^0.2.1" + "@eslint/core": "npm:^0.13.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.24.0" - "@eslint/plugin-kit": "npm:^0.2.7" + "@eslint/js": "npm:9.26.0" + "@eslint/plugin-kit": "npm:^0.2.8" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" + "@modelcontextprotocol/sdk": "npm:^1.8.0" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" @@ -4570,6 +5736,7 @@ __metadata: minimatch: "npm:^3.1.2" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" + zod: "npm:^3.24.2" peerDependencies: jiti: "*" peerDependenciesMeta: @@ -4577,7 +5744,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/f758ff1b9d2f2af5335f562f3f40aa8f71607b3edca33f7616840a222ed224555aeb3ac6943cc86e4f9ac5dc124a60bbfde624d054fb235631a8c04447e39ecc + checksum: 10c0/fb5ba6ce2b85a6c26c89bc1ca9b34f0ffa2166ba85d3d007a06bb2350151fb665e9a5f99d7f24051a00dc713203b50ece6e724a29fed7b297e432cdc79482fec languageName: node linkType: hard @@ -4592,7 +5759,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0, esprima@npm:^4.0.1": +"esprima@npm:^4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -4602,7 +5769,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.5.0": +"esquery@npm:^1.5.0, esquery@npm:^1.6.0": version: 1.6.0 resolution: "esquery@npm:1.6.0" dependencies: @@ -4627,6 +5794,15 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -4634,6 +5810,13 @@ __metadata: languageName: node linkType: hard +"etag@npm:^1.8.1, etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.0": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -4648,6 +5831,22 @@ __metadata: languageName: node linkType: hard +"eventsource-parser@npm:^3.0.1": + version: 3.0.1 + resolution: "eventsource-parser@npm:3.0.1" + checksum: 10c0/146ce5ae8325d07645a49bbc54d7ac3aef42f5138bfbbe83d5cf96293b50eab2219926d6cf41eed0a0f90132578089652ba9286a19297662900133a9da6c2fd0 + languageName: node + linkType: hard + +"eventsource@npm:^3.0.2": + version: 3.0.7 + resolution: "eventsource@npm:3.0.7" + dependencies: + eventsource-parser: "npm:^3.0.1" + checksum: 10c0/c48a73c38f300e33e9f11375d4ee969f25cbb0519608a12378a38068055ae8b55b6e0e8a49c3f91c784068434efe1d9f01eb49b6315b04b0da9157879ce2f67d + languageName: node + linkType: hard + "exec-sh@npm:^0.3.2": version: 0.3.6 resolution: "exec-sh@npm:0.3.6" @@ -4687,27 +5886,10 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.0.0": - version: 5.1.1 - resolution: "execa@npm:5.1.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.0" - human-signals: "npm:^2.1.0" - is-stream: "npm:^2.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^4.0.1" - onetime: "npm:^5.1.2" - signal-exit: "npm:^3.0.3" - strip-final-newline: "npm:^2.0.0" - checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f - languageName: node - linkType: hard - -"exit@npm:^0.1.2": - version: 0.1.2 - resolution: "exit@npm:0.1.2" - checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 +"exit-hook@npm:^4.0.0": + version: 4.0.0 + resolution: "exit-hook@npm:4.0.0" + checksum: 10c0/7fb33eaeb9050aee9479da9c93d42b796fb409c40e1d2b6ea2f40786ae7d7db6dc6a0f6ecc7bc24e479f957b7844bcb880044ded73320334743c64e3ecef48d7 languageName: node linkType: hard @@ -4726,16 +5908,10 @@ __metadata: languageName: node linkType: hard -"expect@npm:^29.0.0, expect@npm:^29.7.0": - version: 29.7.0 - resolution: "expect@npm:29.7.0" - dependencies: - "@jest/expect-utils": "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 +"expect-type@npm:^1.2.1": + version: 1.2.1 + resolution: "expect-type@npm:1.2.1" + checksum: 10c0/b775c9adab3c190dd0d398c722531726cdd6022849b4adba19dceab58dda7e000a7c6c872408cd73d665baa20d381eca36af4f7b393a4ba60dd10232d1fb8898 languageName: node linkType: hard @@ -4746,6 +5922,89 @@ __metadata: languageName: node linkType: hard +"express-rate-limit@npm:^7.5.0": + version: 7.5.0 + resolution: "express-rate-limit@npm:7.5.0" + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + checksum: 10c0/3e96afa05b4f577395688ede37e0cb19901f20c350b32575fb076f3d25176209fb88d3648151755c232aaf304147c58531f070757978f376e2f08326449299fd + languageName: node + linkType: hard + +"express@npm:^4.21.2": + version: 4.21.2 + resolution: "express@npm:4.21.2" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.7.1" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.3.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.12" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.13.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.2" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f + languageName: node + linkType: hard + +"express@npm:^5.0.1": + version: 5.1.0 + resolution: "express@npm:5.1.0" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.2.0" + content-disposition: "npm:^1.0.0" + content-type: "npm:^1.0.5" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.7" + qs: "npm:^6.14.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10c0/80ce7c53c5f56887d759b94c3f2283e2e51066c98d4b72a4cc1338e832b77f1e54f30d0239cc10815a0f849bdb753e6a284d2fa48d4ab56faf9c501f55d751d6 + languageName: node + linkType: hard + "extend-shallow@npm:^2.0.1": version: 2.0.1 resolution: "extend-shallow@npm:2.0.1" @@ -4789,19 +6048,19 @@ __metadata: linkType: hard "fast-glob@npm:^3.2.12, fast-glob@npm:^3.3.2": - version: 3.3.2 - resolution: "fast-glob@npm:3.3.2" + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" dependencies: "@nodelib/fs.stat": "npm:^2.0.2" "@nodelib/fs.walk": "npm:^1.2.3" glob-parent: "npm:^5.1.2" merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.4" - checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:^2.0.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b @@ -4815,12 +6074,28 @@ __metadata: languageName: node linkType: hard +"fast-uri@npm:^3.0.1": + version: 3.0.6 + resolution: "fast-uri@npm:3.0.6" + checksum: 10c0/74a513c2af0584448aee71ce56005185f81239eab7a2343110e5bad50c39ad4fb19c5a6f99783ead1cac7ccaf3461a6034fda89fffa2b30b6d99b9f21c2f9d29 + languageName: node + linkType: hard + "fastq@npm:^1.6.0": - version: 1.17.1 - resolution: "fastq@npm:1.17.1" + version: 1.19.1 + resolution: "fastq@npm:1.19.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 + checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 + languageName: node + linkType: hard + +"faye-websocket@npm:^0.11.3": + version: 0.11.4 + resolution: "faye-websocket@npm:0.11.4" + dependencies: + websocket-driver: "npm:>=0.5.1" + checksum: 10c0/c6052a0bb322778ce9f89af92890f6f4ce00d5ec92418a35e5f4c6864a4fe736fec0bcebd47eac7c0f0e979b01530746b1c85c83cb04bae789271abf19737420 languageName: node linkType: hard @@ -4833,18 +6108,6 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.4.3": - version: 6.4.3 - resolution: "fdir@npm:6.4.3" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10c0/d13c10120e9625adf21d8d80481586200759928c19405a816b77dd28eaeb80e7c59c5def3e2941508045eb06d34eb47fad865ccc8bf98e6ab988bb0ed160fb6f - languageName: node - linkType: hard - "fdir@npm:^6.4.4": version: 6.4.4 resolution: "fdir@npm:6.4.4" @@ -4887,15 +6150,6 @@ __metadata: languageName: node linkType: hard -"filelist@npm:^1.0.4": - version: 1.0.4 - resolution: "filelist@npm:1.0.4" - dependencies: - minimatch: "npm:^5.0.1" - checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 - languageName: node - linkType: hard - "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -4917,6 +6171,35 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: 10c0/d38035831865a49b5610206a3a9a9aae4e8523cbbcd01175d0480ffbf1278c47f11d89be3ca7f617ae6d94f29cf797546a4619cd84dd109009ef33f12f69019f + languageName: node + linkType: hard + +"finalhandler@npm:^2.1.0": + version: 2.1.0 + resolution: "finalhandler@npm:2.1.0" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10c0/da0bbca6d03873472ee890564eb2183f4ed377f25f3628a0fc9d16dac40bed7b150a0d82ebb77356e4c6d97d2796ad2dba22948b951dddee2c8768b0d1b9fb1f + languageName: node + linkType: hard + "find-parent-dir@npm:^0.3.1": version: 0.3.1 resolution: "find-parent-dir@npm:0.3.1" @@ -4924,7 +6207,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^4.0.0, find-up@npm:^4.1.0": +"find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" dependencies: @@ -4964,9 +6247,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.1 - resolution: "flatted@npm:3.3.1" - checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 languageName: node linkType: hard @@ -4989,12 +6272,12 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" dependencies: - is-callable: "npm:^1.1.3" - checksum: 10c0/22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee languageName: node linkType: hard @@ -5015,14 +6298,10 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.1 - resolution: "form-data@npm:4.0.1" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - mime-types: "npm:^2.1.12" - checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 languageName: node linkType: hard @@ -5035,6 +6314,20 @@ __metadata: languageName: node linkType: hard +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc + languageName: node + linkType: hard + "fs-extra@npm:^9.0.0": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" @@ -5063,7 +6356,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.1.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:^2.1.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -5073,7 +6366,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.1.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.1.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -5177,13 +6470,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^6.0.0": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 - languageName: node - linkType: hard - "get-symbol-description@npm:^1.1.0": version: 1.1.0 resolution: "get-symbol-description@npm:1.1.0" @@ -5223,7 +6509,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^5.1.2": +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -5316,7 +6602,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -5330,10 +6616,26 @@ __metadata: languageName: node linkType: hard +"gzip-size@npm:^6.0.0": + version: 6.0.0 + resolution: "gzip-size@npm:6.0.0" + dependencies: + duplexer: "npm:^0.1.2" + checksum: 10c0/4ccb924626c82125897a997d1c84f2377846a6ef57fbee38f7c0e6b41387fba4d00422274440747b58008b5d60114bac2349c2908e9aba55188345281af40a3f + languageName: node + linkType: hard + +"handle-thing@npm:^2.0.0": + version: 2.0.1 + resolution: "handle-thing@npm:2.0.1" + checksum: 10c0/7ae34ba286a3434f1993ebd1cc9c9e6b6d8ea672182db28b1afc0a7119229552fa7031e3e5f3cd32a76430ece4e94b7da6f12af2eb39d6239a7693e4bd63a998 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.2": - version: 1.0.2 - resolution: "has-bigints@npm:1.0.2" - checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 languageName: node linkType: hard @@ -5456,22 +6758,25 @@ __metadata: linkType: hard "hls.js@npm:^1.5.17": - version: 1.5.17 - resolution: "hls.js@npm:1.5.17" - checksum: 10c0/37b6fe2c8ed961b0b45ae961035aeafd203ff43aea26f0b63ac1382a05c45415e0e5d2302a2c4e316444e93d438ccd12e7cd36159e4f1fe2c3bfe57f53be3e98 + version: 1.6.2 + resolution: "hls.js@npm:1.6.2" + checksum: 10c0/c8700d88610afa85bf441cd624f6d548c1fea59c0e510fb05be02f9a33660b8a46cb17910ea5179b9d1307ce9242f4e904d8aa98d7f63d0df750d6d0f6e85134 languageName: node linkType: hard -"html-encoding-sniffer@npm:^3.0.0": - version: 3.0.0 - resolution: "html-encoding-sniffer@npm:3.0.0" +"hpack.js@npm:^2.1.6": + version: 2.1.6 + resolution: "hpack.js@npm:2.1.6" dependencies: - whatwg-encoding: "npm:^2.0.0" - checksum: 10c0/b17b3b0fb5d061d8eb15121c3b0b536376c3e295ecaf09ba48dd69c6b6c957839db124fe1e2b3f11329753a4ee01aa7dedf63b7677999e86da17fbbdd82c5386 + inherits: "npm:^2.0.1" + obuf: "npm:^1.0.0" + readable-stream: "npm:^2.0.1" + wbuf: "npm:^1.1.0" + checksum: 10c0/55b9e824430bab82a19d079cb6e33042d7d0640325678c9917fcc020c61d8a08ca671b6c942c7f0aae9bb6e4b67ffb50734a72f9e21d66407c3138c1983b70f0 languageName: node linkType: hard -"html-escaper@npm:^2.0.0": +"html-escaper@npm:^2.0.2": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 @@ -5479,20 +6784,48 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 languageName: node linkType: hard -"http-proxy-agent@npm:^5.0.0": - version: 5.0.0 - resolution: "http-proxy-agent@npm:5.0.0" +"http-deceiver@npm:^1.2.7": + version: 1.2.7 + resolution: "http-deceiver@npm:1.2.7" + checksum: 10c0/8bb9b716f5fc55f54a451da7f49b9c695c3e45498a789634daec26b61e4add7c85613a4a9e53726c39d09de7a163891ecd6eb5809adb64500a840fd86fe81d03 + languageName: node + linkType: hard + +"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" dependencies: - "@tootallnate/once": "npm:2" - agent-base: "npm:6" - debug: "npm:4" - checksum: 10c0/32a05e413430b2c1e542e5c74b38a9f14865301dd69dff2e53ddb684989440e3d2ce0c4b64d25eb63cf6283e6265ff979a61cf93e3ca3d23047ddfdc8df34a32 + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"http-errors@npm:~1.6.2": + version: 1.6.3 + resolution: "http-errors@npm:1.6.3" + dependencies: + depd: "npm:~1.1.2" + inherits: "npm:2.0.3" + setprototypeof: "npm:1.1.0" + statuses: "npm:>= 1.4.0 < 2" + checksum: 10c0/17ec4046ee974477778bfdd525936c254b872054703ec2caa4d6f099566b8adade636ae6aeeacb39302c5cd6e28fb407ebd937f500f5010d0b6850750414ff78 + languageName: node + linkType: hard + +"http-parser-js@npm:>=0.5.1": + version: 0.5.10 + resolution: "http-parser-js@npm:0.5.10" + checksum: 10c0/8bbcf1832a8d70b2bd515270112116333add88738a2cc05bfb94ba6bde3be4b33efee5611584113818d2bcf654fdc335b652503be5a6b4c0b95e46f214187d93 languageName: node linkType: hard @@ -5506,7 +6839,25 @@ __metadata: languageName: node linkType: hard -"http-proxy@npm:^1.18.0": +"http-proxy-middleware@npm:^2.0.7": + version: 2.0.9 + resolution: "http-proxy-middleware@npm:2.0.9" + dependencies: + "@types/http-proxy": "npm:^1.17.8" + http-proxy: "npm:^1.18.1" + is-glob: "npm:^4.0.1" + is-plain-obj: "npm:^3.0.0" + micromatch: "npm:^4.0.2" + peerDependencies: + "@types/express": ^4.17.13 + peerDependenciesMeta: + "@types/express": + optional: true + checksum: 10c0/8e9032af625f7c9f2f0d318f6cdb14eb725cc16ffe7b4ccccea25cf591fa819bb7c3bb579e0b543e0ae9c73059b505a6d728290c757bff27bae526a6ed11c05e + languageName: node + linkType: hard + +"http-proxy@npm:^1.18.0, http-proxy@npm:^1.18.1": version: 1.18.1 resolution: "http-proxy@npm:1.18.1" dependencies: @@ -5540,16 +6891,6 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.1": - version: 5.0.1 - resolution: "https-proxy-agent@npm:5.0.1" - dependencies: - agent-base: "npm:6" - debug: "npm:4" - checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 - languageName: node - linkType: hard - "https-proxy-agent@npm:^7.0.1": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" @@ -5567,13 +6908,6 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^2.1.0": - version: 2.1.0 - resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - "husky@npm:^9.0.11": version: 9.1.7 resolution: "husky@npm:9.1.7" @@ -5583,7 +6917,23 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": +"hyperdyperid@npm:^1.2.0": + version: 1.2.0 + resolution: "hyperdyperid@npm:1.2.0" + checksum: 10c0/885ba3177c7181d315a856ee9c0005ff8eb5dcb1ce9e9d61be70987895d934d84686c37c981cceeb53216d4c9c15c1cc25f1804e84cc6a74a16993c5d7fd0893 + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -5599,13 +6949,20 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0, ignore@npm:^5.3.1": +"ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 languageName: node linkType: hard +"ignore@npm:^7.0.0": + version: 7.0.4 + resolution: "ignore@npm:7.0.4" + checksum: 10c0/90e1f69ce352b9555caecd9cbfd07abe7626d312a6f90efbbb52c7edca6ea8df065d66303863b30154ab1502afb2da8bc59d5b04e1719a52ef75bbf675c488eb + languageName: node + linkType: hard + "immediate@npm:~3.0.5": version: 3.0.6 resolution: "immediate@npm:3.0.6" @@ -5614,31 +6971,19 @@ __metadata: linkType: hard "immutable@npm:^5.0.2": - version: 5.0.3 - resolution: "immutable@npm:5.0.3" - checksum: 10c0/3269827789e1026cd25c2ea97f0b2c19be852ffd49eda1b674b20178f73d84fa8d945ad6f5ac5bc4545c2b4170af9f6e1f77129bc1cae7974a4bf9b04a9cdfb9 + version: 5.1.2 + resolution: "immutable@npm:5.1.2" + checksum: 10c0/da5af92d2c70323c1f9a0e418832c9eef441feadaf6a295a4e07764bd2400c85186872e016071d9253549d58d364160d55dca8dcdf59fd4a6a06c6756fe61657 languageName: node linkType: hard "import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" dependencies: parent-module: "npm:^1.0.0" resolve-from: "npm:^4.0.0" - checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 - languageName: node - linkType: hard - -"import-local@npm:^3.0.2": - version: 3.2.0 - resolution: "import-local@npm:3.2.0" - dependencies: - pkg-dir: "npm:^4.2.0" - resolve-cwd: "npm:^3.0.0" - bin: - import-local-fixture: fixtures/cli.js - checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec languageName: node linkType: hard @@ -5659,13 +7004,20 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 languageName: node linkType: hard +"inherits@npm:2.0.3": + version: 2.0.3 + resolution: "inherits@npm:2.0.3" + checksum: 10c0/6e56402373149ea076a434072671f9982f5fad030c7662be0332122fe6c0fa490acb3cc1010d90b6eff8d640b1167d77674add52dfd1bb85d545cf29e80e73e7 + languageName: node + linkType: hard + "internal-slot@npm:^1.1.0": version: 1.1.0 resolution: "internal-slot@npm:1.1.0" @@ -5677,6 +7029,13 @@ __metadata: languageName: node linkType: hard +"interpret@npm:^3.1.1": + version: 3.1.1 + resolution: "interpret@npm:3.1.1" + checksum: 10c0/6f3c4d0aa6ec1b43a8862375588a249e3c917739895cbe67fe12f0a76260ea632af51e8e2431b50fbcd0145356dc28ca147be08dbe6a523739fd55c0f91dc2a5 + languageName: node + linkType: hard + "ip-address@npm:^9.0.5": version: 9.0.5 resolution: "ip-address@npm:9.0.5" @@ -5687,6 +7046,20 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"ipaddr.js@npm:^2.1.0": + version: 2.2.0 + resolution: "ipaddr.js@npm:2.2.0" + checksum: 10c0/e4ee875dc1bd92ac9d27e06cfd87cdb63ca786ff9fd7718f1d4f7a8ef27db6e5d516128f52d2c560408cbb75796ac2f83ead669e73507c86282d45f84c5abbb6 + languageName: node + linkType: hard + "is-accessor-descriptor@npm:^1.0.1": version: 1.0.1 resolution: "is-accessor-descriptor@npm:1.0.1" @@ -5707,13 +7080,6 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 - languageName: node - linkType: hard - "is-async-function@npm:^2.0.0": version: 2.1.1 resolution: "is-async-function@npm:2.1.1" @@ -5743,6 +7109,15 @@ __metadata: languageName: node linkType: hard +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + "is-blob@npm:^1.0.0": version: 1.0.0 resolution: "is-blob@npm:1.0.0" @@ -5781,7 +7156,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.2.7": +"is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f @@ -5799,12 +7174,12 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1": - version: 2.15.1 - resolution: "is-core-module@npm:2.15.1" +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" dependencies: hasown: "npm:^2.0.2" - checksum: 10c0/53432f10c69c40bfd2fa8914133a68709ff9498c86c3bf5fca3cdf3145a56fd2168cbf4a43b29843a6202a120a5f9c5ffba0a4322e1e3441739bc0b641682612 + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd languageName: node linkType: hard @@ -5867,6 +7242,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extendable@npm:^0.1.0, is-extendable@npm:^0.1.1": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" @@ -5906,13 +7290,6 @@ __metadata: languageName: node linkType: hard -"is-generator-fn@npm:^2.0.0": - version: 2.1.0 - resolution: "is-generator-fn@npm:2.1.0" - checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d - languageName: node - linkType: hard - "is-generator-function@npm:^1.0.10": version: 1.1.0 resolution: "is-generator-function@npm:1.1.0" @@ -5925,7 +7302,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -5934,6 +7311,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-map@npm:^2.0.3": version: 2.0.3 resolution: "is-map@npm:2.0.3" @@ -5941,6 +7329,13 @@ __metadata: languageName: node linkType: hard +"is-network-error@npm:^1.0.0": + version: 1.1.0 + resolution: "is-network-error@npm:1.1.0" + checksum: 10c0/89eef83c2a4cf43d853145ce175d1cf43183b7a58d48c7a03e7eed4eb395d0934c1f6d101255cdd8c8c2980ab529bfbe5dd9edb24e1c3c28d2b3c814469b5b7d + languageName: node + linkType: hard + "is-number-object@npm:^1.1.1": version: 1.1.1 resolution: "is-number-object@npm:1.1.1" @@ -5967,6 +7362,13 @@ __metadata: languageName: node linkType: hard +"is-plain-obj@npm:^3.0.0": + version: 3.0.0 + resolution: "is-plain-obj@npm:3.0.0" + checksum: 10c0/8e6483bfb051d42ec9c704c0ede051a821c6b6f9a6c7a3e3b55aa855e00981b0580c8f3b1f5e2e62649b39179b1abfee35d6f8086d999bfaa32c1908d29b07bc + languageName: node + linkType: hard + "is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4": version: 2.0.4 resolution: "is-plain-object@npm:2.0.4" @@ -5976,13 +7378,6 @@ __metadata: languageName: node linkType: hard -"is-potential-custom-element-name@npm:^1.0.1": - version: 1.0.1 - resolution: "is-potential-custom-element-name@npm:1.0.1" - checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9 - languageName: node - linkType: hard - "is-promise@npm:^2.1.0": version: 2.2.2 resolution: "is-promise@npm:2.2.2" @@ -5990,6 +7385,13 @@ __metadata: languageName: node linkType: hard +"is-promise@npm:^4.0.0": + version: 4.0.0 + resolution: "is-promise@npm:4.0.0" + checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 + languageName: node + linkType: hard + "is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" @@ -6111,7 +7513,16 @@ __metadata: languageName: node linkType: hard -"isarray@npm:1.0.0": +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + +"isarray@npm:1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d @@ -6155,7 +7566,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": +"istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b @@ -6175,51 +7586,6 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^6.0.0": - version: 6.0.3 - resolution: "istanbul-lib-instrument@npm:6.0.3" - dependencies: - "@babel/core": "npm:^7.23.9" - "@babel/parser": "npm:^7.23.9" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^7.5.4" - checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 - languageName: node - linkType: hard - -"istanbul-lib-report@npm:^3.0.0": - version: 3.0.1 - resolution: "istanbul-lib-report@npm:3.0.1" - dependencies: - istanbul-lib-coverage: "npm:^3.0.0" - make-dir: "npm:^4.0.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 - languageName: node - linkType: hard - -"istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.1 - resolution: "istanbul-lib-source-maps@npm:4.0.1" - dependencies: - debug: "npm:^4.1.1" - istanbul-lib-coverage: "npm:^3.0.0" - source-map: "npm:^0.6.1" - checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.3": - version: 3.1.7 - resolution: "istanbul-reports@npm:3.1.7" - dependencies: - html-escaper: "npm:^2.0.0" - istanbul-lib-report: "npm:^3.0.0" - checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 - languageName: node - linkType: hard - "iterator.prototype@npm:^1.1.4": version: 1.1.5 resolution: "iterator.prototype@npm:1.1.5" @@ -6240,424 +7606,53 @@ __metadata: dependencies: "@types/react-reconciler": "npm:^0.28.0" peerDependencies: - react: ">=18.0" - checksum: 10c0/5618955ac7ae0d7788580186f91ac9989301ead2c87250883258cd7ee32f9a4e08574728506d1deb90477090d7a688ba19571126aae9fb25661e90057dd772df - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 - languageName: node - linkType: hard - -"jake@npm:^10.8.5": - version: 10.9.2 - resolution: "jake@npm:10.9.2" - dependencies: - async: "npm:^3.2.3" - chalk: "npm:^4.0.2" - filelist: "npm:^1.0.4" - minimatch: "npm:^3.1.2" - bin: - jake: bin/cli.js - checksum: 10c0/c4597b5ed9b6a908252feab296485a4f87cba9e26d6c20e0ca144fb69e0c40203d34a2efddb33b3d297b8bd59605e6c1f44f6221ca1e10e69175ecbf3ff5fe31 - languageName: node - linkType: hard - -"jest-changed-files@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-changed-files@npm:29.7.0" - dependencies: - execa: "npm:^5.0.0" - jest-util: "npm:^29.7.0" - p-limit: "npm:^3.1.0" - checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b - languageName: node - linkType: hard - -"jest-circus@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-circus@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - co: "npm:^4.6.0" - dedent: "npm:^1.0.0" - is-generator-fn: "npm:^2.0.0" - jest-each: "npm:^29.7.0" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - p-limit: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - pure-rand: "npm:^6.0.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e - languageName: node - linkType: hard - -"jest-cli@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-cli@npm:29.7.0" - dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - create-jest: "npm:^29.7.0" - exit: "npm:^0.1.2" - import-local: "npm:^3.0.2" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - yargs: "npm:^17.3.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: bin/jest.js - checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a - languageName: node - linkType: hard - -"jest-config@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-config@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/test-sequencer": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-jest: "npm:^29.7.0" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - deepmerge: "npm:^4.2.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-circus: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - parse-json: "npm:^5.2.0" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - peerDependencies: - "@types/node": "*" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - ts-node: - optional: true - checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 - languageName: node - linkType: hard - -"jest-diff@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-diff@npm:29.7.0" - dependencies: - chalk: "npm:^4.0.0" - diff-sequences: "npm:^29.6.3" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 - languageName: node - linkType: hard - -"jest-docblock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-docblock@npm:29.7.0" - dependencies: - detect-newline: "npm:^3.0.0" - checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 - languageName: node - linkType: hard - -"jest-each@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-each@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - pretty-format: "npm:^29.7.0" - checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 - languageName: node - linkType: hard - -"jest-environment-jsdom@npm:^29.4.1": - version: 29.7.0 - resolution: "jest-environment-jsdom@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/jsdom": "npm:^20.0.0" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jsdom: "npm:^20.0.0" - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - checksum: 10c0/139b94e2c8ec1bb5a46ce17df5211da65ce867354b3fd4e00fa6a0d1da95902df4cf7881273fc6ea937e5c325d39d6773f0d41b6c469363334de9d489d2c321f - languageName: node - linkType: hard - -"jest-environment-node@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-environment-node@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b - languageName: node - linkType: hard - -"jest-get-type@npm:^29.6.3": - version: 29.6.3 - resolution: "jest-get-type@npm:29.6.3" - checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b - languageName: node - linkType: hard - -"jest-haste-map@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-haste-map@npm:26.6.2" - dependencies: - "@jest/types": "npm:^26.6.2" - "@types/graceful-fs": "npm:^4.1.2" - "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.1.2" - graceful-fs: "npm:^4.2.4" - jest-regex-util: "npm:^26.0.0" - jest-serializer: "npm:^26.6.2" - jest-util: "npm:^26.6.2" - jest-worker: "npm:^26.6.2" - micromatch: "npm:^4.0.2" - sane: "npm:^4.0.3" - walker: "npm:^1.0.7" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/85a40d8ecf4bfb659613f107c963c7366cdf6dcceb0ca73dc8ca09fbe0e2a63b976940f573db6260c43011993cb804275f447f268c3bc4b680c08baed300701d - languageName: node - linkType: hard - -"jest-haste-map@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-haste-map@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/graceful-fs": "npm:^4.1.3" - "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.3.2" - graceful-fs: "npm:^4.2.9" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c - languageName: node - linkType: hard - -"jest-leak-detector@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-leak-detector@npm:29.7.0" - dependencies: - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" - dependencies: - chalk: "npm:^4.0.0" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e - languageName: node - linkType: hard - -"jest-message-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-message-util@npm:29.7.0" - dependencies: - "@babel/code-frame": "npm:^7.12.13" - "@jest/types": "npm:^29.6.3" - "@types/stack-utils": "npm:^2.0.0" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 - languageName: node - linkType: hard - -"jest-mock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-mock@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-util: "npm:^29.7.0" - checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac - languageName: node - linkType: hard - -"jest-pnp-resolver@npm:^1.2.2": - version: 1.2.3 - resolution: "jest-pnp-resolver@npm:1.2.3" - peerDependencies: - jest-resolve: "*" - peerDependenciesMeta: - jest-resolve: - optional: true - checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac - languageName: node - linkType: hard - -"jest-regex-util@npm:^26.0.0": - version: 26.0.0 - resolution: "jest-regex-util@npm:26.0.0" - checksum: 10c0/988675764a08945b90f48e6f5a8640b0d9885a977f100a168061d10037d53808a6cdb7dc8cb6fe9b1332f0523b42bf3edbb6d2cc6c7f7ba582d05d432efb3e60 - languageName: node - linkType: hard - -"jest-regex-util@npm:^29.6.3": - version: 29.6.3 - resolution: "jest-regex-util@npm:29.6.3" - checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + react: ">=18.0" + checksum: 10c0/5618955ac7ae0d7788580186f91ac9989301ead2c87250883258cd7ee32f9a4e08574728506d1deb90477090d7a688ba19571126aae9fb25661e90057dd772df languageName: node linkType: hard -"jest-resolve-dependencies@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve-dependencies@npm:29.7.0" +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" dependencies: - jest-regex-util: "npm:^29.6.3" - jest-snapshot: "npm:^29.7.0" - checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 languageName: node linkType: hard -"jest-resolve@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve@npm:29.7.0" +"jest-haste-map@npm:^26.6.2": + version: 26.6.2 + resolution: "jest-haste-map@npm:26.6.2" dependencies: - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-pnp-resolver: "npm:^1.2.2" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - resolve: "npm:^1.20.0" - resolve.exports: "npm:^2.0.0" - slash: "npm:^3.0.0" - checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + "@jest/types": "npm:^26.6.2" + "@types/graceful-fs": "npm:^4.1.2" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.1.2" + graceful-fs: "npm:^4.2.4" + jest-regex-util: "npm:^26.0.0" + jest-serializer: "npm:^26.6.2" + jest-util: "npm:^26.6.2" + jest-worker: "npm:^26.6.2" + micromatch: "npm:^4.0.2" + sane: "npm:^4.0.3" + walker: "npm:^1.0.7" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/85a40d8ecf4bfb659613f107c963c7366cdf6dcceb0ca73dc8ca09fbe0e2a63b976940f573db6260c43011993cb804275f447f268c3bc4b680c08baed300701d languageName: node linkType: hard -"jest-runner@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runner@npm:29.7.0" - dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/environment": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - emittery: "npm:^0.13.1" - graceful-fs: "npm:^4.2.9" - jest-docblock: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-leak-detector: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-resolve: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - p-limit: "npm:^3.1.0" - source-map-support: "npm:0.5.13" - checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 - languageName: node - linkType: hard - -"jest-runtime@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runtime@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/globals": "npm:^29.7.0" - "@jest/source-map": "npm:^29.6.3" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - cjs-module-lexer: "npm:^1.0.0" - collect-v8-coverage: "npm:^1.0.0" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-bom: "npm:^4.0.0" - checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 +"jest-regex-util@npm:^26.0.0": + version: 26.0.0 + resolution: "jest-regex-util@npm:26.0.0" + checksum: 10c0/988675764a08945b90f48e6f5a8640b0d9885a977f100a168061d10037d53808a6cdb7dc8cb6fe9b1332f0523b42bf3edbb6d2cc6c7f7ba582d05d432efb3e60 languageName: node linkType: hard @@ -6671,34 +7666,6 @@ __metadata: languageName: node linkType: hard -"jest-snapshot@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-snapshot@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@babel/generator": "npm:^7.7.2" - "@babel/plugin-syntax-jsx": "npm:^7.7.2" - "@babel/plugin-syntax-typescript": "npm:^7.7.2" - "@babel/types": "npm:^7.3.3" - "@jest/expect-utils": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" - chalk: "npm:^4.0.0" - expect: "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - natural-compare: "npm:^1.4.0" - pretty-format: "npm:^29.7.0" - semver: "npm:^7.5.3" - checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 - languageName: node - linkType: hard - "jest-util@npm:^26.6.2": version: 26.6.2 resolution: "jest-util@npm:26.6.2" @@ -6713,50 +7680,6 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^29.0.0, jest-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-util@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - graceful-fs: "npm:^4.2.9" - picomatch: "npm:^2.2.3" - checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 - languageName: node - linkType: hard - -"jest-validate@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-validate@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - camelcase: "npm:^6.2.0" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - leven: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 - languageName: node - linkType: hard - -"jest-watcher@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-watcher@npm:29.7.0" - dependencies: - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" - emittery: "npm:^0.13.1" - jest-util: "npm:^29.7.0" - string-length: "npm:^4.0.1" - checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 - languageName: node - linkType: hard - "jest-worker@npm:^26.6.2": version: 26.6.2 resolution: "jest-worker@npm:26.6.2" @@ -6768,37 +7691,6 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-worker@npm:29.7.0" - dependencies: - "@types/node": "npm:*" - jest-util: "npm:^29.7.0" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.0.0" - checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 - languageName: node - linkType: hard - -"jest@npm:^29.7.0": - version: 29.7.0 - resolution: "jest@npm:29.7.0" - dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - import-local: "npm:^3.0.2" - jest-cli: "npm:^29.7.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: bin/jest.js - checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b - languageName: node - linkType: hard - "js-base64@npm:^3.7.5": version: 3.7.7 resolution: "js-base64@npm:3.7.7" @@ -6874,51 +7766,19 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^20.0.0": - version: 20.0.3 - resolution: "jsdom@npm:20.0.3" - dependencies: - abab: "npm:^2.0.6" - acorn: "npm:^8.8.1" - acorn-globals: "npm:^7.0.0" - cssom: "npm:^0.5.0" - cssstyle: "npm:^2.3.0" - data-urls: "npm:^3.0.2" - decimal.js: "npm:^10.4.2" - domexception: "npm:^4.0.0" - escodegen: "npm:^2.0.0" - form-data: "npm:^4.0.0" - html-encoding-sniffer: "npm:^3.0.0" - http-proxy-agent: "npm:^5.0.0" - https-proxy-agent: "npm:^5.0.1" - is-potential-custom-element-name: "npm:^1.0.1" - nwsapi: "npm:^2.2.2" - parse5: "npm:^7.1.1" - saxes: "npm:^6.0.0" - symbol-tree: "npm:^3.2.4" - tough-cookie: "npm:^4.1.2" - w3c-xmlserializer: "npm:^4.0.0" - webidl-conversions: "npm:^7.0.0" - whatwg-encoding: "npm:^2.0.0" - whatwg-mimetype: "npm:^3.0.0" - whatwg-url: "npm:^11.0.0" - ws: "npm:^8.11.0" - xml-name-validator: "npm:^4.0.0" - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - checksum: 10c0/b109073bb826a966db7828f46cb1d7371abecd30f182b143c52be5fe1ed84513bbbe995eb3d157241681fcd18331381e61e3dc004d4949f3a63bca02f6214902 +"jsdoc-type-pratt-parser@npm:~4.1.0": + version: 4.1.0 + resolution: "jsdoc-type-pratt-parser@npm:4.1.0" + checksum: 10c0/7700372d2e733a32f7ea0a1df9cec6752321a5345c11a91b2ab478a031a426e934f16d5c1f15c8566c7b2c10af9f27892a29c2c789039f595470e929a4aa60ea languageName: node linkType: hard "jsesc@npm:^3.0.2": - version: 3.0.2 - resolution: "jsesc@npm:3.0.2" + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" bin: jsesc: bin/jsesc - checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1 + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 languageName: node linkType: hard @@ -6929,13 +7789,6 @@ __metadata: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 - languageName: node - linkType: hard - "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -6943,6 +7796,13 @@ __metadata: languageName: node linkType: hard +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -6970,13 +7830,6 @@ __metadata: languageName: node linkType: hard -"jsonc-parser@npm:^3.2.0": - version: 3.3.1 - resolution: "jsonc-parser@npm:3.3.1" - checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 - languageName: node - linkType: hard - "jsonfile@npm:^6.0.1": version: 6.1.0 resolution: "jsonfile@npm:6.1.0" @@ -6990,6 +7843,13 @@ __metadata: languageName: node linkType: hard +"jsonschema@npm:^1.5.0": + version: 1.5.0 + resolution: "jsonschema@npm:1.5.0" + checksum: 10c0/c24ddb8d741f02efc0da3ad9b597a275f6b595062903d3edbfaa535c3f9c4c98613df68da5cb6635ed9aeab30d658986fea61d7662fc5b2b92840d5a1e21235e + languageName: node + linkType: hard + "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": version: 3.3.5 resolution: "jsx-ast-utils@npm:3.3.5" @@ -7045,13 +7905,6 @@ __metadata: languageName: node linkType: hard -"kleur@npm:^3.0.3": - version: 3.0.3 - resolution: "kleur@npm:3.0.3" - checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b - languageName: node - linkType: hard - "language-subtag-registry@npm:^0.3.20": version: 0.3.23 resolution: "language-subtag-registry@npm:0.3.23" @@ -7068,6 +7921,16 @@ __metadata: languageName: node linkType: hard +"launch-editor@npm:^2.6.1": + version: 2.10.0 + resolution: "launch-editor@npm:2.10.0" + dependencies: + picocolors: "npm:^1.0.0" + shell-quote: "npm:^1.8.1" + checksum: 10c0/8b5a26be6b0da1da039ed2254b837dea0651a6406ea4dc4c9a5b28ea72862f1b12880135c495baf9d8a08997473b44034172506781744cf82e155451a40b7d51 + languageName: node + linkType: hard + "leven@npm:^2.1.0": version: 2.1.0 resolution: "leven@npm:2.1.0" @@ -7075,13 +7938,6 @@ __metadata: languageName: node linkType: hard -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df - languageName: node - linkType: hard - "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -7108,10 +7964,12 @@ __metadata: languageName: node linkType: hard -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d +"linkify-it@npm:^5.0.0": + version: 5.0.0 + resolution: "linkify-it@npm:5.0.0" + dependencies: + uc.micro: "npm:^2.0.0" + checksum: 10c0/ff4abbcdfa2003472fc3eb4b8e60905ec97718e11e33cca52059919a4c80cc0e0c2a14d23e23d8c00e5402bc5a885cdba8ca053a11483ab3cc8b3c7a52f88e2d languageName: node linkType: hard @@ -7147,21 +8005,14 @@ __metadata: languageName: node linkType: hard -"lodash.memoize@npm:^4.1.2": - version: 4.1.2 - resolution: "lodash.memoize@npm:4.1.2" - checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 - languageName: node - linkType: hard - -"lodash.merge@npm:^4.6.2": +"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 languageName: node linkType: hard -"lodash@npm:^4.17.14, lodash@npm:^4.17.21": +"lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -7179,6 +8030,13 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^3.1.0, loupe@npm:^3.1.3": + version: 3.1.3 + resolution: "loupe@npm:3.1.3" + checksum: 10c0/f5dab4144254677de83a35285be1b8aba58b3861439ce4ba65875d0d5f3445a4a496daef63100ccf02b2dbc25bf58c6db84c9cb0b96d6435331e9d0a33b48541 + languageName: node + linkType: hard + "lower-case@npm:^2.0.2": version: 2.0.2 resolution: "lower-case@npm:2.0.2" @@ -7220,7 +8078,7 @@ __metadata: languageName: node linkType: hard -"maath@npm:^0.10.7": +"maath@npm:^0.10.8": version: 0.10.8 resolution: "maath@npm:0.10.8" peerDependencies: @@ -7230,19 +8088,12 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^4.0.0": - version: 4.0.0 - resolution: "make-dir@npm:4.0.0" +"magic-string@npm:^0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" dependencies: - semver: "npm:^7.5.3" - checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 - languageName: node - linkType: hard - -"make-error@npm:^1.3.6": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 languageName: node linkType: hard @@ -7290,12 +8141,19 @@ __metadata: languageName: node linkType: hard -"marked@npm:^4.3.0": - version: 4.3.0 - resolution: "marked@npm:4.3.0" +"markdown-it@npm:^14.1.0": + version: 14.1.0 + resolution: "markdown-it@npm:14.1.0" + dependencies: + argparse: "npm:^2.0.1" + entities: "npm:^4.4.0" + linkify-it: "npm:^5.0.0" + mdurl: "npm:^2.0.0" + punycode.js: "npm:^2.3.1" + uc.micro: "npm:^2.1.0" bin: - marked: bin/marked.js - checksum: 10c0/0013463855e31b9c88d8bb2891a611d10ef1dc79f2e3cbff1bf71ba389e04c5971298c886af0be799d7fa9aa4593b086a136062d59f1210b0480b026a8c5dc47 + markdown-it: bin/markdown-it.mjs + checksum: 10c0/9a6bb444181d2db7016a4173ae56a95a62c84d4cbfb6916a399b11d3e6581bf1cc2e4e1d07a2f022ae72c25f56db90fbe1e529fca16fbf9541659dc53480d4b4 languageName: node linkType: hard @@ -7306,6 +8164,53 @@ __metadata: languageName: node linkType: hard +"mdurl@npm:^2.0.0": + version: 2.0.0 + resolution: "mdurl@npm:2.0.0" + checksum: 10c0/633db522272f75ce4788440669137c77540d74a83e9015666a9557a152c02e245b192edc20bc90ae953bbab727503994a53b236b4d9c99bdaee594d0e7dd2ce0 + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"media-typer@npm:^1.1.0": + version: 1.1.0 + resolution: "media-typer@npm:1.1.0" + checksum: 10c0/7b4baa40b25964bb90e2121ee489ec38642127e48d0cc2b6baa442688d3fde6262bfdca86d6bbf6ba708784afcac168c06840c71facac70e390f5f759ac121b9 + languageName: node + linkType: hard + +"memfs@npm:^4.6.0": + version: 4.17.1 + resolution: "memfs@npm:4.17.1" + dependencies: + "@jsonjoy.com/json-pack": "npm:^1.0.3" + "@jsonjoy.com/util": "npm:^1.3.0" + tree-dump: "npm:^1.0.1" + tslib: "npm:^2.0.0" + checksum: 10c0/f3e7931697e268b24506abe56ef5729cebddf8568cac76c7bd1b07e9964a26f16e6281c52a22333b9a42f1275f80b8c7f6d61b9f7a92e24401066ca2a0809100 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -7320,7 +8225,7 @@ __metadata: languageName: node linkType: hard -"meshline@npm:^3.1.6": +"meshline@npm:^3.3.1": version: 3.3.1 resolution: "meshline@npm:3.3.1" peerDependencies: @@ -7336,6 +8241,13 @@ __metadata: languageName: node linkType: hard +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + "micromatch@npm:^3.1.4": version: 3.1.10 resolution: "micromatch@npm:3.1.10" @@ -7357,7 +8269,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -7374,7 +8286,14 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12": +"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -7383,7 +8302,16 @@ __metadata: languageName: node linkType: hard -"mime@npm:^1.6.0": +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": + version: 3.0.1 + resolution: "mime-types@npm:3.0.1" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10c0/bd8c20d3694548089cf229016124f8f40e6a60bbb600161ae13e45f793a2d5bb40f96bbc61f275836696179c77c1d6bf4967b2a75e0a8ad40fe31f4ed5be4da5 + languageName: node + linkType: hard + +"mime@npm:1.6.0, mime@npm:^1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" bin: @@ -7399,6 +8327,13 @@ __metadata: languageName: node linkType: hard +"minimalistic-assert@npm:^1.0.0": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -7408,7 +8343,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": +"minimatch@npm:^5.1.0": version: 5.1.6 resolution: "minimatch@npm:5.1.6" dependencies: @@ -7417,7 +8352,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.3, minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -7519,7 +8454,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.6": +"mkdirp@npm:^0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -7536,90 +8471,17 @@ __metadata: bin: mkdirp: bin/cmd.js checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf - languageName: node - linkType: hard - -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" - bin: - mkdirp: dist/cjs/src/bin.js - checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d - languageName: node - linkType: hard - -"modules@workspace:.": - version: 0.0.0-use.local - resolution: "modules@workspace:." - dependencies: - "@blueprintjs/core": "npm:^5.10.2" - "@blueprintjs/icons": "npm:^5.9.0" - "@box2d/core": "npm:^0.10.0" - "@box2d/debug-draw": "npm:^0.10.0" - "@commander-js/extra-typings": "npm:^13.0.0" - "@dimforge/rapier3d-compat": "npm:^0.11.2" - "@jscad/modeling": "npm:2.9.6" - "@jscad/regl-renderer": "npm:^2.6.1" - "@jscad/stl-serializer": "npm:2.1.11" - "@stylistic/eslint-plugin": "npm:^4.2.0" - "@types/estree": "npm:^1.0.0" - "@types/jest": "npm:^29.0.0" - "@types/lodash": "npm:^4.14.198" - "@types/node": "npm:^20.12.12" - "@types/plotly.js": "npm:^2.35.4" - "@types/react": "npm:^18.3.2" - "@types/react-dom": "npm:^18.3.0" - "@types/three": "npm:^0.175.0" - "@vitejs/plugin-react": "npm:^4.3.4" - ace-builds: "npm:^1.25.1" - acorn: "npm:^8.8.1" - astring: "npm:^1.8.6" - chalk: "npm:^5.0.1" - classnames: "npm:^2.3.1" - commander: "npm:^13.0.0" - console-table-printer: "npm:^2.11.1" - esbuild: "npm:^0.25.0" - eslint: "npm:^9.21.0" - eslint-plugin-import: "npm:^2.31.0" - eslint-plugin-jest: "npm:^28.11.0" - eslint-plugin-jsx-a11y: "npm:^6.10.2" - eslint-plugin-react: "npm:^7.37.4" - eslint-plugin-react-hooks: "npm:^5.1.0" - gl-matrix: "npm:^3.3.0" - globals: "npm:^15.11.0" - http-server: "npm:^0.13.0" - husky: "npm:^9.0.11" - jest: "npm:^29.7.0" - jest-environment-jsdom: "npm:^29.4.1" - jest-mock: "npm:^29.7.0" - js-slang: "npm:^1.0.81" - lodash: "npm:^4.17.21" - mqtt: "npm:^4.3.7" - nbody: "npm:^0.2.0" - patch-package: "npm:^6.5.1" - phaser: "npm:^3.54.0" - plotly.js-dist: "npm:^2.17.1" - postinstall-postinstall: "npm:^2.1.0" - re-resizable: "npm:^6.9.11" - react: "npm:^18.3.1" - react-ace: "npm:^10.1.0" - react-dom: "npm:^18.3.1" - regl: "npm:^2.1.0" - saar: "npm:^1.0.4" - sass: "npm:^1.85.0" - save-file: "npm:^2.3.1" - source-academy-utils: "npm:^1.0.0" - source-academy-wabt: "npm:^1.0.4" - three: "npm:^0.175.0" - ts-jest: "npm:^29.1.2" - typedoc: "npm:^0.25.12" - typescript: "npm:^5.8.2" - typescript-eslint: "npm:^8.24.1" - uniqid: "npm:^5.4.0" - vite: "npm:^6.3.4" - yarnhook: "npm:^0.6.0" - languageName: unknown - linkType: soft + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard "mqtt-packet@npm:^6.8.0": version: 6.10.0 @@ -7661,6 +8523,13 @@ __metadata: languageName: node linkType: hard +"mrmime@npm:^2.0.0": + version: 2.0.1 + resolution: "mrmime@npm:2.0.1" + checksum: 10c0/af05afd95af202fdd620422f976ad67dc18e6ee29beb03dd1ce950ea6ef664de378e44197246df4c7cdd73d47f2e7143a6e26e473084b9e4aa2095c0ad1e1761 + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -7668,13 +8537,25 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 languageName: node linkType: hard +"multicast-dns@npm:^7.2.5": + version: 7.2.5 + resolution: "multicast-dns@npm:7.2.5" + dependencies: + dns-packet: "npm:^5.2.2" + thunky: "npm:^1.0.2" + bin: + multicast-dns: cli.js + checksum: 10c0/5120171d4bdb1577764c5afa96e413353bff530d1b37081cb29cccc747f989eb1baf40574fe8e27060fc1aef72b59c042f72b9b208413de33bcf411343c69057 + languageName: node + linkType: hard + "nanoid@npm:^3.3.8": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -7725,6 +8606,13 @@ __metadata: languageName: node linkType: hard +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + "negotiator@npm:^1.0.0": version: 1.0.0 resolution: "negotiator@npm:1.0.0" @@ -7732,6 +8620,13 @@ __metadata: languageName: node linkType: hard +"negotiator@npm:~0.6.4": + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -7758,6 +8653,13 @@ __metadata: languageName: node linkType: hard +"node-forge@npm:^1": + version: 1.3.1 + resolution: "node-forge@npm:1.3.1" + checksum: 10c0/e882819b251a4321f9fc1d67c85d1501d3004b4ee889af822fd07f64de3d1a8e272ff00b689570af0465d65d6bf5074df9c76e900e0aff23e60b847f2a46fbe8 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 11.2.0 resolution: "node-gyp@npm:11.2.0" @@ -7785,10 +8687,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.18": - version: 2.0.18 - resolution: "node-releases@npm:2.0.18" - checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27 +"node-releases@npm:^2.0.19": + version: 2.0.19 + resolution: "node-releases@npm:2.0.19" + checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa languageName: node linkType: hard @@ -7812,7 +8714,7 @@ __metadata: languageName: node linkType: hard -"normalize-path@npm:^3.0.0": +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 @@ -7835,7 +8737,7 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^4.0.0, npm-run-path@npm:^4.0.1": +"npm-run-path@npm:^4.0.0": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" dependencies: @@ -7854,14 +8756,7 @@ __metadata: languageName: node linkType: hard -"nwsapi@npm:^2.2.2": - version: 2.2.13 - resolution: "nwsapi@npm:2.2.13" - checksum: 10c0/9dbd1071bba3570ef0b046c43c03d0584c461063f27539ba39f4185188e9d5c10cb06fd4426cdb300bb83020c3daa2c8f4fa9e8a070299539ac4007433357ac0 - languageName: node - linkType: hard - -"object-assign@npm:^4.1.1": +"object-assign@npm:^4, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 @@ -7972,6 +8867,29 @@ __metadata: languageName: node linkType: hard +"obuf@npm:^1.0.0, obuf@npm:^1.1.2": + version: 1.1.2 + resolution: "obuf@npm:1.1.2" + checksum: 10c0/520aaac7ea701618eacf000fc96ae458e20e13b0569845800fc582f81b386731ab22d55354b4915d58171db00e79cfcd09c1638c02f89577ef092b38c65b7d81 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"on-headers@npm:~1.0.2": + version: 1.0.2 + resolution: "on-headers@npm:1.0.2" + checksum: 10c0/f649e65c197bf31505a4c0444875db0258e198292f34b884d73c2f751e91792ef96bb5cf89aa0f4fecc2e4dc662461dda606b1274b0e564f539cae5d2f5fc32f + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -7981,7 +8899,7 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^5.1.0, onetime@npm:^5.1.2": +"onetime@npm:^5.1.0": version: 5.1.2 resolution: "onetime@npm:5.1.2" dependencies: @@ -7990,6 +8908,18 @@ __metadata: languageName: node linkType: hard +"open@npm:^10.0.3": + version: 10.1.2 + resolution: "open@npm:10.1.2" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^3.1.0" + checksum: 10c0/1bee796f06e549ce764f693272100323fbc04da8fa3c5b0402d6c2d11b3d76fa0aac0be7535e710015ff035326638e3b9a563f3b0e7ac3266473ed5663caae6d + languageName: node + linkType: hard + "open@npm:^7.4.2": version: 7.4.2 resolution: "open@npm:7.4.2" @@ -8000,7 +8930,7 @@ __metadata: languageName: node linkType: hard -"opener@npm:^1.5.1": +"opener@npm:^1.5.1, opener@npm:^1.5.2": version: 1.5.2 resolution: "opener@npm:1.5.2" bin: @@ -8064,7 +8994,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -8098,6 +9028,17 @@ __metadata: languageName: node linkType: hard +"p-retry@npm:^6.2.0": + version: 6.2.1 + resolution: "p-retry@npm:6.2.1" + dependencies: + "@types/retry": "npm:0.12.2" + is-network-error: "npm:^1.0.0" + retry: "npm:^0.13.1" + checksum: 10c0/10d014900107da2c7071ad60fffe4951675f09930b7a91681643ea224ae05649c05001d9e78436d902fe8b116d520dd1f60e72e091de097e2640979d56f3fb60 + languageName: node + linkType: hard + "p-try@npm:^2.0.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" @@ -8131,24 +9072,10 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.2.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": "npm:^7.0.0" - error-ex: "npm:^1.3.1" - json-parse-even-better-errors: "npm:^2.3.0" - lines-and-columns: "npm:^1.1.6" - checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 - languageName: node - linkType: hard - -"parse5@npm:^7.0.0, parse5@npm:^7.1.1": - version: 7.2.0 - resolution: "parse5@npm:7.2.0" - dependencies: - entities: "npm:^4.5.0" - checksum: 10c0/76d68684708befb41ff1d5e0e9835f566afb3950807d340941afc9dbe4c9c28db2414bda0c8503d459de863463869b8540c6abf8c9742cffa0b9b31eecd37951 +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 languageName: node linkType: hard @@ -8255,6 +9182,34 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10c0/1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b + languageName: node + linkType: hard + +"path-to-regexp@npm:^8.0.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 + languageName: node + linkType: hard + "phaser@npm:^3.54.0": version: 3.88.2 resolution: "phaser@npm:3.88.2" @@ -8264,14 +9219,14 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be @@ -8285,19 +9240,17 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.1, pirates@npm:^4.0.4": - version: 4.0.6 - resolution: "pirates@npm:4.0.6" - checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 +"pirates@npm:^4.0.1": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a languageName: node linkType: hard -"pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" - dependencies: - find-up: "npm:^4.0.0" - checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 +"pkce-challenge@npm:^5.0.0": + version: 5.0.0 + resolution: "pkce-challenge@npm:5.0.0" + checksum: 10c0/c6706d627fdbb6f22bf8cc5d60d96d6b6a7bb481399b336a3d3f4e9bfba3e167a2c32f8ec0b5e74be686a0ba3bcc9894865d4c2dd1b91cea4c05dba1f28602c3 languageName: node linkType: hard @@ -8309,13 +9262,12 @@ __metadata: linkType: hard "portfinder@npm:^1.0.25": - version: 1.0.32 - resolution: "portfinder@npm:1.0.32" + version: 1.0.37 + resolution: "portfinder@npm:1.0.37" dependencies: - async: "npm:^2.6.4" - debug: "npm:^3.2.7" - mkdirp: "npm:^0.5.6" - checksum: 10c0/cef8b567b78aabccc59fe8e103bac8b394bb45a6a69be626608f099f454124c775aaf47b274c006332c07ab3f501cde55e49aaeb9d49d78d90362d776a565cbf + async: "npm:^3.2.6" + debug: "npm:^4.3.6" + checksum: 10c0/eabd2764ced7bb0e6da7a1382bb77f9531309f7782fb6169021d05eecff0c0a17958bcf87573047a164dd0bb23f294d5d74b08ffe58c47005c28ed92eea9a6a7 languageName: node linkType: hard @@ -8327,9 +9279,9 @@ __metadata: linkType: hard "possible-typed-array-names@npm:^1.0.0": - version: 1.0.0 - resolution: "possible-typed-array-names@npm:1.0.0" - checksum: 10c0/d9aa22d31f4f7680e20269db76791b41c3a32c01a373e25f8a4813b4d45f7456bfc2b6d68f752dc4aab0e0bb0721cb3d76fb678c9101cb7a16316664bc2c73fd + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 languageName: node linkType: hard @@ -8365,17 +9317,6 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": - version: 29.7.0 - resolution: "pretty-format@npm:29.7.0" - dependencies: - "@jest/schemas": "npm:^29.6.3" - ansi-styles: "npm:^5.0.0" - react-is: "npm:^18.0.0" - checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f - languageName: node - linkType: hard - "proc-log@npm:^5.0.0": version: 5.0.0 resolution: "proc-log@npm:5.0.0" @@ -8383,7 +9324,7 @@ __metadata: languageName: node linkType: hard -"process-nextick-args@npm:^2.0.1": +"process-nextick-args@npm:^2.0.1, process-nextick-args@npm:~2.0.0": version: 2.0.1 resolution: "process-nextick-args@npm:2.0.1" checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 @@ -8410,16 +9351,6 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1": - version: 2.4.2 - resolution: "prompts@npm:2.4.2" - dependencies: - kleur: "npm:^3.0.3" - sisteransi: "npm:^1.0.5" - checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 - languageName: node - linkType: hard - "prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" @@ -8431,10 +9362,13 @@ __metadata: languageName: node linkType: hard -"psl@npm:^1.1.33": - version: 1.9.0 - resolution: "psl@npm:1.9.0" - checksum: 10c0/6a3f805fdab9442f44de4ba23880c4eba26b20c8e8e0830eff1cb31007f6825dace61d17203c58bfe36946842140c97a1ba7f67bc63ca2d88a7ee052b65d97ab +"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 languageName: node linkType: hard @@ -8448,21 +9382,21 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.0, punycode@npm:^2.1.1": +"punycode.js@npm:^2.3.1": version: 2.3.1 - resolution: "punycode@npm:2.3.1" - checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + resolution: "punycode.js@npm:2.3.1" + checksum: 10c0/1d12c1c0e06127fa5db56bd7fdf698daf9a78104456a6b67326877afc21feaa821257b171539caedd2f0524027fa38e67b13dd094159c8d70b6d26d2bea4dfdb languageName: node linkType: hard -"pure-rand@npm:^6.0.0": - version: 6.1.0 - resolution: "pure-rand@npm:6.1.0" - checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 languageName: node linkType: hard -"qs@npm:^6.4.0": +"qs@npm:6.13.0": version: 6.13.0 resolution: "qs@npm:6.13.0" dependencies: @@ -8471,10 +9405,12 @@ __metadata: languageName: node linkType: hard -"querystringify@npm:^2.1.1": - version: 2.2.0 - resolution: "querystringify@npm:2.2.0" - checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa +"qs@npm:^6.14.0, qs@npm:^6.4.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c languageName: node linkType: hard @@ -8485,6 +9421,37 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + languageName: node + linkType: hard + +"raw-body@npm:^3.0.0": + version: 3.0.0 + resolution: "raw-body@npm:3.0.0" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.6.3" + unpipe: "npm:1.0.0" + checksum: 10c0/f8daf4b724064a4811d118745a781ca0fb4676298b8adadfd6591155549cfea0a067523cf7dd3baeb1265fecc9ce5dfb2fc788c12c66b85202a336593ece0f87 + languageName: node + linkType: hard + "re-resizable@npm:^6.9.11": version: 6.11.2 resolution: "re-resizable@npm:6.11.2" @@ -8548,13 +9515,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - "react-popper@npm:^2.3.0": version: 2.3.0 resolution: "react-popper@npm:2.3.0" @@ -8604,17 +9564,30 @@ __metadata: linkType: hard "react-uid@npm:^2.3.3": - version: 2.3.3 - resolution: "react-uid@npm:2.3.3" + version: 2.4.0 + resolution: "react-uid@npm:2.4.0" dependencies: tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/8aa30e92ee03561d25779c68ba6d9409ba4ea83386a8798729b6eb5977bbdc4a5ff199b758d2a29e958d7560b2a7517cab8671932b83e8fcd50092631c209b4c + checksum: 10c0/5e01e8d3a4144d160af26f7ea8300887ed8379ee14b1fad7979ea755d5bfa4badfdab531cb7d4495488e94083f829bd5b28a8f6661522b11729fceb42dcf73ea + languageName: node + linkType: hard + +"react-use-measure@npm:^2.1.7": + version: 2.1.7 + resolution: "react-use-measure@npm:2.1.7" + peerDependencies: + react: ">=16.13" + react-dom: ">=16.13" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/ff24130e6f95e853feb6892fb74af08dbc5aae3574b701169e3bc3adb392c3162f51a58ddfe39bb7337db13ae609bbec0bb51a9de8b5fae5420f9d17e1f8b542 languageName: node linkType: hard @@ -8627,7 +9600,22 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:^2.0.1": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -8639,9 +9627,27 @@ __metadata: linkType: hard "readdirp@npm:^4.0.1": - version: 4.0.2 - resolution: "readdirp@npm:4.0.2" - checksum: 10c0/a16ecd8ef3286dcd90648c3b103e3826db2b766cdb4a988752c43a83f683d01c7059158d623cbcd8bdfb39e65d302d285be2d208e7d9f34d022d912b929217dd + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"rechoir@npm:^0.8.0": + version: 0.8.0 + resolution: "rechoir@npm:0.8.0" + dependencies: + resolve: "npm:^1.20.0" + checksum: 10c0/1a30074124a22abbd5d44d802dac26407fa72a0a95f162aa5504ba8246bc5452f8b1a027b154d9bdbabcd8764920ff9333d934c46a8f17479c8912e92332f3ff languageName: node linkType: hard @@ -8668,13 +9674,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 - languageName: node - linkType: hard - "regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": version: 1.0.2 resolution: "regex-not@npm:1.0.2" @@ -8706,13 +9705,6 @@ __metadata: languageName: node linkType: hard -"regl@npm:^2.1.0": - version: 2.1.1 - resolution: "regl@npm:2.1.1" - checksum: 10c0/68aa1c6f94cb33da612515b0e69833af67dce422e5b8d891c6d8f4c055aac1afd44e2b94dcfe90bb79fde1c5cd48501db292f5268365140765256ea3518546e1 - languageName: node - linkType: hard - "reinterval@npm:^1.1.0": version: 1.1.0 resolution: "reinterval@npm:1.1.0" @@ -8762,15 +9754,6 @@ __metadata: languageName: node linkType: hard -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -8792,23 +9775,16 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:^2.0.0": - version: 2.0.2 - resolution: "resolve.exports@npm:2.0.2" - checksum: 10c0/cc4cffdc25447cf34730f388dca5021156ba9302a3bad3d7f168e790dc74b2827dff603f1bc6ad3d299bac269828dca96dd77e036dc9fba6a2a1807c47ab5c98 - languageName: node - linkType: hard - "resolve@npm:^1.20.0, resolve@npm:^1.22.4": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" + version: 1.22.10 + resolution: "resolve@npm:1.22.10" dependencies: - is-core-module: "npm:^2.13.0" + is-core-module: "npm:^2.16.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 languageName: node linkType: hard @@ -8826,15 +9802,15 @@ __metadata: linkType: hard "resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: - is-core-module: "npm:^2.13.0" + is-core-module: "npm:^2.16.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 languageName: node linkType: hard @@ -8865,10 +9841,17 @@ __metadata: languageName: node linkType: hard +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + "reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa languageName: node linkType: hard @@ -8891,29 +9874,29 @@ __metadata: linkType: hard "rollup@npm:^4.34.9": - version: 4.40.0 - resolution: "rollup@npm:4.40.0" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.40.0" - "@rollup/rollup-android-arm64": "npm:4.40.0" - "@rollup/rollup-darwin-arm64": "npm:4.40.0" - "@rollup/rollup-darwin-x64": "npm:4.40.0" - "@rollup/rollup-freebsd-arm64": "npm:4.40.0" - "@rollup/rollup-freebsd-x64": "npm:4.40.0" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.40.0" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.40.0" - "@rollup/rollup-linux-arm64-gnu": "npm:4.40.0" - "@rollup/rollup-linux-arm64-musl": "npm:4.40.0" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.40.0" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.40.0" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.40.0" - "@rollup/rollup-linux-riscv64-musl": "npm:4.40.0" - "@rollup/rollup-linux-s390x-gnu": "npm:4.40.0" - "@rollup/rollup-linux-x64-gnu": "npm:4.40.0" - "@rollup/rollup-linux-x64-musl": "npm:4.40.0" - "@rollup/rollup-win32-arm64-msvc": "npm:4.40.0" - "@rollup/rollup-win32-ia32-msvc": "npm:4.40.0" - "@rollup/rollup-win32-x64-msvc": "npm:4.40.0" + version: 4.40.2 + resolution: "rollup@npm:4.40.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.40.2" + "@rollup/rollup-android-arm64": "npm:4.40.2" + "@rollup/rollup-darwin-arm64": "npm:4.40.2" + "@rollup/rollup-darwin-x64": "npm:4.40.2" + "@rollup/rollup-freebsd-arm64": "npm:4.40.2" + "@rollup/rollup-freebsd-x64": "npm:4.40.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.40.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.40.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.40.2" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.40.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.40.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.40.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-x64-musl": "npm:4.40.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.40.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.40.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.40.2" "@types/estree": "npm:1.0.7" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -8961,7 +9944,20 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/90aa57487d4a9a7de1a47bf42a6091f83f1cb7fe1814650dfec278ab8ddae5736b86535d4c766493517720f334dfd4aa0635405ca8f4f36ed8d3c0f875f2a801 + checksum: 10c0/cbe9b766891da74fbf7c3b50420bb75102e5c59afc0ea45751f7e43a581d2cd93367763f521f820b72e341cf1f6b9951fbdcd3be67a1b0aa774b754525a8b9c7 + languageName: node + linkType: hard + +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" + dependencies: + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10c0/3279de7450c8eae2f6e095e9edacbdeec0abb5cb7249c6e719faa0db2dba43574b4fff5892d9220631c9abaff52dd3cad648cfea2aaace845e1a071915ac8867 languageName: node linkType: hard @@ -8972,6 +9968,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.0.0 + resolution: "run-applescript@npm:7.0.0" + checksum: 10c0/bd821bbf154b8e6c8ecffeaf0c33cebbb78eb2987476c3f6b420d67ab4c5301faa905dec99ded76ebb3a7042b4e440189ae6d85bbbd3fc6e8d493347ecda8bfe + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -9011,13 +10014,20 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 languageName: node linkType: hard +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + "safe-push-apply@npm:^1.0.0": version: 1.0.0 resolution: "safe-push-apply@npm:1.0.0" @@ -9048,7 +10058,7 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3.0.0": +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 @@ -9075,8 +10085,8 @@ __metadata: linkType: hard "sass@npm:^1.85.0": - version: 1.86.3 - resolution: "sass@npm:1.86.3" + version: 1.88.0 + resolution: "sass@npm:1.88.0" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -9087,7 +10097,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/ba819a0828f732adf7a94cd8ca017bce92bc299ffb878836ed1da80a30612bfbbf56a5e42d6dff3ad80d919c2025afb42948fc7b54a7bc61a9a2d58e1e0c558a + checksum: 10c0/dcb16dc29116bfa5a90485d24fd8020d2b0d95155bd2e31285901588729343b59fefe44365c5f146b2ba5a9ebadef90b23a7220b902507bdbd91ca2ba0a0b688 languageName: node linkType: hard @@ -9105,15 +10115,6 @@ __metadata: languageName: node linkType: hard -"saxes@npm:^6.0.0": - version: 6.0.0 - resolution: "saxes@npm:6.0.0" - dependencies: - xmlchars: "npm:^2.2.0" - checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74 - languageName: node - linkType: hard - "scheduler@npm:^0.21.0": version: 0.21.0 resolution: "scheduler@npm:0.21.0" @@ -9132,6 +10133,18 @@ __metadata: languageName: node linkType: hard +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0": + version: 4.3.2 + resolution: "schema-utils@npm:4.3.2" + dependencies: + "@types/json-schema": "npm:^7.0.9" + ajv: "npm:^8.9.0" + ajv-formats: "npm:^2.1.1" + ajv-keywords: "npm:^5.1.0" + checksum: 10c0/981632f9bf59f35b15a9bcdac671dd183f4946fe4b055ae71a301e66a9797b95e5dd450de581eb6cca56fb6583ce8f24d67b2d9f8e1b2936612209697f6c277e + languageName: node + linkType: hard + "secure-compare@npm:3.0.1": version: 3.0.1 resolution: "secure-compare@npm:3.0.1" @@ -9139,6 +10152,23 @@ __metadata: languageName: node linkType: hard +"select-hose@npm:^2.0.0": + version: 2.0.0 + resolution: "select-hose@npm:2.0.0" + checksum: 10c0/01cc52edd29feddaf379efb4328aededa633f0ac43c64b11a8abd075ff34f05b0d280882c4fbcbdf1a0658202c9cd2ea8d5985174dcf9a2dac7e3a4996fa9b67 + languageName: node + linkType: hard + +"selfsigned@npm:^2.4.1": + version: 2.4.1 + resolution: "selfsigned@npm:2.4.1" + dependencies: + "@types/node-forge": "npm:^1.3.0" + node-forge: "npm:^1" + checksum: 10c0/521829ec36ea042f7e9963bf1da2ed040a815cf774422544b112ec53b7edc0bc50a0f8cc2ae7aa6cc19afa967c641fd96a15de0fc650c68651e41277d2e1df09 + languageName: node + linkType: hard + "semver@npm:^5.5.0, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -9157,12 +10187,52 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.7.1": - version: 7.7.1 - resolution: "semver@npm:7.7.1" +"semver@npm:^7.3.5, semver@npm:^7.6.0": + version: 7.7.2 + resolution: "semver@npm:7.7.2" bin: semver: bin/semver.js - checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958 + checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + languageName: node + linkType: hard + +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.0 + resolution: "send@npm:1.2.0" + dependencies: + debug: "npm:^4.3.5" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + mime-types: "npm:^3.0.1" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.1" + checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 languageName: node linkType: hard @@ -9177,6 +10247,45 @@ __metadata: languageName: node linkType: hard +"serve-index@npm:^1.9.1": + version: 1.9.1 + resolution: "serve-index@npm:1.9.1" + dependencies: + accepts: "npm:~1.3.4" + batch: "npm:0.6.1" + debug: "npm:2.6.9" + escape-html: "npm:~1.0.3" + http-errors: "npm:~1.6.2" + mime-types: "npm:~2.1.17" + parseurl: "npm:~1.3.2" + checksum: 10c0/a666471a24196f74371edf2c3c7bcdd82adbac52f600804508754b5296c3567588bf694258b19e0cb23a567acfa20d9721bfdaed3286007b81f9741ada8a3a9c + languageName: node + linkType: hard + +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.19.0" + checksum: 10c0/528fff6f5e12d0c5a391229ad893910709bc51b5705962b09404a1d813857578149b8815f35d3ee5752f44cd378d0f31669d4b1d7e2d11f41e08283d5134bd1f + languageName: node + linkType: hard + +"serve-static@npm:^2.2.0": + version: 2.2.0 + resolution: "serve-static@npm:2.2.0" + dependencies: + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 + languageName: node + linkType: hard + "set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -9226,6 +10335,20 @@ __metadata: languageName: node linkType: hard +"setprototypeof@npm:1.1.0": + version: 1.1.0 + resolution: "setprototypeof@npm:1.1.0" + checksum: 10c0/a77b20876689c6a89c3b42f0c3596a9cae02f90fc902570cbd97198e9e8240382086c9303ad043e88cee10f61eae19f1004e51d885395a1e9bf49f9ebed12872 + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" @@ -9258,15 +10381,10 @@ __metadata: languageName: node linkType: hard -"shiki@npm:^0.14.7": - version: 0.14.7 - resolution: "shiki@npm:0.14.7" - dependencies: - ansi-sequence-parser: "npm:^1.1.0" - jsonc-parser: "npm:^3.2.0" - vscode-oniguruma: "npm:^1.7.0" - vscode-textmate: "npm:^8.0.0" - checksum: 10c0/5c7fcbb870d0facccc7ae2f3410a28121f8e0b3f298e4e956de817ad6ab60a4c7e20a9184edfe50a93447addbb88b95b69e6ef88ac16ac6ca3e94c50771a6459 +"shell-quote@npm:^1.8.1": + version: 1.8.2 + resolution: "shell-quote@npm:1.8.2" + checksum: 10c0/85fdd44f2ad76e723d34eb72c753f04d847ab64e9f1f10677e3f518d0e5b0752a176fd805297b30bb8c3a1556ebe6e77d2288dbd7b7b0110c7e941e9e9c20ce1 languageName: node linkType: hard @@ -9318,7 +10436,14 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -9346,10 +10471,14 @@ __metadata: languageName: node linkType: hard -"sisteransi@npm:^1.0.5": - version: 1.0.5 - resolution: "sisteransi@npm:1.0.5" - checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 +"sirv@npm:^2.0.3": + version: 2.0.4 + resolution: "sirv@npm:2.0.4" + dependencies: + "@polka/url": "npm:^1.0.0-next.24" + mrmime: "npm:^2.0.0" + totalist: "npm:^3.0.0" + checksum: 10c0/68f8ee857f6a9415e9c07a1f31c7c561df8d5f1b1ba79bee3de583fa37da8718def5309f6b1c6e2c3ef77de45d74f5e49efc7959214443aa92d42e9c99180a4e languageName: node linkType: hard @@ -9420,6 +10549,17 @@ __metadata: languageName: node linkType: hard +"sockjs@npm:^0.3.24": + version: 0.3.24 + resolution: "sockjs@npm:0.3.24" + dependencies: + faye-websocket: "npm:^0.11.3" + uuid: "npm:^8.3.2" + websocket-driver: "npm:^0.7.4" + checksum: 10c0/aa102c7d921bf430215754511c81ea7248f2dcdf268fbdb18e4d8183493a86b8793b164c636c52f474a886f747447c962741df2373888823271efdb9d2594f33 + languageName: node + linkType: hard + "socks-proxy-agent@npm:^8.0.3": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" @@ -9475,19 +10615,9 @@ __metadata: atob: "npm:^2.1.2" decode-uri-component: "npm:^0.2.0" resolve-url: "npm:^0.2.1" - source-map-url: "npm:^0.4.0" - urix: "npm:^0.1.0" - checksum: 10c0/410acbe93882e058858d4c1297be61da3e1533f95f25b95903edddc1fb719654e705663644677542d1fb78a66390238fad1a57115fc958a0724cf9bb509caf57 - languageName: node - linkType: hard - -"source-map-support@npm:0.5.13": - version: 0.5.13 - resolution: "source-map-support@npm:0.5.13" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + source-map-url: "npm:^0.4.0" + urix: "npm:^0.1.0" + checksum: 10c0/410acbe93882e058858d4c1297be61da3e1533f95f25b95903edddc1fb719654e705663644677542d1fb78a66390238fad1a57115fc958a0724cf9bb509caf57 languageName: node linkType: hard @@ -9512,13 +10642,40 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": +"source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 languageName: node linkType: hard +"spdy-transport@npm:^3.0.0": + version: 3.0.0 + resolution: "spdy-transport@npm:3.0.0" + dependencies: + debug: "npm:^4.1.0" + detect-node: "npm:^2.0.4" + hpack.js: "npm:^2.1.6" + obuf: "npm:^1.1.2" + readable-stream: "npm:^3.0.6" + wbuf: "npm:^1.7.3" + checksum: 10c0/eaf7440fa90724fffc813c386d4a8a7427d967d6e46d7c51d8f8a533d1a6911b9823ea9218703debbae755337e85f110185d7a00ae22ec5c847077b908ce71bb + languageName: node + linkType: hard + +"spdy@npm:^4.0.2": + version: 4.0.2 + resolution: "spdy@npm:4.0.2" + dependencies: + debug: "npm:^4.1.0" + handle-thing: "npm:^2.0.0" + http-deceiver: "npm:^1.2.7" + select-hose: "npm:^2.0.0" + spdy-transport: "npm:^3.0.0" + checksum: 10c0/983509c0be9d06fd00bb9dff713c5b5d35d3ffd720db869acdd5ad7aa6fc0e02c2318b58f75328957d8ff772acdf1f7d19382b6047df342044ff3e2d6805ccdf + languageName: node + linkType: hard + "split-string@npm:^3.0.1, split-string@npm:^3.0.2": version: 3.1.0 resolution: "split-string@npm:3.1.0" @@ -9567,12 +10724,10 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.3": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 languageName: node linkType: hard @@ -9586,12 +10741,16 @@ __metadata: languageName: node linkType: hard -"stats-gl@npm:^2.0.0": - version: 2.2.8 - resolution: "stats-gl@npm:2.2.8" +"stats-gl@npm:^2.2.8": + version: 2.4.2 + resolution: "stats-gl@npm:2.4.2" dependencies: - "@types/three": "npm:^0.163.0" - checksum: 10c0/b82d4d51098d6775272c295d67575be5df6b81d2f53fb660773e271be24998dbc771469035b62406a5d85f9b8631da4cdde32fc658fe11c4697c583b5eed242a + "@types/three": "npm:*" + three: "npm:^0.170.0" + peerDependencies: + "@types/three": "*" + three: "*" + checksum: 10c0/cff079103d4004cdfd72d65c977b7237a929b02f57b275081188e9313d74620b70ea0a57e0561fb6a43b1597830068b4adb2625dbc08b99799fd12a71ec54729 languageName: node linkType: hard @@ -9602,6 +10761,27 @@ __metadata: languageName: node linkType: hard +"statuses@npm:2.0.1, statuses@npm:^2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"statuses@npm:>= 1.4.0 < 2": + version: 1.5.0 + resolution: "statuses@npm:1.5.0" + checksum: 10c0/e433900956357b3efd79b1c547da4d291799ac836960c016d10a98f6a810b1b5c0dcc13b5a7aa609a58239b5190e1ea176ad9221c2157d2fd1c747393e6b2940 + languageName: node + linkType: hard + +"std-env@npm:^3.9.0": + version: 3.9.0 + resolution: "std-env@npm:3.9.0" + checksum: 10c0/4a6f9218aef3f41046c3c7ecf1f98df00b30a07f4f35c6d47b28329bc2531eef820828951c7d7b39a1c5eb19ad8a46e3ddfc7deb28f0a2f3ceebee11bab7ba50 + languageName: node + linkType: hard + "stream-shift@npm:^1.0.2": version: 1.0.3 resolution: "stream-shift@npm:1.0.3" @@ -9609,16 +10789,6 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.1": - version: 4.0.2 - resolution: "string-length@npm:4.0.2" - dependencies: - char-regex: "npm:^1.0.2" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c - languageName: node - linkType: hard - "string-to-arraybuffer@npm:^1.0.0": version: 1.0.2 resolution: "string-to-arraybuffer@npm:1.0.2" @@ -9740,6 +10910,15 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -9765,13 +10944,6 @@ __metadata: languageName: node linkType: hard -"strip-bom@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-bom@npm:4.0.0" - checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef - languageName: node - linkType: hard - "strip-eof@npm:^1.0.0": version: 1.0.0 resolution: "strip-eof@npm:1.0.0" @@ -9802,15 +10974,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -9827,13 +10990,6 @@ __metadata: languageName: node linkType: hard -"symbol-tree@npm:^3.2.4": - version: 3.2.4 - resolution: "symbol-tree@npm:3.2.4" - checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509 - languageName: node - linkType: hard - "tar@npm:^7.4.3": version: 7.4.3 resolution: "tar@npm:7.4.3" @@ -9859,6 +11015,15 @@ __metadata: languageName: node linkType: hard +"thingies@npm:^1.20.0": + version: 1.21.0 + resolution: "thingies@npm:1.21.0" + peerDependencies: + tslib: ^2 + checksum: 10c0/7570ee855aecb73185a672ecf3eb1c287a6512bf5476449388433b2d4debcf78100bc8bfd439b0edd38d2bc3bfb8341de5ce85b8557dec66d0f27b962c9a8bc1 + languageName: node + linkType: hard + "three-mesh-bvh@npm:^0.7.8": version: 0.7.8 resolution: "three-mesh-bvh@npm:0.7.8" @@ -9868,9 +11033,9 @@ __metadata: languageName: node linkType: hard -"three-stdlib@npm:^2.21.1, three-stdlib@npm:^2.29.9": - version: 2.33.0 - resolution: "three-stdlib@npm:2.33.0" +"three-stdlib@npm:^2.21.1, three-stdlib@npm:^2.35.6": + version: 2.36.0 + resolution: "three-stdlib@npm:2.36.0" dependencies: "@types/draco3d": "npm:^1.4.0" "@types/offscreencanvas": "npm:^2019.6.4" @@ -9880,7 +11045,7 @@ __metadata: potpack: "npm:^1.0.1" peerDependencies: three: ">=0.128.0" - checksum: 10c0/ba35f0d035877f189bcfe8228d0a731497ba620e28108ad61720e2cbbe5fc2826eeca3566cfe5fbb300456d74ddc81adf12496c14e53a744282d28ea27fc40dd + checksum: 10c0/cbfd85fa817d0319b051de00dfa968bae7a2c83d96c74706fbae1b0a1b01b7dff2a8ef17d6774063ff7b5a6b92bfea4cd66135fc53019cdbd05f18c4adf596d0 languageName: node linkType: hard @@ -9891,6 +11056,13 @@ __metadata: languageName: node linkType: hard +"three@npm:^0.170.0": + version: 0.170.0 + resolution: "three@npm:0.170.0" + checksum: 10c0/eeaa1eccb31467654a044fb90634b3b6bb8d0768a4f029c082fb6aa22aec0d6362d0e3af74938caa1db512b7823a031b54a76e88fba6cfd535054165ea9667c9 + languageName: node + linkType: hard + "three@npm:^0.175.0": version: 0.175.0 resolution: "three@npm:0.175.0" @@ -9898,17 +11070,28 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.12": - version: 0.2.12 - resolution: "tinyglobby@npm:0.2.12" - dependencies: - fdir: "npm:^6.4.3" - picomatch: "npm:^4.0.2" - checksum: 10c0/7c9be4fd3625630e262dcb19015302aad3b4ba7fc620f269313e688f2161ea8724d6cb4444baab5ef2826eb6bed72647b169a33ec8eea37501832a2526ff540f +"thunky@npm:^1.0.2": + version: 1.1.0 + resolution: "thunky@npm:1.1.0" + checksum: 10c0/369764f39de1ce1de2ba2fa922db4a3f92e9c7f33bcc9a713241bc1f4a5238b484c17e0d36d1d533c625efb00e9e82c3e45f80b47586945557b45abb890156d2 languageName: node linkType: hard -"tinyglobby@npm:^0.2.13": +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.2": + version: 0.3.2 + resolution: "tinyexec@npm:0.3.2" + checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13": version: 0.2.13 resolution: "tinyglobby@npm:0.2.13" dependencies: @@ -9918,6 +11101,27 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^1.0.2": + version: 1.0.2 + resolution: "tinypool@npm:1.0.2" + checksum: 10c0/31ac184c0ff1cf9a074741254fe9ea6de95026749eb2b8ec6fd2b9d8ca94abdccda731f8e102e7f32e72ed3b36d32c6975fd5f5523df3f1b6de6c3d8dfd95e63 + languageName: node + linkType: hard + +"tinyrainbow@npm:^2.0.0": + version: 2.0.0 + resolution: "tinyrainbow@npm:2.0.0" + checksum: 10c0/c83c52bef4e0ae7fb8ec6a722f70b5b6fa8d8be1c85792e829f56c0e1be94ab70b293c032dc5048d4d37cfe678f1f5babb04bdc65fd123098800148ca989184f + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -9985,101 +11189,65 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.1.2": - version: 4.1.4 - resolution: "tough-cookie@npm:4.1.4" - dependencies: - psl: "npm:^1.1.33" - punycode: "npm:^2.1.1" - universalify: "npm:^0.2.0" - url-parse: "npm:^1.5.3" - checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45 +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 languageName: node linkType: hard -"tr46@npm:^3.0.0": - version: 3.0.0 - resolution: "tr46@npm:3.0.0" - dependencies: - punycode: "npm:^2.1.1" - checksum: 10c0/cdc47cad3a9d0b6cb293e39ccb1066695ae6fdd39b9e4f351b010835a1f8b4f3a6dc3a55e896b421371187f22b48d7dac1b693de4f6551bdef7b6ab6735dfe3b +"totalist@npm:^3.0.0": + version: 3.0.1 + resolution: "totalist@npm:3.0.1" + checksum: 10c0/4bb1fadb69c3edbef91c73ebef9d25b33bbf69afe1e37ce544d5f7d13854cda15e47132f3e0dc4cafe300ddb8578c77c50a65004d8b6e97e77934a69aa924863 + languageName: node + linkType: hard + +"tree-dump@npm:^1.0.1": + version: 1.0.2 + resolution: "tree-dump@npm:1.0.2" + peerDependencies: + tslib: 2 + checksum: 10c0/d1d180764e9c691b28332dbd74226c6b6af361dfb1e134bb11e60e17cb11c215894adee50ffc578da5dcf546006693947be8b6665eb1269b56e2f534926f1c1f languageName: node linkType: hard -"troika-three-text@npm:^0.49.0": - version: 0.49.1 - resolution: "troika-three-text@npm:0.49.1" +"troika-three-text@npm:^0.52.0": + version: 0.52.4 + resolution: "troika-three-text@npm:0.52.4" dependencies: bidi-js: "npm:^1.0.2" - troika-three-utils: "npm:^0.49.0" - troika-worker-utils: "npm:^0.49.0" + troika-three-utils: "npm:^0.52.4" + troika-worker-utils: "npm:^0.52.0" webgl-sdf-generator: "npm:1.1.1" peerDependencies: three: ">=0.125.0" - checksum: 10c0/a356c9cc1e637ddbe92b41b1dc3bcceee7f4adcaacce88039044b4b33a6c3f3a146c3e3b8085c0dfb3a3cec94074fd96a2be548bfd73e7f40ad67a747089bdc1 + checksum: 10c0/61475643728472df1e4169ff3167c5ae740bcf4b13d2680b2a1046613b5b65e6e573fc2afcd27e517f163e6885a7dd1287307c476139a6b98a8256d26fcd50e1 languageName: node linkType: hard -"troika-three-utils@npm:^0.49.0": - version: 0.49.0 - resolution: "troika-three-utils@npm:0.49.0" +"troika-three-utils@npm:^0.52.4": + version: 0.52.4 + resolution: "troika-three-utils@npm:0.52.4" peerDependencies: three: ">=0.125.0" - checksum: 10c0/6cf14e6e49424e587538972810241d0acf4b005e5d9bbb3d5555bcb2b8b406c3fb0752ada68b455a18a6c29b42b6aa10c6cb333346343bffc73b5e198d5cb9f7 + checksum: 10c0/06de01ed332b755e5a1f376ea047f5e3585c71c98e2216822011fcf25e107fa9e28cbdd6586d3507eec45973998de80db0a2f07efba550b62623ceb58c955c98 languageName: node linkType: hard -"troika-worker-utils@npm:^0.49.0": - version: 0.49.0 - resolution: "troika-worker-utils@npm:0.49.0" - checksum: 10c0/e360f33fd5cb2adf50fbd91e85de15df518725370f667321043bb26fefd06b2ab26f8a894854aeb88f4f732faebf0f9cec9ece011dde1fe5bf2780d748ee0904 +"troika-worker-utils@npm:^0.52.0": + version: 0.52.0 + resolution: "troika-worker-utils@npm:0.52.0" + checksum: 10c0/bcd776324ce3e941c1a913531cee6022c867b71bc2d6bb67f1eadf6df96fd4f865c51b90ac58fc5c0ae3346a4bd8360b0a52398864800a7a9c60473151d452d5 languageName: node linkType: hard -"ts-api-utils@npm:^2.0.1": - version: 2.0.1 - resolution: "ts-api-utils@npm:2.0.1" +"ts-api-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "ts-api-utils@npm:2.1.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/23fd56a958b332cac00150a652e4c84730df30571bd2faa1ba6d7b511356d1a61656621492bb6c7f15dd6e18847a1408357a0e406671d358115369a17f5bfedd - languageName: node - linkType: hard - -"ts-jest@npm:^29.1.2": - version: 29.3.2 - resolution: "ts-jest@npm:29.3.2" - dependencies: - bs-logger: "npm:^0.2.6" - ejs: "npm:^3.1.10" - fast-json-stable-stringify: "npm:^2.1.0" - jest-util: "npm:^29.0.0" - json5: "npm:^2.2.3" - lodash.memoize: "npm:^4.1.2" - make-error: "npm:^1.3.6" - semver: "npm:^7.7.1" - type-fest: "npm:^4.39.1" - yargs-parser: "npm:^21.1.1" - peerDependencies: - "@babel/core": ">=7.0.0-beta.0 <8" - "@jest/transform": ^29.0.0 - "@jest/types": ^29.0.0 - babel-jest: ^29.0.0 - jest: ^29.0.0 - typescript: ">=4.3 <6" - peerDependenciesMeta: - "@babel/core": - optional: true - "@jest/transform": - optional: true - "@jest/types": - optional: true - babel-jest: - optional: true - esbuild: - optional: true - bin: - ts-jest: cli.js - checksum: 10c0/84762720dbef45c1644348d67d0dcb8b7ad6369a16628c4752aceeb47f0ccdad63ae14485048b641c20ce096337a160ab816881361ef5517325bac6a5b3756e0 + checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f languageName: node linkType: hard @@ -10095,10 +11263,10 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.3": - version: 2.8.0 - resolution: "tslib@npm:2.8.0" - checksum: 10c0/31e4d14dc1355e9b89e4d3c893a18abb7f90b6886b089c2da91224d0a7752c79f3ddc41bc1aa0a588ac895bd97bb99c5bc2bfdb2f86de849f31caeb3ba79bbe5 +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.4.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 languageName: node linkType: hard @@ -10127,24 +11295,24 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": - version: 4.0.8 - resolution: "type-detect@npm:4.0.8" - checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd - languageName: node - linkType: hard - -"type-fest@npm:^0.21.3": - version: 0.21.3 - resolution: "type-fest@npm:0.21.3" - checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 +"type-is@npm:^2.0.0, type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" + dependencies: + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10c0/7f7ec0a060b16880bdad36824ab37c26019454b67d73e8a465ed5a3587440fbe158bc765f0da68344498235c877e7dbbb1600beccc94628ed05599d667951b99 languageName: node linkType: hard -"type-fest@npm:^4.39.1": - version: 4.39.1 - resolution: "type-fest@npm:4.39.1" - checksum: 10c0/f5bf302eb2e2f70658be1757aa578f4a09da3f65699b0b12b7ae5502ccea76e5124521a6e6b69540f442c3dc924c394202a2ab58718d0582725c7ac23c072594 +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d languageName: node linkType: hard @@ -10217,33 +11385,34 @@ __metadata: languageName: node linkType: hard -"typedoc@npm:^0.25.12": - version: 0.25.13 - resolution: "typedoc@npm:0.25.13" +"typedoc@npm:^0.28.4": + version: 0.28.4 + resolution: "typedoc@npm:0.28.4" dependencies: + "@gerrit0/mini-shiki": "npm:^3.2.2" lunr: "npm:^2.3.9" - marked: "npm:^4.3.0" - minimatch: "npm:^9.0.3" - shiki: "npm:^0.14.7" + markdown-it: "npm:^14.1.0" + minimatch: "npm:^9.0.5" + yaml: "npm:^2.7.1" peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x bin: typedoc: bin/typedoc - checksum: 10c0/13878e6a9fc2b65d65e3b514efa11b43bdfd57149861cefc4a969ec213f4bc4b36ee9239d0b654ae18bcbbd5174206d409383f9000b7bdea22da1945f7ac91de + checksum: 10c0/5c7f4019da81e8b0869e4757b3d74c001dc021be381c5716a14212fbf63ad81bcfc470e040b7eac132603447c367019d5acab323d1b358b040979f1f56fe6393 languageName: node linkType: hard "typescript-eslint@npm:^8.24.1": - version: 8.30.1 - resolution: "typescript-eslint@npm:8.30.1" + version: 8.32.1 + resolution: "typescript-eslint@npm:8.32.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.30.1" - "@typescript-eslint/parser": "npm:8.30.1" - "@typescript-eslint/utils": "npm:8.30.1" + "@typescript-eslint/eslint-plugin": "npm:8.32.1" + "@typescript-eslint/parser": "npm:8.32.1" + "@typescript-eslint/utils": "npm:8.32.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/41c53910308fa03d2216ccae9885e82422b8abc96b384a6e47277b5b351f462e6da3a4dfbb8c9bc7defa8c96fb71c4371fa5759eaa86c7c1b3b53a4a9994e6ab + checksum: 10c0/15602916b582b86c8b4371e99d5721c92af7ae56f9b49cd7971d2a49f11bf0bd64dd8d2c0e2b3ca87b2f3a6fd14966738121f3f8299de50c6109b9f245397f3b languageName: node linkType: hard @@ -10287,6 +11456,13 @@ __metadata: languageName: node linkType: hard +"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": + version: 2.1.0 + resolution: "uc.micro@npm:2.1.0" + checksum: 10c0/8862eddb412dda76f15db8ad1c640ccc2f47cdf8252a4a30be908d535602c8d33f9855dfcccb8b8837855c1ce1eaa563f7fa7ebe3c98fd0794351aab9b9c55fa + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -10359,13 +11535,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^0.2.0": - version: 0.2.0 - resolution: "universalify@npm:0.2.0" - checksum: 10c0/cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe - languageName: node - linkType: hard - "universalify@npm:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1" @@ -10373,6 +11542,13 @@ __metadata: languageName: node linkType: hard +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + "unset-value@npm:^1.0.0": version: 1.0.0 resolution: "unset-value@npm:1.0.0" @@ -10383,17 +11559,17 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.1.1": - version: 1.1.1 - resolution: "update-browserslist-db@npm:1.1.1" +"update-browserslist-db@npm:^1.1.3": + version: 1.1.3 + resolution: "update-browserslist-db@npm:1.1.3" dependencies: escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.0" + picocolors: "npm:^1.1.1" peerDependencies: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/536a2979adda2b4be81b07e311bd2f3ad5e978690987956bc5f514130ad50cac87cd22c710b686d79731e00fbee8ef43efe5fcd72baa241045209195d43dcc80 + checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 languageName: node linkType: hard @@ -10438,22 +11614,12 @@ __metadata: languageName: node linkType: hard -"url-parse@npm:^1.5.3": - version: 1.5.10 - resolution: "url-parse@npm:1.5.10" - dependencies: - querystringify: "npm:^2.1.1" - requires-port: "npm:^1.0.0" - checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 - languageName: node - linkType: hard - -"use-sync-external-store@npm:1.2.2, use-sync-external-store@npm:^1.2.0": - version: 1.2.2 - resolution: "use-sync-external-store@npm:1.2.2" +"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.2.2": + version: 1.5.0 + resolution: "use-sync-external-store@npm:1.5.0" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/23b1597c10adf15b26ade9e8c318d8cc0abc9ec0ab5fc7ca7338da92e89c2536abd150a5891bf076836c352fdfa104fc7231fb48f806fd9960e0cbe03601abaf + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/1b8663515c0be34fa653feb724fdcce3984037c78dd4a18f68b2c8be55cc1a1084c578d5b75f158d41b5ddffc2bf5600766d1af3c19c8e329bb20af2ec6f52f4 languageName: node linkType: hard @@ -10464,43 +11630,61 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1": +"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 languageName: node linkType: hard -"utility-types@npm:^3.10.0": +"utility-types@npm:^3.11.0": version: 3.11.0 resolution: "utility-types@npm:3.11.0" checksum: 10c0/2f1580137b0c3e6cf5405f37aaa8f5249961a76d26f1ca8efc0ff49a2fc0e0b2db56de8e521a174d075758e0c7eb3e590edec0832eb44478b958f09914920f19 languageName: node linkType: hard -"uuid@npm:^9.0.1": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" bin: uuid: dist/bin/uuid - checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 languageName: node linkType: hard -"v8-to-istanbul@npm:^9.0.1": - version: 9.3.0 - resolution: "v8-to-istanbul@npm:9.3.0" +"vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"vite-node@npm:3.1.4": + version: 3.1.4 + resolution: "vite-node@npm:3.1.4" dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.12" - "@types/istanbul-lib-coverage": "npm:^2.0.1" - convert-source-map: "npm:^2.0.0" - checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 + cac: "npm:^6.7.14" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.7.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/2fc71ddadd308b19b0d0dc09f5b9a108ea9bb640ec5fbd6179267994da8fd6c9d6a4c92098af7de73a0fa817055b518b28972452a2f19a1be754e79947e289d2 languageName: node linkType: hard -"vite@npm:^6.3.4": - version: 6.3.4 - resolution: "vite@npm:6.3.4" +"vite@npm:^5.0.0 || ^6.0.0, vite@npm:^6.3.4": + version: 6.3.5 + resolution: "vite@npm:6.3.5" dependencies: esbuild: "npm:^0.25.0" fdir: "npm:^6.4.4" @@ -10549,34 +11733,65 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/f1534a3f42d14b30e11c58e5e451903d965d5f5ba18d8c81f9df208589e3d2c65535abaa3268d3963573174b8e056ea7bc445f567622c65fcdf98eb4acc1bf4e - languageName: node - linkType: hard - -"vscode-oniguruma@npm:^1.7.0": - version: 1.7.0 - resolution: "vscode-oniguruma@npm:1.7.0" - checksum: 10c0/bef0073c665ddf8c86e51da94529c905856559e9aba97a9882f951acd572da560384775941ab6e7e8db94d9c578b25fefb951e4b73c37e8712e16b0231de2689 - languageName: node - linkType: hard - -"vscode-textmate@npm:^8.0.0": - version: 8.0.0 - resolution: "vscode-textmate@npm:8.0.0" - checksum: 10c0/836f7fe73fc94998a38ca193df48173a2b6eab08b4943d83c8cac9a2a0c3546cfdab4cf1b10b890ec4a4374c5bee03a885ef0e83e7fd2bd618cf00781c017c04 - languageName: node - linkType: hard - -"w3c-xmlserializer@npm:^4.0.0": - version: 4.0.0 - resolution: "w3c-xmlserializer@npm:4.0.0" - dependencies: - xml-name-validator: "npm:^4.0.0" - checksum: 10c0/02cc66d6efc590bd630086cd88252444120f5feec5c4043932b0d0f74f8b060512f79dc77eb093a7ad04b4f02f39da79ce4af47ceb600f2bf9eacdc83204b1a8 + checksum: 10c0/df70201659085133abffc6b88dcdb8a57ef35f742a01311fc56a4cfcda6a404202860729cc65a2c401a724f6e25f9ab40ce4339ed4946f550541531ced6fe41c + languageName: node + linkType: hard + +"vitest@npm:^3.1.4": + version: 3.1.4 + resolution: "vitest@npm:3.1.4" + dependencies: + "@vitest/expect": "npm:3.1.4" + "@vitest/mocker": "npm:3.1.4" + "@vitest/pretty-format": "npm:^3.1.4" + "@vitest/runner": "npm:3.1.4" + "@vitest/snapshot": "npm:3.1.4" + "@vitest/spy": "npm:3.1.4" + "@vitest/utils": "npm:3.1.4" + chai: "npm:^5.2.0" + debug: "npm:^4.4.0" + expect-type: "npm:^1.2.1" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + std-env: "npm:^3.9.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.13" + tinypool: "npm:^1.0.2" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0" + vite-node: "npm:3.1.4" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.1.4 + "@vitest/ui": 3.1.4 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/aec575e3cc6cf9b3cee224ae63569479e3a41fa980e495a73d384e31e273f34b18317a0da23bbd577c60fe5e717fa41cdc390de4049ce224ffdaa266ea0cdc67 languageName: node linkType: hard -"walker@npm:^1.0.7, walker@npm:^1.0.8, walker@npm:~1.0.5": +"walker@npm:^1.0.7, walker@npm:~1.0.5": version: 1.0.8 resolution: "walker@npm:1.0.8" dependencies: @@ -10594,6 +11809,15 @@ __metadata: languageName: node linkType: hard +"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3": + version: 1.7.3 + resolution: "wbuf@npm:1.7.3" + dependencies: + minimalistic-assert: "npm:^1.0.0" + checksum: 10c0/56edcc5ef2b3d30913ba8f1f5cccc364d180670b24d5f3f8849c1e6fb514e5c7e3a87548ae61227a82859eba6269c11393ae24ce12a2ea1ecb9b465718ddced7 + languageName: node + linkType: hard + "webgl-constants@npm:^1.1.1": version: 1.1.1 resolution: "webgl-constants@npm:1.1.1" @@ -10608,36 +11832,106 @@ __metadata: languageName: node linkType: hard -"webidl-conversions@npm:^7.0.0": - version: 7.0.0 - resolution: "webidl-conversions@npm:7.0.0" - checksum: 10c0/228d8cb6d270c23b0720cb2d95c579202db3aaf8f633b4e9dd94ec2000a04e7e6e43b76a94509cdb30479bd00ae253ab2371a2da9f81446cc313f89a4213a2c4 +"webpack-bundle-analyzer@npm:4.10.2": + version: 4.10.2 + resolution: "webpack-bundle-analyzer@npm:4.10.2" + dependencies: + "@discoveryjs/json-ext": "npm:0.5.7" + acorn: "npm:^8.0.4" + acorn-walk: "npm:^8.0.0" + commander: "npm:^7.2.0" + debounce: "npm:^1.2.1" + escape-string-regexp: "npm:^4.0.0" + gzip-size: "npm:^6.0.0" + html-escaper: "npm:^2.0.2" + opener: "npm:^1.5.2" + picocolors: "npm:^1.0.0" + sirv: "npm:^2.0.3" + ws: "npm:^7.3.1" + bin: + webpack-bundle-analyzer: lib/bin/analyzer.js + checksum: 10c0/00603040e244ead15b2d92981f0559fa14216381349412a30070a7358eb3994cd61a8221d34a3b3fb8202dc3d1c5ee1fbbe94c5c52da536e5b410aa1cf279a48 languageName: node linkType: hard -"whatwg-encoding@npm:^2.0.0": - version: 2.0.0 - resolution: "whatwg-encoding@npm:2.0.0" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 10c0/91b90a49f312dc751496fd23a7e68981e62f33afe938b97281ad766235c4872fc4e66319f925c5e9001502b3040dd25a33b02a9c693b73a4cbbfdc4ad10c3e3e +"webpack-dev-middleware@npm:^7.4.2": + version: 7.4.2 + resolution: "webpack-dev-middleware@npm:7.4.2" + dependencies: + colorette: "npm:^2.0.10" + memfs: "npm:^4.6.0" + mime-types: "npm:^2.1.31" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + schema-utils: "npm:^4.0.0" + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + checksum: 10c0/2aa873ef57a7095d7fba09400737b6066adc3ded229fd6eba89a666f463c2614c68e01ae58f662c9cdd74f0c8da088523d972329bf4a054e470bc94feb8bcad0 languageName: node linkType: hard -"whatwg-mimetype@npm:^3.0.0": - version: 3.0.0 - resolution: "whatwg-mimetype@npm:3.0.0" - checksum: 10c0/323895a1cda29a5fb0b9ca82831d2c316309fede0365047c4c323073e3239067a304a09a1f4b123b9532641ab604203f33a1403b5ca6a62ef405bcd7a204080f +"webpack-dev-server@npm:5.2.0": + version: 5.2.0 + resolution: "webpack-dev-server@npm:5.2.0" + dependencies: + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" + ansi-html-community: "npm:^0.0.8" + bonjour-service: "npm:^1.2.1" + chokidar: "npm:^3.6.0" + colorette: "npm:^2.0.10" + compression: "npm:^1.7.4" + connect-history-api-fallback: "npm:^2.0.0" + express: "npm:^4.21.2" + graceful-fs: "npm:^4.2.6" + http-proxy-middleware: "npm:^2.0.7" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" + p-retry: "npm:^6.2.0" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^2.4.1" + serve-index: "npm:^1.9.1" + sockjs: "npm:^0.3.24" + spdy: "npm:^4.0.2" + webpack-dev-middleware: "npm:^7.4.2" + ws: "npm:^8.18.0" + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + bin: + webpack-dev-server: bin/webpack-dev-server.js + checksum: 10c0/afb2e51945ac54ef3039e11e377241e1cb97a8d3f526f39f13c3fa924c530fb6063200c2c3ae4e33e6bcc110d4abed777c09ce18e2d261012853d81f3c5820ab languageName: node linkType: hard -"whatwg-url@npm:^11.0.0": - version: 11.0.0 - resolution: "whatwg-url@npm:11.0.0" +"websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": + version: 0.7.4 + resolution: "websocket-driver@npm:0.7.4" dependencies: - tr46: "npm:^3.0.0" - webidl-conversions: "npm:^7.0.0" - checksum: 10c0/f7ec264976d7c725e0696fcaf9ebe056e14422eacbf92fdbb4462034609cba7d0c85ffa1aab05e9309d42969bcf04632ba5ed3f3882c516d7b093053315bf4c1 + http-parser-js: "npm:>=0.5.1" + safe-buffer: "npm:>=5.1.0" + websocket-extensions: "npm:>=0.1.1" + checksum: 10c0/5f09547912b27bdc57bac17b7b6527d8993aa4ac8a2d10588bb74aebaf785fdcf64fea034aae0c359b7adff2044dd66f3d03866e4685571f81b13e548f9021f1 + languageName: node + linkType: hard + +"websocket-extensions@npm:>=0.1.1": + version: 0.1.4 + resolution: "websocket-extensions@npm:0.1.4" + checksum: 10c0/bbc8c233388a0eb8a40786ee2e30d35935cacbfe26ab188b3e020987e85d519c2009fe07cfc37b7f718b85afdba7e54654c9153e6697301f72561bfe429177e0 languageName: node linkType: hard @@ -10688,16 +11982,17 @@ __metadata: linkType: hard "which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18": - version: 1.1.18 - resolution: "which-typed-array@npm:1.1.18" + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" dependencies: available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - for-each: "npm:^0.3.3" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-tostringtag: "npm:^1.0.2" - checksum: 10c0/0412f4a91880ca1a2a63056187c2e3de6b129b2b5b6c17bc3729f0f7041047ae48fb7424813e51506addb2c97320003ee18b8c57469d2cde37983ef62126143c + checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f languageName: node linkType: hard @@ -10734,6 +12029,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" @@ -10782,16 +12089,6 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^4.0.2": - version: 4.0.2 - resolution: "write-file-atomic@npm:4.0.2" - dependencies: - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^3.0.7" - checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 - languageName: node - linkType: hard - "write@npm:^1.0.0": version: 1.0.3 resolution: "write@npm:1.0.3" @@ -10801,7 +12098,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.5.5": +"ws@npm:^7.3.1, ws@npm:^7.5.5": version: 7.5.10 resolution: "ws@npm:7.5.10" peerDependencies: @@ -10816,9 +12113,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.11.0": - version: 8.18.0 - resolution: "ws@npm:8.18.0" +"ws@npm:^8.18.0": + version: 8.18.2 + resolution: "ws@npm:8.18.2" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -10827,21 +12124,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 - languageName: node - linkType: hard - -"xml-name-validator@npm:^4.0.0": - version: 4.0.0 - resolution: "xml-name-validator@npm:4.0.0" - checksum: 10c0/c1bfa219d64e56fee265b2bd31b2fcecefc063ee802da1e73bad1f21d7afd89b943c9e2c97af2942f60b1ad46f915a4c81e00039c7d398b53cf410e29d3c30bd - languageName: node - linkType: hard - -"xmlchars@npm:^2.2.0": - version: 2.2.0 - resolution: "xmlchars@npm:2.2.0" - checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 + checksum: 10c0/4b50f67931b8c6943c893f59c524f0e4905bbd183016cfb0f2b8653aa7f28dad4e456b9d99d285bbb67cca4fedd9ce90dfdfaa82b898a11414ebd66ee99141e4 languageName: node linkType: hard @@ -10887,6 +12170,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.7.1": + version: 2.7.1 + resolution: "yaml@npm:2.7.1" + bin: + yaml: bin.mjs + checksum: 10c0/ee2126398ab7d1fdde566b4013b68e36930b9e6d8e68b6db356875c99614c10d678b6f45597a145ff6d63814961221fc305bf9242af8bf7450177f8a68537590 + languageName: node + linkType: hard + "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -10894,7 +12186,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1": +"yargs@npm:17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -10928,6 +12220,22 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.24.1": + version: 3.24.5 + resolution: "zod-to-json-schema@npm:3.24.5" + peerDependencies: + zod: ^3.24.1 + checksum: 10c0/0745b94ba53e652d39f262641cdeb2f75d24339fb6076a38ce55bcf53d82dfaea63adf524ebc5f658681005401687f8e9551c4feca7c4c882e123e66091dfb90 + languageName: node + linkType: hard + +"zod@npm:^3.23.8, zod@npm:^3.24.2": + version: 3.24.4 + resolution: "zod@npm:3.24.4" + checksum: 10c0/ab3112f017562180a41a0f83d870b333677f7d6b77f106696c56894567051b91154714a088149d8387a4f50806a2520efcb666f108cd384a35c236a191186d91 + languageName: node + linkType: hard + "zustand@npm:^3.7.1": version: 3.7.2 resolution: "zustand@npm:3.7.2" @@ -10941,10 +12249,10 @@ __metadata: linkType: hard "zustand@npm:^4.3.2": - version: 4.5.5 - resolution: "zustand@npm:4.5.5" + version: 4.5.6 + resolution: "zustand@npm:4.5.6" dependencies: - use-sync-external-store: "npm:1.2.2" + use-sync-external-store: "npm:^1.2.2" peerDependencies: "@types/react": ">=16.8" immer: ">=9.0.6" @@ -10956,6 +12264,27 @@ __metadata: optional: true react: optional: true - checksum: 10c0/d04469d76b29c7e4070da269886de4efdadedd3d3824dc2a06ac4ff62e3b5877f925e927afe7382de651829872b99adec48082f1bd69fe486149be666345e626 + checksum: 10c0/5b39aff2ef57e5a8ada647261ec1115697d397be311c51461d9ea81b5b63c6d2c498b960477ad2db72dc21db6aa229a92bdf644f6a8ecf7b1d71df5b4a5e95d3 + languageName: node + linkType: hard + +"zustand@npm:^5.0.1": + version: 5.0.4 + resolution: "zustand@npm:5.0.4" + peerDependencies: + "@types/react": ">=18.0.0" + immer: ">=9.0.6" + react: ">=18.0.0" + use-sync-external-store: ">=1.2.0" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + checksum: 10c0/5b6220f51b315cef3224a6517fcc00fa553df1af604009a9f02f8091727fe52e0499bc093be3efdd64b9fa4ad9238346aff21f34cdf79355207fcad097031596 languageName: node linkType: hard From 32b4c5fe2e618f7e1873ac95e8a4b348f0bb9785 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sat, 24 May 2025 16:42:31 +0800 Subject: [PATCH 022/112] Migrate to vitest --- devserver/package.json | 10 +- devserver/src/components/ControlButton.tsx | 7 +- devserver/src/components/Playground.tsx | 85 +++++++++--- devserver/src/components/SettingsPopup.tsx | 44 ++++++ .../src/components/__tests__/Playground.tsx | 75 +++++++++++ ...ayground-tests-Loading-tabs-via-Vite-1.png | Bin 0 -> 26976 bytes ...ayground-tests-Loading-tabs-via-Vite-2.png | Bin 0 -> 25013 bytes ...layground-tests-Multiple-evaluations-1.png | Bin 0 -> 85079 bytes .../Playground-tests-What-1.png | Bin 0 -> 23134 bytes devserver/src/components/repl/Repl.tsx | 13 +- .../sideContent/importers/importers.ts | 68 ++++++++++ .../components/sideContent/importers/index.ts | 6 + .../sideContent/importers/requireProvider.ts | 24 ++++ devserver/src/components/sideContent/types.ts | 9 +- devserver/src/components/sideContent/utils.ts | 31 ----- devserver/src/main.tsx | 3 +- devserver/tsconfig.json | 6 +- devserver/tsconfig.node.json | 2 +- devserver/vite.config.ts | 63 +++++++-- eslint.config.js | 94 ++++++++----- lib/buildtools/src/__mocks__/utils.ts | 7 + .../bundles/not_bundle/index.ts | 0 .../__test_mocks__/bundles/test0/index.ts | 5 + .../bundles/test0/manifest.json | 3 + .../bundles/test0/tsconfig.json | 0 .../__test_mocks__/bundles/test1/index.ts | 0 .../bundles/test1/tsconfig.json | 0 .../__test_mocks__/bundles/tsconfig.json | 0 .../__test_mocks__/tabs/tab0/src/index.tsx | 6 + .../__test_mocks__/tabs/tab0/tsconfig.json | 3 + .../bundles/test0/manifest.json | 1 - .../src/build/docs/__tests__/building.test.ts | 28 ++-- .../{bundle.test.ts => building.test.ts} | 28 +++- .../build/modules/__tests__/manifest.test.ts | 88 ++++++++++++ .../src/commands/__tests__/docs.test.ts | 59 +++++++++ .../src/commands/__tests__/lint.test.ts | 6 +- .../src/commands/__tests__/main.test.ts | 4 +- .../src/commands/__tests__/template.test.ts | 4 +- lib/buildtools/tsconfig.json | 2 +- lib/buildtools/vitest.config.ts | 22 +-- lib/buildtools/vitest.setup.ts | 36 +++-- lib/lintplugin/vitest.config.ts | 26 ++-- modules.json | 125 ------------------ package.json | 24 ++-- src/bundles/jest.config.js | 19 --- src/bundles/jest.polyfills.js | 7 - src/bundles/vitest.config.ts | 32 +++-- src/modules-lib/vitest.config.ts | 18 ++- src/tabs/vitest.config.ts | 35 ++--- vitest.config.ts | 15 ++- 50 files changed, 775 insertions(+), 368 deletions(-) create mode 100644 devserver/src/components/SettingsPopup.tsx create mode 100644 devserver/src/components/__tests__/Playground.tsx create mode 100644 devserver/src/components/__tests__/__screenshots__/Playground.tsx/Playground-tests-Loading-tabs-via-Vite-1.png create mode 100644 devserver/src/components/__tests__/__screenshots__/Playground.tsx/Playground-tests-Loading-tabs-via-Vite-2.png create mode 100644 devserver/src/components/__tests__/__screenshots__/Playground.tsx/Playground-tests-Multiple-evaluations-1.png create mode 100644 devserver/src/components/__tests__/__screenshots__/Playground.tsx/Playground-tests-What-1.png create mode 100644 devserver/src/components/sideContent/importers/importers.ts create mode 100644 devserver/src/components/sideContent/importers/index.ts create mode 100644 devserver/src/components/sideContent/importers/requireProvider.ts delete mode 100644 devserver/src/components/sideContent/utils.ts create mode 100644 lib/buildtools/src/__mocks__/utils.ts create mode 100644 lib/buildtools/src/__test_mocks__/bundles/not_bundle/index.ts rename lib/buildtools/src/{build => }/__test_mocks__/bundles/test0/index.ts (78%) create mode 100644 lib/buildtools/src/__test_mocks__/bundles/test0/manifest.json rename lib/buildtools/src/{build => }/__test_mocks__/bundles/test0/tsconfig.json (100%) rename lib/buildtools/src/{build => }/__test_mocks__/bundles/test1/index.ts (100%) rename lib/buildtools/src/{build => }/__test_mocks__/bundles/test1/tsconfig.json (100%) rename lib/buildtools/src/{build => }/__test_mocks__/bundles/tsconfig.json (100%) create mode 100644 lib/buildtools/src/__test_mocks__/tabs/tab0/src/index.tsx create mode 100644 lib/buildtools/src/__test_mocks__/tabs/tab0/tsconfig.json delete mode 100644 lib/buildtools/src/build/__test_mocks__/bundles/test0/manifest.json rename lib/buildtools/src/build/modules/__tests__/{bundle.test.ts => building.test.ts} (56%) create mode 100644 lib/buildtools/src/build/modules/__tests__/manifest.test.ts create mode 100644 lib/buildtools/src/commands/__tests__/docs.test.ts delete mode 100644 modules.json delete mode 100644 src/bundles/jest.config.js delete mode 100644 src/bundles/jest.polyfills.js diff --git a/devserver/package.json b/devserver/package.json index eedf39498d..d1319ff734 100644 --- a/devserver/package.json +++ b/devserver/package.json @@ -10,6 +10,7 @@ "@blueprintjs/icons": "^5.9.0", "ace-builds": "^1.25.1", "classnames": "^2.3.1", + "js-slang": "^1.0.81", "re-resizable": "^6.9.11", "react": "^18.3.1", "react-ace": "^10.1.0", @@ -19,10 +20,15 @@ "@types/react": "^18.3.1", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", + "@vitest/browser": "^3.1.4", + "playwright": "^1.52.0", "sass": "^1.85.0", - "vite": "^6.3.4" + "vite": "^6.3.4", + "vite-plugin-node-polyfills": "^0.23.0", + "vitest-browser-react": "^0.2.0" }, "scripts": { - "dev": "vite" + "dev": "vite", + "postinstall": "playwright install" } } diff --git a/devserver/src/components/ControlButton.tsx b/devserver/src/components/ControlButton.tsx index 4b64f6ffe7..ff940ce73a 100644 --- a/devserver/src/components/ControlButton.tsx +++ b/devserver/src/components/ControlButton.tsx @@ -27,13 +27,13 @@ const defaultOptions = { minimal: true }; -const ControlButton: React.FC = ({ +const ControlButton = React.forwardRef(({ label = '', icon, onClick, options = {}, isDisabled = false -}) => { +}, ref) => { const buttonOptions: ButtonOptions = { ...defaultOptions, ...options @@ -53,12 +53,13 @@ const ControlButton: React.FC = ({ className={buttonOptions.className} type={buttonOptions.type} onClick={onClick} + ref={ref} icon={!buttonOptions.iconOnRight && iconElement} rightIcon={buttonOptions.iconOnRight && iconElement} > {label} ); -}; +}); export default ControlButton; diff --git a/devserver/src/components/Playground.tsx b/devserver/src/components/Playground.tsx index 7cb4440155..0e7c6a0eea 100644 --- a/devserver/src/components/Playground.tsx +++ b/devserver/src/components/Playground.tsx @@ -1,20 +1,22 @@ -import { Classes, Intent, OverlayToaster, type ToastProps } from '@blueprintjs/core'; +import { type ToastProps, Intent, OverlayToaster, Popover, Tooltip, Button, Classes } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; import classNames from 'classnames'; -import { SourceDocumentation, getNames, runInContext, type Context } from 'js-slang'; +import { type Context, getNames, SourceDocumentation, runInContext } from 'js-slang'; // Importing this straight from js-slang doesn't work for whatever reason import createContext from 'js-slang/dist/createContext'; import { Chapter, Variant } from 'js-slang/dist/types'; import { stringify } from 'js-slang/dist/utils/stringify'; -import React, { useCallback } from 'react'; +import React from 'react'; import mockModuleContext from '../mockModuleContext'; import type { InterpreterOutput } from '../types'; +import SettingsPopup from './SettingsPopup'; import Workspace, { type WorkspaceProps } from './Workspace'; import { ControlBarClearButton } from './controlBar/ControlBarClearButton'; import { ControlBarRefreshButton } from './controlBar/ControlBarRefreshButton'; import { ControlBarRunButton } from './controlBar/ControlBarRunButton'; import testTabContent from './sideContent/TestTab'; +import loadDynamicTabs from './sideContent/importers'; import type { SideContentTab } from './sideContent/types'; -import { getDynamicTabs } from './sideContent/utils'; const refreshSuccessToast: ToastProps = { intent: Intent.SUCCESS, @@ -31,15 +33,28 @@ const evalSuccessToast: ToastProps = { message: 'Code evaluated successfully!' }; -const createContextHelper = () => { - const tempContext = createContext(Chapter.SOURCE_4, Variant.DEFAULT); +const createContextHelper = (onConsoleLog: (arg: string) => void) => { + const tempContext = createContext(Chapter.SOURCE_4, Variant.DEFAULT, {}, [], undefined, { + rawDisplay(value: any, str: string | undefined) { + const valueStr = typeof value === 'string' ? value : stringify(value); + if (str !== undefined) { + onConsoleLog(`${valueStr} ${str}`); + } else { + onConsoleLog(valueStr); + } + }, + } as any); return tempContext; }; const Playground: React.FC = () => { + const consoleLogs = React.useRef([]); + const [moduleBackend, setModuleBackend] = React.useState(null); + const [useCompiledTabs, setUseCompiledTabs] = React.useState(false); + const [dynamicTabs, setDynamicTabs] = React.useState([]); const [selectedTabId, setSelectedTab] = React.useState(testTabContent.id); - const [codeContext, setCodeContext] = React.useState(createContextHelper()); + const [codeContext, setCodeContext] = React.useState(createContextHelper(str => consoleLogs.current.push(str))); const [editorValue, setEditorValue] = React.useState(localStorage.getItem('editorValue') ?? ''); const [replOutput, setReplOutput] = React.useState(null); const [alerts, setAlerts] = React.useState([]); @@ -50,12 +65,12 @@ const Playground: React.FC = () => { if (toaster.current) { toaster.current.show({ ...props, - timeout: 1500 + timeout: 15000 }); } }; - const getAutoComplete = useCallback((row: number, col: number, callback: any) => { + const getAutoComplete = React.useCallback((row: number, col: number, callback: any) => { getNames(editorValue, row, col, codeContext) .then(([editorNames, displaySuggestions]) => { if (!displaySuggestions) { @@ -89,8 +104,9 @@ const Playground: React.FC = () => { }); }, [editorValue, codeContext]); - const loadTabs = () => getDynamicTabs(codeContext) - .then((tabs) => { + const loadTabs = async () => { + try { + const tabs = await loadDynamicTabs(codeContext, useCompiledTabs); setDynamicTabs(tabs); const newIds = tabs.map(({ id }) => id); @@ -100,17 +116,23 @@ const Playground: React.FC = () => { setSelectedTab(testTabContent.id); } setAlerts(newIds); - }) - .catch((error) => { + + } catch (error) { showToast(errorToast); console.log(error); - }); + } + }; const evalCode = () => { codeContext.errors = []; codeContext.moduleContexts = mockModuleContext.moduleContexts = {}; + consoleLogs.current = []; - runInContext(editorValue, codeContext) + runInContext(editorValue, codeContext, { + importOptions: { + loadTabs: useCompiledTabs + } + }) .then((result) => { if (codeContext.errors.length > 0) { showToast(errorToast); @@ -119,26 +141,27 @@ const Playground: React.FC = () => { .then(() => showToast(evalSuccessToast)); } - // TODO: Add support for console.log? if (result.status === 'finished') { setReplOutput({ type: 'result', // code: editorValue, - consoleLogs: [], + consoleLogs: consoleLogs.current, value: stringify(result.value) }); } else if (result.status === 'error') { setReplOutput({ type: 'errors', errors: codeContext.errors, - consoleLogs: [] + consoleLogs: consoleLogs.current }); } }); }; const resetEditor = () => { - setCodeContext(createContextHelper()); + setCodeContext(createContextHelper(str => consoleLogs.current.push(str))); + consoleLogs.current = []; + setEditorValue(''); localStorage.setItem('editorValue', ''); setDynamicTabs([]); @@ -155,6 +178,27 @@ const Playground: React.FC = () => { const workspaceProps: WorkspaceProps = { controlBarProps: { editorButtons: [ + } + renderTarget={({ isOpen: _isOpen, ...targetProps }) => { + return ( + + @@ -133,7 +136,7 @@ const MultiItemDisplay = ({ elements }: MultiItemDisplayProps) => { justifyContent: 'center' }} > - {elements[currentStep]} + {props.elements[currentStep]} ); diff --git a/lib/modules-lib/src/tabs/PlayButton.tsx b/lib/modules-lib/src/tabs/PlayButton.tsx index c21f3bd0e0..b78cd15943 100644 --- a/lib/modules-lib/src/tabs/PlayButton.tsx +++ b/lib/modules-lib/src/tabs/PlayButton.tsx @@ -1,7 +1,6 @@ /* [Imports] */ import { Icon, type ButtonProps, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React from 'react'; import ButtonComponent from './ButtonComponent'; /* [Exports] */ @@ -11,17 +10,15 @@ export type PlayButtonProps = ButtonProps & { }; /* [Main] */ -export default class PlayButton extends React.Component { - render() { - return - - - - ; - } +export default function PlayButton(props: PlayButtonProps) { + return + + + + ; } diff --git a/lib/modules-lib/src/tabs/testUtils.ts b/lib/modules-lib/src/tabs/testUtils.ts deleted file mode 100644 index f662679a41..0000000000 --- a/lib/modules-lib/src/tabs/testUtils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { DebuggerContext } from '../types'; - -export const mockDebuggerContext = (state: T, moduleName: string) => ({ - context: { - moduleContexts: { - [moduleName]: { - state - } - } - } -}) as DebuggerContext; diff --git a/lib/modules-lib/src/tabs/utils.ts b/lib/modules-lib/src/tabs/utils.ts index c9c9429b1d..fb7170dd33 100644 --- a/lib/modules-lib/src/tabs/utils.ts +++ b/lib/modules-lib/src/tabs/utils.ts @@ -1,34 +1,19 @@ -import type { IconName } from '@blueprintjs/icons'; -import type { DebuggerContext } from '../types'; +import type { DebuggerContext, ModuleSideContent } from '../types'; -export function getModuleState({ context }: DebuggerContext, name: string): T { - return context.moduleContexts[name].state; +/** + * Helper function for extracting the state object for your bundle + * @template T The type of your bundle's state object + * @param debuggerContext DebuggerContext as returned by the frontend + * @param name Name of your bundle + * @returns The state object of your bundle + */ +export function getModuleState(debuggerContext: DebuggerContext, name: string): T { + return debuggerContext.context.moduleContexts[name].state; } /** * Helper for typing tabs */ -export function defineTab(tab: { - /** - * BlueprintJS IconName element's name, used to render the icon which will be - * displayed in the side contents panel. - * @see https://blueprintjs.com/docs/#icons - */ - iconName: IconName - /** - * The Tab's icon tooltip in the side contents on Source Academy frontend. - */ - label: string - /** - * This function will be called to determine if the component will be - * rendered - */ - toSpawn?: (context: DebuggerContext) => boolean - /** - * This function will be called to render the module tab in the side contents - * on Source Academy frontend. - */ - body: (context: DebuggerContext) => JSX.Element -}) { +export function defineTab(tab: ModuleSideContent) { return tab; } diff --git a/lib/modules-lib/src/types/index.ts b/lib/modules-lib/src/types/index.ts index 9da4fb9ad3..79e72ee67e 100644 --- a/lib/modules-lib/src/types/index.ts +++ b/lib/modules-lib/src/types/index.ts @@ -1,13 +1,21 @@ +import type { IconName } from '@blueprintjs/icons'; import type { Context } from 'js-slang'; import type { FC } from 'react'; /** * Represents an animation drawn using WebGL - * @field duration Duration of the animation in secondss - * @field fps Framerate in frames per second */ export abstract class glAnimation { - constructor(public readonly duration: number, public readonly fps: number) { } + constructor( + /** + * Duration of the animation in seconds + */ + public readonly duration: number, + /** + * Framerate in frames per second + */ + public readonly fps: number + ) { } public abstract getFrame(timestamp: number): AnimFrame; @@ -42,3 +50,26 @@ export interface ReplResult { } export type ModuleTab = FC<{ context: DebuggerContext }>; + +export type ModuleSideContent = { + /** + * BlueprintJS IconName element's name, used to render the icon which will be + * displayed in the side contents panel. + * @see https://blueprintjs.com/docs/#icons + */ + iconName: IconName + /** + * The Tab's icon tooltip in the side contents on Source Academy frontend. + */ + label: string + /** + * This function will be called to determine if the component will be + * rendered + */ + toSpawn?: (context: DebuggerContext) => boolean + /** + * This function will be called to render the module tab in the side contents + * on Source Academy frontend. + */ + body: (context: DebuggerContext) => JSX.Element +}; diff --git a/lib/modules-lib/src/types/js-slang/context.d.ts b/lib/modules-lib/src/types/js-slang/context.d.ts deleted file mode 100644 index 55b36bda11..0000000000 --- a/lib/modules-lib/src/types/js-slang/context.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { Context } from 'js-slang'; - -declare const ctx: Context; -export default ctx; diff --git a/yarn.lock b/yarn.lock index cec0dfad92..c186e35a25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3265,7 +3265,7 @@ __metadata: js-slang: "npm:^1.0.81" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" - typedoc: "npm:^0.28.5" + typedoc: "npm:^0.28.4" typedoc-plugin-markdown: "npm:^4.7.0" typedoc-plugin-rename-defaults: "npm:^0.7.3" typescript: "npm:^5.8.2" @@ -13371,23 +13371,6 @@ __metadata: languageName: node linkType: hard -"typedoc@npm:^0.28.5": - version: 0.28.5 - resolution: "typedoc@npm:0.28.5" - dependencies: - "@gerrit0/mini-shiki": "npm:^3.2.2" - lunr: "npm:^2.3.9" - markdown-it: "npm:^14.1.0" - minimatch: "npm:^9.0.5" - yaml: "npm:^2.7.1" - peerDependencies: - typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x - bin: - typedoc: bin/typedoc - checksum: 10c0/fc8235dbe8f14da24fdb088467b01887b3f1375b27d5caf0276ae405f03aa1f523e94aea52fe8ce1a3d477ae9e3f4f69fdc28614275445a828a77db88784e6ce - languageName: node - linkType: hard - "typescript-eslint@npm:^8.33.1": version: 8.33.1 resolution: "typescript-eslint@npm:8.33.1" From a373b09f86b5c8ce5c76f11a0670969cf7a06b2e Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Mon, 23 Jun 2025 22:49:10 +0800 Subject: [PATCH 077/112] Fix broken tests and installs --- devserver/package.json | 2 +- lib/buildtools/src/build/__tests__/manifest.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devserver/package.json b/devserver/package.json index fc8f1adf41..f78da9b09e 100644 --- a/devserver/package.json +++ b/devserver/package.json @@ -38,7 +38,7 @@ "scripts": { "dev": "vite", "lint": "eslint src", - "postinstall": "playwright install", + "postinstall": "playwright install --with-deps", "test": "vitest --config ./vite.config.ts --project \"Dev Server\"", "tsc": "tsc --project ./tsconfig.json" } diff --git a/lib/buildtools/src/build/__tests__/manifest.test.ts b/lib/buildtools/src/build/__tests__/manifest.test.ts index b0bc145b3d..1ef92329b9 100644 --- a/lib/buildtools/src/build/__tests__/manifest.test.ts +++ b/lib/buildtools/src/build/__tests__/manifest.test.ts @@ -10,7 +10,7 @@ describe('Test bundle manifest schema validation', () => { test('Valid Schema', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": [] }'); - mockedReadFile.mockResolvedValueOnce('{ version: "1.0.0" }'); + mockedReadFile.mockResolvedValueOnce('{ "version": "1.0.0" }'); await expect(getBundleManifest('yes')) .resolves @@ -22,7 +22,7 @@ describe('Test bundle manifest schema validation', () => { test('Valid Schema with tabs without verification', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": ["tab0", "tab1"] }'); - mockedReadFile.mockResolvedValueOnce('{ version: "1.0.0" }'); + mockedReadFile.mockResolvedValueOnce('{ "version": "1.0.0" }'); await expect(getBundleManifest('yes', false)) .resolves From 312c0feaab7312101a1b8915e9fa0e5b4c139e3e Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 24 Jun 2025 00:16:24 +0800 Subject: [PATCH 078/112] Fix broken tsc test --- lib/buildtools/src/__test_mocks__/bundles/context.d.ts | 4 ++++ lib/buildtools/src/__test_mocks__/bundles/tsconfig.json | 2 +- lib/buildtools/src/prebuild/tsc.ts | 3 +-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 lib/buildtools/src/__test_mocks__/bundles/context.d.ts diff --git a/lib/buildtools/src/__test_mocks__/bundles/context.d.ts b/lib/buildtools/src/__test_mocks__/bundles/context.d.ts new file mode 100644 index 0000000000..55b36bda11 --- /dev/null +++ b/lib/buildtools/src/__test_mocks__/bundles/context.d.ts @@ -0,0 +1,4 @@ +import type { Context } from 'js-slang'; + +declare const ctx: Context; +export default ctx; diff --git a/lib/buildtools/src/__test_mocks__/bundles/tsconfig.json b/lib/buildtools/src/__test_mocks__/bundles/tsconfig.json index 59d6985b78..799c68db64 100644 --- a/lib/buildtools/src/__test_mocks__/bundles/tsconfig.json +++ b/lib/buildtools/src/__test_mocks__/bundles/tsconfig.json @@ -32,7 +32,7 @@ "noImplicitAny": false, "verbatimModuleSyntax": true, "paths": { - "js-slang/context": ["../../../../modules-lib/src/types/js-slang/context.d.ts"] + "js-slang/context": ["./context.d.ts"] }, "ignoreDeprecations": "5.0" }, diff --git a/lib/buildtools/src/prebuild/tsc.ts b/lib/buildtools/src/prebuild/tsc.ts index 800cda7861..52c020cbfc 100644 --- a/lib/buildtools/src/prebuild/tsc.ts +++ b/lib/buildtools/src/prebuild/tsc.ts @@ -130,8 +130,7 @@ export const { }; } }, tscResult => { - const inputType = 'entryPoint' in tscResult.input ? 'tab' : 'bundle'; - const prefix = `${chalk.blueBright(`[${inputType} ${tscResult.input.name}]`)}: ${chalk.cyanBright('tsc completed')}`; + const prefix = `${chalk.blueBright(`[${tscResult.input.type} ${tscResult.input.name}]`)}: ${chalk.cyanBright('tsc completed')}`; if (tscResult.severity === 'error' && 'error' in tscResult) { return `${prefix} ${chalk.cyanBright('with')} ${chalk.redBright('errors')}: ${tscResult.error}`; } From 25bc826b02432ef06360a022dd1cc8289b3511df Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 24 Jun 2025 21:56:13 +0800 Subject: [PATCH 079/112] Change document configurations --- docs/.gitignore | 2 + docs/.vitepress/config.ts | 9 +- docs/package.json | 6 +- docs/src/buildtools/1-builders/1-modules.md | 7 +- docs/src/buildtools/1-builders/2-docs.md | 2 +- docs/src/buildtools/3-structure.md | 4 - docs/src/buildtools/index.md | 2 +- docs/src/lib/modules-lib/README.md | 26 - docs/src/lib/modules-lib/specialErrors.md | 25 - .../lib/modules-lib/tabs/AnimationCanvas.md | 93 --- .../lib/modules-lib/tabs/AutoLoopSwitch.md | 66 -- .../lib/modules-lib/tabs/ButtonComponent.md | 128 ---- docs/src/lib/modules-lib/tabs/ModalDiv.md | 151 ----- .../lib/modules-lib/tabs/MultItemDisplay.md | 91 --- docs/src/lib/modules-lib/tabs/PlayButton.md | 66 -- docs/src/lib/modules-lib/tabs/WebglCanvas.md | 20 - .../src/lib/modules-lib/tabs/css_constants.md | 117 ---- docs/src/lib/modules-lib/tabs/utils.md | 139 ---- docs/src/lib/modules-lib/type_map.md | 384 ------------ docs/src/lib/modules-lib/types.md | 593 ------------------ docs/src/lib/modules-lib/utilities.md | 155 ----- .../modules/2-bundle/2-creating/2-creating.md | 9 +- lib/modules-lib/package.json | 2 + lib/modules-lib/src/tabs/AnimationCanvas.tsx | 2 +- .../src/tabs/MultiItemDisplay/image.png | Bin 0 -> 104008 bytes .../index.tsx} | 1 + lib/modules-lib/src/tabs/index.ts | 14 + lib/modules-lib/src/type_map.ts | 16 +- lib/modules-lib/typedoc.config.js | 43 ++ yarn.lock | 21 + 30 files changed, 118 insertions(+), 2076 deletions(-) delete mode 100644 docs/src/lib/modules-lib/README.md delete mode 100644 docs/src/lib/modules-lib/specialErrors.md delete mode 100644 docs/src/lib/modules-lib/tabs/AnimationCanvas.md delete mode 100644 docs/src/lib/modules-lib/tabs/AutoLoopSwitch.md delete mode 100644 docs/src/lib/modules-lib/tabs/ButtonComponent.md delete mode 100644 docs/src/lib/modules-lib/tabs/ModalDiv.md delete mode 100644 docs/src/lib/modules-lib/tabs/MultItemDisplay.md delete mode 100644 docs/src/lib/modules-lib/tabs/PlayButton.md delete mode 100644 docs/src/lib/modules-lib/tabs/WebglCanvas.md delete mode 100644 docs/src/lib/modules-lib/tabs/css_constants.md delete mode 100644 docs/src/lib/modules-lib/tabs/utils.md delete mode 100644 docs/src/lib/modules-lib/type_map.md delete mode 100644 docs/src/lib/modules-lib/types.md delete mode 100644 docs/src/lib/modules-lib/utilities.md create mode 100644 lib/modules-lib/src/tabs/MultiItemDisplay/image.png rename lib/modules-lib/src/tabs/{MultItemDisplay.tsx => MultiItemDisplay/index.tsx} (99%) create mode 100644 lib/modules-lib/src/tabs/index.ts create mode 100644 lib/modules-lib/typedoc.config.js diff --git a/docs/.gitignore b/docs/.gitignore index b73303468e..c005e7c30b 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,2 +1,4 @@ .vitepress/cache .vitepress/dist + +src/lib/modules-lib diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 2e890627f2..837afb0ab3 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -12,7 +12,9 @@ const vitepressOptions: UserConfig = { // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Home', link: '/' }, - { text: 'Module Development', link: '/modules/' } + { text: 'Module Development', link: '/modules/' }, + { text: 'Common Library', link: '/lib' }, + { text: 'Build Tools', link: '/buildtools' } ], siteTitle: 'SA Modules', socialLinks: [ @@ -25,7 +27,7 @@ const vitepressOptions: UserConfig = { search: { provider: 'local' } - } + }, }; const commonSideBarOptions: VitePressSidebarOptions = { @@ -43,7 +45,8 @@ const commonSideBarOptions: VitePressSidebarOptions = { const sidebarConfigs: Record = { buildtools: {}, modules: {}, - lib: {} + lib: {}, + '/lib/modules-lib': {} }; const sideBarOptions = Object.entries(sidebarConfigs).map(([startPath, options]): VitePressSidebarOptions => ({ diff --git a/docs/package.json b/docs/package.json index a3ce9a981a..225679c979 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,8 +9,8 @@ "vitepress-sidebar": "^1.31.1" }, "scripts": { - "docs:dev": "vitepress dev .", - "docs:build": "vitepress build .", - "docs:preview": "vitepress preview ." + "build": "vitepress build .", + "dev": "vitepress dev .", + "preview": "vitepress preview ." } } diff --git a/docs/src/buildtools/1-builders/1-modules.md b/docs/src/buildtools/1-builders/1-modules.md index d4783f099b..9612c5cdc3 100644 --- a/docs/src/buildtools/1-builders/1-modules.md +++ b/docs/src/buildtools/1-builders/1-modules.md @@ -36,7 +36,7 @@ Bundles and tabs are transpiled with esbuild using the following common options <<< ../../../../lib/buildtools/src/build/modules/commons.ts#esbuildOptions -## Options Explained: +## Options Explained ### `bundle: true` Tell `esbuild` to bundle the code into a single file. @@ -94,7 +94,7 @@ var module = (function() { ``` Which are then transformed by the `outputBundleOrTab` function, which produces output that looks like this: ```js -require => { +export default require => { var exports = {} exports.add_one = function(x) { return x + 1; @@ -104,7 +104,8 @@ require => { } ``` -When bundles and tabs are loaded, the IIFE is called with a function that simulates the `require()` function in CommonJS to provide the dependencies marked as external (that have to be provided at runtime). +Consumers of this compiled version of bundles and tabs can retrieve the IIFE by using the default export. When bundles and tabs are loaded, the IIFE is called with a function that simulates the `require()` function +in CommonJS to provide the dependencies marked as external (that have to be provided at runtime). ## `js-slang/context` `js-slang/context` is an import provided at runtime by `js-slang` that returns the context in use for evaluation. It is not an actual import that's available diff --git a/docs/src/buildtools/1-builders/2-docs.md b/docs/src/buildtools/1-builders/2-docs.md index 6fe6cb7033..24cf40f578 100644 --- a/docs/src/buildtools/1-builders/2-docs.md +++ b/docs/src/buildtools/1-builders/2-docs.md @@ -1,6 +1,6 @@ # Documentation Generation -There are two types of documentation used by Source, which are the jsons and the HTML documentation. Both are built using the [`typedoc`](typedoc.org) tool. By reading comments and type annotations, `typedoc` is able to generate both human readable documentation and documentation in the form of JSON. +There are two types of documentation used by Source, which are the jsons and the HTML documentation. Both are built using the [`typedoc`](https://typedoc.org) tool. By reading comments and type annotations, `typedoc` is able to generate both human readable documentation and documentation in the form of JSON. ## Typedoc Overview Typedoc has been configured to use the [`package` entry point strategy](https://typedoc.org/documents/Options.Input.html#packages). To make sure that each bundle's documentation is generated with the proper name, each bundle's `tsconfig.json` contains diff --git a/docs/src/buildtools/3-structure.md b/docs/src/buildtools/3-structure.md index 520e3c2eb4..89f65dd1cb 100644 --- a/docs/src/buildtools/3-structure.md +++ b/docs/src/buildtools/3-structure.md @@ -1,7 +1,3 @@ ---- -order: 3 ---- - # General Design of the Build Tools ## Path Resolution diff --git a/docs/src/buildtools/index.md b/docs/src/buildtools/index.md index 17acfc680d..d39f3f412b 100644 --- a/docs/src/buildtools/index.md +++ b/docs/src/buildtools/index.md @@ -8,7 +8,7 @@ The Source Academy Modules build tools are written in Typescript and designed to The build tools are comprised of several sections: -- [Command Handlers](./command) +- [Command Handlers](./2-command) - The actual code that runs command line argument parsing - [Builders](./1-builders) - The code that converts a bundle or tab into its outputs diff --git a/docs/src/lib/modules-lib/README.md b/docs/src/lib/modules-lib/README.md deleted file mode 100644 index 6bcf9e823f..0000000000 --- a/docs/src/lib/modules-lib/README.md +++ /dev/null @@ -1,26 +0,0 @@ -**Modules Common Library** - -*** - -# Source Academy Modules Common Library - -This is a library for providing common utilities that are used between different bundles and tabs, as well as for -providing reusable frontend components for tabs. - -To build, run `yarn build`. Both types and compiled Javascript are found in `dist`. - -## Modules - -- [specialErrors](specialErrors.md) -- [tabs/AnimationCanvas](tabs/AnimationCanvas.md) -- [tabs/AutoLoopSwitch](tabs/AutoLoopSwitch.md) -- [tabs/ButtonComponent](tabs/ButtonComponent.md) -- [tabs/css\_constants](tabs/css_constants.md) -- [tabs/ModalDiv](tabs/ModalDiv.md) -- [tabs/MultItemDisplay](tabs/MultItemDisplay.md) -- [tabs/PlayButton](tabs/PlayButton.md) -- [tabs/utils](tabs/utils.md) -- [tabs/WebglCanvas](tabs/WebglCanvas.md) -- [type\_map](type_map.md) -- [types](types.md) -- [utilities](utilities.md) diff --git a/docs/src/lib/modules-lib/specialErrors.md b/docs/src/lib/modules-lib/specialErrors.md deleted file mode 100644 index da763e5021..0000000000 --- a/docs/src/lib/modules-lib/specialErrors.md +++ /dev/null @@ -1,25 +0,0 @@ -[**Modules Common Library**](README.md) - -*** - -[Modules Common Library](README.md) / specialErrors - -# specialErrors - -## Functions - -### interrupt() - -```ts -function interrupt(): void; -``` - -Defined in: [specialErrors.ts:6](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/specialErrors.ts#L6) - -This function is used to interrupt the frontend execution. -When called, the frontend will notify that the program has ended successfully -and display a message that the program is stopped by a module. - -#### Returns - -`void` diff --git a/docs/src/lib/modules-lib/tabs/AnimationCanvas.md b/docs/src/lib/modules-lib/tabs/AnimationCanvas.md deleted file mode 100644 index d3e1cf2e71..0000000000 --- a/docs/src/lib/modules-lib/tabs/AnimationCanvas.md +++ /dev/null @@ -1,93 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/AnimationCanvas - -# tabs/AnimationCanvas - -## Type Aliases - -### AnimCanvasProps - -```ts -type AnimCanvasProps = { - animation: glAnimation; -}; -``` - -Defined in: [tabs/AnimationCanvas.tsx:11](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/AnimationCanvas.tsx#L11) - -#### Properties - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `animation` - - - -[`glAnimation`](../types.md#glanimation) - - - -[tabs/AnimationCanvas.tsx:12](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/AnimationCanvas.tsx#L12) - -
    - -## Functions - -### AnimationCanvas() - -```ts -function AnimationCanvas(props): Element; -``` - -Defined in: [tabs/AnimationCanvas.tsx:349](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/AnimationCanvas.tsx#L349) - -React Component for displaying [glAnimations](../types.md#glanimation). - -Uses [WebGLCanvas](WebglCanvas.md#webglcanvas) internally. - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`props` - - - -[`AnimCanvasProps`](#animcanvasprops) - -
    - -#### Returns - -`Element` diff --git a/docs/src/lib/modules-lib/tabs/AutoLoopSwitch.md b/docs/src/lib/modules-lib/tabs/AutoLoopSwitch.md deleted file mode 100644 index 5153b13f57..0000000000 --- a/docs/src/lib/modules-lib/tabs/AutoLoopSwitch.md +++ /dev/null @@ -1,66 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/AutoLoopSwitch - -# tabs/AutoLoopSwitch - -## Type Aliases - -### AutoLoopSwitchProps - -```ts -type AutoLoopSwitchProps = Omit & { - isAutoLooping: boolean; -}; -``` - -Defined in: [tabs/AutoLoopSwitch.tsx:5](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/AutoLoopSwitch.tsx#L5) - -#### Type declaration - -##### isAutoLooping - -```ts -isAutoLooping: boolean; -``` - -## Functions - -### AutoLoopSwitch() - -```ts -function AutoLoopSwitch(props): Element; -``` - -Defined in: [tabs/AutoLoopSwitch.tsx:10](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/AutoLoopSwitch.tsx#L10) - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`props` - - - -[`AutoLoopSwitchProps`](#autoloopswitchprops) - -
    - -#### Returns - -`Element` diff --git a/docs/src/lib/modules-lib/tabs/ButtonComponent.md b/docs/src/lib/modules-lib/tabs/ButtonComponent.md deleted file mode 100644 index f63ebd4a8d..0000000000 --- a/docs/src/lib/modules-lib/tabs/ButtonComponent.md +++ /dev/null @@ -1,128 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/ButtonComponent - -# tabs/ButtonComponent - -## Type Aliases - -### ButtonComponentProps - -```ts -type ButtonComponentProps = { - children?: ReactNode; - disabled?: boolean; - onClick?: MouseEventHandler; -}; -``` - -Defined in: [tabs/ButtonComponent.tsx:12](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ButtonComponent.tsx#L12) - -#### Properties - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `children?` - - - -`ReactNode` - - - -[tabs/ButtonComponent.tsx:15](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ButtonComponent.tsx#L15) - -
    - - `disabled?` - - - -`boolean` - - - -[tabs/ButtonComponent.tsx:14](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ButtonComponent.tsx#L14) - -
    - - `onClick?` - - - -`MouseEventHandler`\<`HTMLElement`\> - - - -[tabs/ButtonComponent.tsx:13](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ButtonComponent.tsx#L13) - -
    - -## Functions - -### ButtonComponent() - -```ts -function ButtonComponent(props): Element; -``` - -Defined in: [tabs/ButtonComponent.tsx:22](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ButtonComponent.tsx#L22) - -Button Component that retains interactability even when disabled. Refer to -[https://blueprintjs.com/docs/#core/components/buttons.anchorbutton\|this](https://blueprintjs.com/docs/#core/components/buttons.anchorbutton|this) for more information - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`props` - - - -[`ButtonComponentProps`](#buttoncomponentprops) - -
    - -#### Returns - -`Element` diff --git a/docs/src/lib/modules-lib/tabs/ModalDiv.md b/docs/src/lib/modules-lib/tabs/ModalDiv.md deleted file mode 100644 index e1856a28d4..0000000000 --- a/docs/src/lib/modules-lib/tabs/ModalDiv.md +++ /dev/null @@ -1,151 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/ModalDiv - -# tabs/ModalDiv - -## Interfaces - -### ModalProps - -Defined in: [tabs/ModalDiv.tsx:42](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L42) - -#### Properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `children` - - - -`ReactElement` - - - -[tabs/ModalDiv.tsx:47](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L47) - -
    - - `handleClose` - - - -`MouseEventHandler` - - - -[tabs/ModalDiv.tsx:46](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L46) - -
    - - `height` - - - -`string` - - - -[tabs/ModalDiv.tsx:44](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L44) - -
    - - `open` - - - -`boolean` - - - -[tabs/ModalDiv.tsx:43](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L43) - -
    - - `width` - - - -`string` - - - -[tabs/ModalDiv.tsx:45](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L45) - -
    - -## Functions - -### Modal() - -```ts -function Modal(__namedParameters): Element; -``` - -Defined in: [tabs/ModalDiv.tsx:49](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/ModalDiv.tsx#L49) - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`__namedParameters` - - - -[`ModalProps`](#modalprops) - -
    - -#### Returns - -`Element` diff --git a/docs/src/lib/modules-lib/tabs/MultItemDisplay.md b/docs/src/lib/modules-lib/tabs/MultItemDisplay.md deleted file mode 100644 index 24284fc47a..0000000000 --- a/docs/src/lib/modules-lib/tabs/MultItemDisplay.md +++ /dev/null @@ -1,91 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/MultItemDisplay - -# tabs/MultItemDisplay - -## Type Aliases - -### MultiItemDisplayProps - -```ts -type MultiItemDisplayProps = { - elements: React.JSX.Element[]; -}; -``` - -Defined in: [tabs/MultItemDisplay.tsx:6](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/MultItemDisplay.tsx#L6) - -#### Properties - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `elements` - - - -`React.JSX.Element`[] - - - -[tabs/MultItemDisplay.tsx:7](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/MultItemDisplay.tsx#L7) - -
    - -## Functions - -### MultiItemDisplay() - -```ts -function MultiItemDisplay(props): Element; -``` - -Defined in: [tabs/MultItemDisplay.tsx:13](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/MultItemDisplay.tsx#L13) - -React Component for displaying multiple items - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`props` - - - -[`MultiItemDisplayProps`](#multiitemdisplayprops) - -
    - -#### Returns - -`Element` diff --git a/docs/src/lib/modules-lib/tabs/PlayButton.md b/docs/src/lib/modules-lib/tabs/PlayButton.md deleted file mode 100644 index 23a97bcd13..0000000000 --- a/docs/src/lib/modules-lib/tabs/PlayButton.md +++ /dev/null @@ -1,66 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/PlayButton - -# tabs/PlayButton - -## Type Aliases - -### PlayButtonProps - -```ts -type PlayButtonProps = ButtonProps & { - isPlaying: boolean; -}; -``` - -Defined in: [tabs/PlayButton.tsx:7](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/PlayButton.tsx#L7) - -#### Type declaration - -##### isPlaying - -```ts -isPlaying: boolean; -``` - -## Functions - -### PlayButton() - -```ts -function PlayButton(props): Element; -``` - -Defined in: [tabs/PlayButton.tsx:13](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/PlayButton.tsx#L13) - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`props` - - - -[`PlayButtonProps`](#playbuttonprops) - -
    - -#### Returns - -`Element` diff --git a/docs/src/lib/modules-lib/tabs/WebglCanvas.md b/docs/src/lib/modules-lib/tabs/WebglCanvas.md deleted file mode 100644 index 6bd84321d8..0000000000 --- a/docs/src/lib/modules-lib/tabs/WebglCanvas.md +++ /dev/null @@ -1,20 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/WebglCanvas - -# tabs/WebglCanvas - -## Variables - -### WebGLCanvas - -```ts -const WebGLCanvas: ForwardRefExoticComponent & RefAttributes>; -``` - -Defined in: [tabs/WebglCanvas.tsx:14](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/WebglCanvas.tsx#L14) - -Canvas component used by the curve and rune modules. Standardizes the -appearances of canvases for both modules. diff --git a/docs/src/lib/modules-lib/tabs/css_constants.md b/docs/src/lib/modules-lib/tabs/css_constants.md deleted file mode 100644 index af97bc46b6..0000000000 --- a/docs/src/lib/modules-lib/tabs/css_constants.md +++ /dev/null @@ -1,117 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/css\_constants - -# tabs/css\_constants - -## Variables - -### ACE\_GUTTER\_BACKGROUND\_COLOR - -```ts -const ACE_GUTTER_BACKGROUND_COLOR: string = '#34495E'; -``` - -Defined in: [tabs/css\_constants.ts:20](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L20) - -*** - -### ACE\_GUTTER\_TEXT\_COLOR - -```ts -const ACE_GUTTER_TEXT_COLOR: string = '#8091A0'; -``` - -Defined in: [tabs/css\_constants.ts:19](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L19) - -*** - -### BP\_CARD\_BORDER\_RADIUS - -```ts -const BP_CARD_BORDER_RADIUS: string = '2px'; -``` - -Defined in: [tabs/css\_constants.ts:12](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L12) - -*** - -### BP\_ICON\_COLOR - -```ts -const BP_ICON_COLOR: string = '#A7B6C2'; -``` - -Defined in: [tabs/css\_constants.ts:16](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L16) - -*** - -### BP\_TAB\_BUTTON\_MARGIN - -```ts -const BP_TAB_BUTTON_MARGIN: string = '20px'; -``` - -Defined in: [tabs/css\_constants.ts:10](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L10) - -*** - -### BP\_TAB\_PANEL\_MARGIN - -```ts -const BP_TAB_PANEL_MARGIN: string = '20px'; -``` - -Defined in: [tabs/css\_constants.ts:11](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L11) - -*** - -### BP\_TEXT\_COLOR - -```ts -const BP_TEXT_COLOR: string = '#FFFFFF'; -``` - -Defined in: [tabs/css\_constants.ts:15](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L15) - -*** - -### BP\_TEXT\_MARGIN - -```ts -const BP_TEXT_MARGIN: string = '10px'; -``` - -Defined in: [tabs/css\_constants.ts:13](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L13) - -*** - -### CANVAS\_MAX\_WIDTH - -```ts -const CANVAS_MAX_WIDTH: string = 'max(70vh, 30vw)'; -``` - -Defined in: [tabs/css\_constants.ts:23](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L23) - -*** - -### SA\_TAB\_BUTTON\_WIDTH - -```ts -const SA_TAB_BUTTON_WIDTH: string = '40px'; -``` - -Defined in: [tabs/css\_constants.ts:6](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L6) - -*** - -### SA\_TAB\_ICON\_SIZE - -```ts -const SA_TAB_ICON_SIZE: number = IconSize.LARGE; -``` - -Defined in: [tabs/css\_constants.ts:7](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/css_constants.ts#L7) diff --git a/docs/src/lib/modules-lib/tabs/utils.md b/docs/src/lib/modules-lib/tabs/utils.md deleted file mode 100644 index 57a563ccaf..0000000000 --- a/docs/src/lib/modules-lib/tabs/utils.md +++ /dev/null @@ -1,139 +0,0 @@ -[**Modules Common Library**](../README.md) - -*** - -[Modules Common Library](../README.md) / tabs/utils - -# tabs/utils - -## Functions - -### defineTab() - -```ts -function defineTab(tab): ModuleSideContent; -``` - -Defined in: [tabs/utils.ts:17](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/utils.ts#L17) - -Helper for typing tabs - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`tab` - - - -[`ModuleSideContent`](../types.md#modulesidecontent) - -
    - -#### Returns - -[`ModuleSideContent`](../types.md#modulesidecontent) - -*** - -### getModuleState() - -```ts -function getModuleState(debuggerContext, name): T; -``` - -Defined in: [tabs/utils.ts:10](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/tabs/utils.ts#L10) - -Helper function for extracting the state object for your bundle - -#### Type Parameters - - - - - - - - - - - - - - -
    Type ParameterDescription
    - -`T` - - - -The type of your bundle's state object - -
    - -#### Parameters - - - - - - - - - - - - - - - - - - - - - -
    ParameterTypeDescription
    - -`debuggerContext` - - - -[`DebuggerContext`](../types.md#debuggercontext) - - - -DebuggerContext as returned by the frontend - -
    - -`name` - - - -`string` - - - -Name of your bundle - -
    - -#### Returns - -`T` - -The state object of your bundle diff --git a/docs/src/lib/modules-lib/type_map.md b/docs/src/lib/modules-lib/type_map.md deleted file mode 100644 index b4afc903d7..0000000000 --- a/docs/src/lib/modules-lib/type_map.md +++ /dev/null @@ -1,384 +0,0 @@ -[**Modules Common Library**](README.md) - -*** - -[Modules Common Library](README.md) / type\_map - -# type\_map - -## Functions - -### createTypeMap() - -```ts -function createTypeMap(): { - classDeclaration: (name) => (_target) => void; - functionDeclaration: (paramTypes, returnType) => (_target, propertyKey, _descriptor) => void; - type_map: Record; - typeDeclaration: (type, declaration) => (target) => void; - variableDeclaration: (type) => (_target, propertyKey) => void; -}; -``` - -Defined in: [type\_map.ts:7](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/type_map.ts#L7) - -Utility function for creating type maps - -#### Returns - -```ts -{ - classDeclaration: (name) => (_target) => void; - functionDeclaration: (paramTypes, returnType) => (_target, propertyKey, _descriptor) => void; - type_map: Record; - typeDeclaration: (type, declaration) => (target) => void; - variableDeclaration: (type) => (_target, propertyKey) => void; -} -``` - -A reference to a type map alongside decorators that are -used to populate it - -##### classDeclaration() - -```ts -classDeclaration: (name) => (_target) => void; -``` - -###### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`name` - - - -`string` - -
    - -###### Returns - -```ts -(_target): void; -``` - -###### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`_target` - - - -`any` - -
    - -###### Returns - -`void` - -##### functionDeclaration() - -```ts -functionDeclaration: (paramTypes, returnType) => (_target, propertyKey, _descriptor) => void; -``` - -###### Parameters - - - - - - - - - - - - - - - - - - -
    ParameterType
    - -`paramTypes` - - - -`string` - -
    - -`returnType` - - - -`string` - -
    - -###### Returns - -```ts -( - _target, - propertyKey, - _descriptor): void; -``` - -###### Parameters - - - - - - - - - - - - - - - - - - - - - - -
    ParameterType
    - -`_target` - - - -`any` - -
    - -`propertyKey` - - - -`string` - -
    - -`_descriptor` - - - -`PropertyDescriptor` - -
    - -###### Returns - -`void` - -##### type\_map - -```ts -type_map: Record; -``` - -##### typeDeclaration() - -```ts -typeDeclaration: (type, declaration) => (target) => void; -``` - -###### Parameters - - - - - - - - - - - - - - - - - - - - - -
    ParameterTypeDefault value
    - -`type` - - - -`string` - - - -`undefined` - -
    - -`declaration` - - - -`null` - - - -`null` - -
    - -###### Returns - -```ts -(target): void; -``` - -###### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`target` - - - -`any` - -
    - -###### Returns - -`void` - -##### variableDeclaration() - -```ts -variableDeclaration: (type) => (_target, propertyKey) => void; -``` - -###### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`type` - - - -`string` - -
    - -###### Returns - -```ts -(_target, propertyKey): void; -``` - -###### Parameters - - - - - - - - - - - - - - - - - - -
    ParameterType
    - -`_target` - - - -`any` - -
    - -`propertyKey` - - - -`string` - -
    - -###### Returns - -`void` diff --git a/docs/src/lib/modules-lib/types.md b/docs/src/lib/modules-lib/types.md deleted file mode 100644 index ef49c647c1..0000000000 --- a/docs/src/lib/modules-lib/types.md +++ /dev/null @@ -1,593 +0,0 @@ -[**Modules Common Library**](README.md) - -*** - -[Modules Common Library](README.md) / types - -# types - -## Classes - -### `abstract` glAnimation - -Defined in: [types/index.ts:8](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L8) - -Represents an animation drawn using WebGL - -#### Constructors - -##### Constructor - -```ts -new glAnimation(duration, fps): glAnimation; -``` - -Defined in: [types/index.ts:9](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L9) - -###### Parameters - - - - - - - - - - - - - - - - - - - - - -
    ParameterTypeDescription
    - -`duration` - - - -`number` - - - -Duration of the animation in seconds - -
    - -`fps` - - - -`number` - - - -Framerate in frames per second - -
    - -###### Returns - -[`glAnimation`](#glanimation) - -#### Properties - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PropertyModifierTypeDescriptionDefined in
    - - `duration` - - - -`readonly` - - - -`number` - - - -Duration of the animation in seconds - - - -[types/index.ts:13](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L13) - -
    - - `fps` - - - -`readonly` - - - -`number` - - - -Framerate in frames per second - - - -[types/index.ts:17](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L17) - -
    - -#### Methods - -##### getFrame() - -```ts -abstract getFrame(timestamp): AnimFrame; -``` - -Defined in: [types/index.ts:20](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L20) - -###### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`timestamp` - - - -`number` - -
    - -###### Returns - -[`AnimFrame`](#animframe) - -##### isAnimation() - -```ts -static isAnimation(obj): obj is glAnimation; -``` - -Defined in: [types/index.ts:22](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L22) - -###### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`obj` - - - -`any` - -
    - -###### Returns - -`obj is glAnimation` - -## Interfaces - -### AnimFrame - -Defined in: [types/index.ts:24](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L24) - -#### Properties - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `draw` - - - -(`canvas`) => `void` - - - -[types/index.ts:25](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L25) - -
    - -*** - -### ReplResult - -Defined in: [types/index.ts:48](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L48) - -Interface to represent objects that require a string representation in the -REPL - -#### Properties - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `toReplString` - - - -() => `string` - - - -[types/index.ts:49](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L49) - -
    - -## Type Aliases - -### DebuggerContext - -```ts -type DebuggerContext = { - code: string; - context: Context; - lastDebuggerResult: any; - result: any; - workspaceLocation?: any; -}; -``` - -Defined in: [types/index.ts:34](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L34) - -DebuggerContext type used by frontend to assist typing information - -#### Properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PropertyTypeDefined in
    - - `code` - - - -`string` - - - -[types/index.ts:37](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L37) - -
    - - `context` - - - -`Context` - - - -[types/index.ts:38](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L38) - -
    - - `lastDebuggerResult` - - - -`any` - - - -[types/index.ts:36](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L36) - -
    - - `result` - - - -`any` - - - -[types/index.ts:35](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L35) - -
    - - `workspaceLocation?` - - - -`any` - - - -[types/index.ts:39](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L39) - -
    - -*** - -### DeepPartial\ - -```ts -type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T; -``` - -Defined in: [types/index.ts:27](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L27) - -#### Type Parameters - - - - - - - - - - - - -
    Type Parameter
    - -`T` - -
    - -*** - -### ModuleContexts - -```ts -type ModuleContexts = Context["moduleContexts"]; -``` - -Defined in: [types/index.ts:42](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L42) - -*** - -### ModuleSideContent - -```ts -type ModuleSideContent = { - body: (context) => JSX.Element; - iconName: IconName; - label: string; - toSpawn?: (context) => boolean; -}; -``` - -Defined in: [types/index.ts:54](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L54) - -#### Properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PropertyTypeDescriptionDefined in
    - - `body` - - - -(`context`) => `JSX.Element` - - - -This function will be called to render the module tab in the side contents -on Source Academy frontend. - - - -[types/index.ts:74](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L74) - -
    - - `iconName` - - - -`IconName` - - - -BlueprintJS IconName element's name, used to render the icon which will be -displayed in the side contents panel. - -**See** - -https://blueprintjs.com/docs/#icons - - - -[types/index.ts:60](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L60) - -
    - - `label` - - - -`string` - - - -The Tab's icon tooltip in the side contents on Source Academy frontend. - - - -[types/index.ts:64](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L64) - -
    - - `toSpawn?` - - - -(`context`) => `boolean` - - - -This function will be called to determine if the component will be -rendered - - - -[types/index.ts:69](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L69) - -
    - -*** - -### ModuleTab - -```ts -type ModuleTab = FC<{ - context: DebuggerContext; -}>; -``` - -Defined in: [types/index.ts:52](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/types/index.ts#L52) diff --git a/docs/src/lib/modules-lib/utilities.md b/docs/src/lib/modules-lib/utilities.md deleted file mode 100644 index 2ffe33ea51..0000000000 --- a/docs/src/lib/modules-lib/utilities.md +++ /dev/null @@ -1,155 +0,0 @@ -[**Modules Common Library**](README.md) - -*** - -[Modules Common Library](README.md) / utilities - -# utilities - -## Functions - -### degreesToRadians() - -```ts -function degreesToRadians(degrees): number; -``` - -Defined in: [utilities.ts:4](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/utilities.ts#L4) - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`degrees` - - - -`number` - -
    - -#### Returns - -`number` - -*** - -### hexToColor() - -```ts -function hexToColor(hex): [number, number, number]; -``` - -Defined in: [utilities.ts:8](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/utilities.ts#L8) - -#### Parameters - - - - - - - - - - - - - - -
    ParameterType
    - -`hex` - - - -`string` - -
    - -#### Returns - -\[`number`, `number`, `number`\] - -*** - -### mockDebuggerContext() - -```ts -function mockDebuggerContext(state, module): DebuggerContext; -``` - -Defined in: [utilities.ts:20](https://github.com/source-academy/modules/blob/4a6cfc213df50fe6ef11b98adee1482718d1c1eb/lib/modules-lib/src/utilities.ts#L20) - -#### Type Parameters - - - - - - - - - - - - -
    Type Parameter
    - -`T` - -
    - -#### Parameters - - - - - - - - - - - - - - - - - - -
    ParameterType
    - -`state` - - - -`T` - -
    - -`module` - - - -`string` - -
    - -#### Returns - -[`DebuggerContext`](types.md#debuggercontext) diff --git a/docs/src/modules/2-bundle/2-creating/2-creating.md b/docs/src/modules/2-bundle/2-creating/2-creating.md index c0e40bb51d..412d2b268b 100644 --- a/docs/src/modules/2-bundle/2-creating/2-creating.md +++ b/docs/src/modules/2-bundle/2-creating/2-creating.md @@ -29,8 +29,13 @@ What is the name of your new module? (eg. binary_tree) new_bundle ``` -This will create a new folder `src/bundles/new_bundle` with all the necessary files for creating your bundle: +The name of your bundle is what Source users will import from to actually use your bundle: +```ts +import { function } from 'new_bundle'; +``` + +The command should have creates a new folder `src/bundles/new_bundle` with all the necessary files for creating your bundle: -![](image.png) +![](./new_bundle.png) From there you can edit your bundle as necessary diff --git a/lib/modules-lib/package.json b/lib/modules-lib/package.json index acbf0a3e03..5faf3b40dd 100644 --- a/lib/modules-lib/package.json +++ b/lib/modules-lib/package.json @@ -8,6 +8,7 @@ "@types/react-dom": "^18.3.1", "eslint": "^9.29.0", "typedoc": "^0.28.4", + "typedoc-plugin-frontmatter": "^1.3.0", "typedoc-plugin-markdown": "^4.7.0", "typedoc-plugin-rename-defaults": "^0.7.3", "typescript": "^5.8.2", @@ -15,6 +16,7 @@ }, "exports": { "./*": "./dist/*.js", + "./tabs": "./dist/tabs/index.js", "./types": "./dist/types/index.js" }, "dependencies": { diff --git a/lib/modules-lib/src/tabs/AnimationCanvas.tsx b/lib/modules-lib/src/tabs/AnimationCanvas.tsx index e3aa416d8e..df970c1cfa 100644 --- a/lib/modules-lib/src/tabs/AnimationCanvas.tsx +++ b/lib/modules-lib/src/tabs/AnimationCanvas.tsx @@ -5,7 +5,7 @@ import type { glAnimation } from '../types'; import AutoLoopSwitch from './AutoLoopSwitch'; import ButtonComponent from './ButtonComponent'; import PlayButton from './PlayButton'; -import WebGLCanvas from './WebglCanvas'; +import WebGLCanvas from './WebGLCanvas'; import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from './css_constants'; export type AnimCanvasProps = { diff --git a/lib/modules-lib/src/tabs/MultiItemDisplay/image.png b/lib/modules-lib/src/tabs/MultiItemDisplay/image.png new file mode 100644 index 0000000000000000000000000000000000000000..9755095964d9080154984cbc86f3f8ceddbe8098 GIT binary patch literal 104008 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 nPnIS_>N0b`Z+KCy5cZ^VNKm!KQEY9;Rl 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 GLH`3Vd6zW+ literal 0 HcmV?d00001 diff --git a/lib/modules-lib/src/tabs/MultItemDisplay.tsx b/lib/modules-lib/src/tabs/MultiItemDisplay/index.tsx similarity index 99% rename from lib/modules-lib/src/tabs/MultItemDisplay.tsx rename to lib/modules-lib/src/tabs/MultiItemDisplay/index.tsx index 1ff67d1457..8ae87c5145 100644 --- a/lib/modules-lib/src/tabs/MultItemDisplay.tsx +++ b/lib/modules-lib/src/tabs/MultiItemDisplay/index.tsx @@ -9,6 +9,7 @@ export type MultiItemDisplayProps = { /** * React Component for displaying multiple items + * ![image](./image.png) */ const MultiItemDisplay = (props: MultiItemDisplayProps) => { // The actual index of the currently selected element diff --git a/lib/modules-lib/src/tabs/index.ts b/lib/modules-lib/src/tabs/index.ts new file mode 100644 index 0000000000..bae6b4ba06 --- /dev/null +++ b/lib/modules-lib/src/tabs/index.ts @@ -0,0 +1,14 @@ +/** + * Reusable React Components and styling utilities designed for use with SA Module Tabs + * @module Tabs + * @title Tabs Library + */ +export { default as AnimationCanvas } from './AnimationCanvas'; +export { default as AutoLoopSwitch } from './AutoLoopSwitch'; +export { default as ButtonComponent } from './ButtonComponent'; +export * from './css_constants'; +export { default as ModalDiv } from './ModalDiv'; +export { default as MultiItemDisplay } from './MultiItemDisplay'; +export { default as PlayButton } from './PlayButton'; +export * from './utils'; +export { default as WebGLCanvas } from './WebGLCanvas'; diff --git a/lib/modules-lib/src/type_map.ts b/lib/modules-lib/src/type_map.ts index 11fffdadd9..8ff6a8e41b 100644 --- a/lib/modules-lib/src/type_map.ts +++ b/lib/modules-lib/src/type_map.ts @@ -1,3 +1,11 @@ +/** + * Utilites for creating and manipulating Bundle Type Maps + * @module Type Maps + */ + +/** @hidden */ +type Decorator = (...args: any[]) => any; + /** * Utility function for creating type maps * @@ -15,13 +23,13 @@ export default function createTypeMap() { } } - function classDeclaration(name: string) { + function classDeclaration(name: string): Decorator { return (_target: any) => { registerType('prelude', `class ${name} {}`); }; } - function typeDeclaration(type: string, declaration = null) { + function typeDeclaration(type: string, declaration = null): Decorator { return (target: any) => { const typeAlias = `type ${target.name} = ${type}`; let variableDeclaration = `const ${target.name} = ${declaration === null ? type : declaration}`; @@ -45,7 +53,7 @@ export default function createTypeMap() { }; } - function functionDeclaration(paramTypes: string, returnType: string) { + function functionDeclaration(paramTypes: string, returnType: string): Decorator { return (_target: any, propertyKey: string, _descriptor: PropertyDescriptor) => { let returnValue = ''; switch (returnType) { @@ -69,7 +77,7 @@ export default function createTypeMap() { }; } - function variableDeclaration(type: string) { + function variableDeclaration(type: string): Decorator { return (_target: any, propertyKey: string) => { registerType(propertyKey, `const ${propertyKey}: ${type} = ${type}`); }; diff --git a/lib/modules-lib/typedoc.config.js b/lib/modules-lib/typedoc.config.js new file mode 100644 index 0000000000..444c5d8bc9 --- /dev/null +++ b/lib/modules-lib/typedoc.config.js @@ -0,0 +1,43 @@ +import { OptionDefaults } from 'typedoc'; + +/** + * @type {import('typedoc').TypeDocOptions & import('typedoc-plugin-markdown').PluginOptions & import('typedoc-plugin-frontmatter').PluginOptions} + */ +const typedocOptions = { + entryPoints: [ + 'src/tabs/index.ts', + 'src/types', + 'src/*.ts' + ], + name: 'Modules Common Library', + out: '../../docs/src/lib/modules-lib', + plugin: [ + 'typedoc-plugin-frontmatter', + 'typedoc-plugin-markdown', + 'typedoc-plugin-rename-defaults' + ], + readme: 'none', + router: 'module', + skipErrorChecking: true, + + // This lets us define some custom block tags + blockTags: [ + ...OptionDefaults.blockTags, + '@title' + ], + // that we can use as frontmatter tags + frontmatterCommentTags: ['title'], + + // Formatting Options + classPropertiesFormat: 'htmlTable', + entryFileName: 'index', + expandObjects: true, + hidePageHeader: true, + interfacePropertiesFormat: 'htmlTable', + mergeReadme: true, + parametersFormat: 'htmlTable', + typeAliasPropertiesFormat: 'htmlTable', + useCodeBlocks: true, +}; + +export default typedocOptions; diff --git a/yarn.lock b/yarn.lock index c186e35a25..8985bf8dca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3266,6 +3266,7 @@ __metadata: react: "npm:^18.3.1" react-dom: "npm:^18.3.1" typedoc: "npm:^0.28.4" + typedoc-plugin-frontmatter: "npm:^1.3.0" typedoc-plugin-markdown: "npm:^4.7.0" typedoc-plugin-rename-defaults: "npm:^0.7.3" typescript: "npm:^5.8.2" @@ -13334,6 +13335,17 @@ __metadata: languageName: node linkType: hard +"typedoc-plugin-frontmatter@npm:^1.3.0": + version: 1.3.0 + resolution: "typedoc-plugin-frontmatter@npm:1.3.0" + dependencies: + yaml: "npm:^2.7.0" + peerDependencies: + typedoc-plugin-markdown: ">=4.5.0" + checksum: 10c0/967ee5c38ab64b94489b9001744bbecc1cb6951bb07232caf2f4925f6a4dc0ac53305c4305e34ba10d6fddaab8f939642a2fbf10f1da884d6d6885d370c94cf3 + languageName: node + linkType: hard + "typedoc-plugin-markdown@npm:^4.7.0": version: 4.7.0 resolution: "typedoc-plugin-markdown@npm:4.7.0" @@ -14319,6 +14331,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.7.0": + version: 2.8.0 + resolution: "yaml@npm:2.8.0" + bin: + yaml: bin.mjs + checksum: 10c0/f6f7310cf7264a8107e72c1376f4de37389945d2fb4656f8060eca83f01d2d703f9d1b925dd8f39852a57034fafefde6225409ddd9f22aebfda16c6141b71858 + languageName: node + linkType: hard + "yaml@npm:^2.7.1": version: 2.7.1 resolution: "yaml@npm:2.7.1" From 7ea67dbc42562ff264801907851fe2d1fe2c6e5c Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 24 Jun 2025 23:30:41 +0800 Subject: [PATCH 080/112] Try to fix devserver tests failing --- README.md | 132 ++++++------------ devserver/vite.config.ts | 3 + docs/package.json | 4 +- docs/src/index.md | 18 +-- .../1-overview.md} | 3 + docs/src/lib/lintplugin/2-rules.md | 58 ++++++++ docs/src/lib/lintplugin/index.md | 3 + .../modules/1-getting-started/1-overview.md | 7 +- .../modules/2-bundle/1-overview/1-overview.md | 4 + docs/src/repotools/devserver/index.md | 0 lib/buildtools/src/templates/bundle.ts | 6 +- lib/{ => lintplugin}/.gitignore | 0 lib/lintplugin/src/rules/tabType.ts | 13 +- lib/modules-lib/package.json | 4 + .../src/__tests__/hextocolor.test.ts | 7 +- lib/modules-lib/src/tabs/ButtonComponent.tsx | 12 +- lib/modules-lib/src/tabs/PlayButton.tsx | 6 +- lib/modules-lib/src/tabs/WebglCanvas.tsx | 12 +- lib/modules-lib/src/tabs/index.ts | 14 +- lib/modules-lib/src/type_map.ts | 2 +- lib/modules-lib/src/utilities.ts | 37 +++-- lib/modules-lib/typedoc.config.js | 6 +- src/bundles/tsconfig.json | 1 + src/tabs/tsconfig.json | 7 +- yarn.config.cjs | 8 ++ yarn.lock | 1 + 26 files changed, 216 insertions(+), 152 deletions(-) rename docs/src/lib/{lintplugin.md => lintplugin/1-overview.md} (97%) create mode 100644 docs/src/lib/lintplugin/2-rules.md create mode 100644 docs/src/lib/lintplugin/index.md create mode 100644 docs/src/repotools/devserver/index.md rename lib/{ => lintplugin}/.gitignore (100%) diff --git a/README.md b/README.md index c59b2c208f..61208689b8 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,45 @@ -# Modules +# Source Academy Modules Repository -![License](https://img.shields.io/badge/License-Apache%202.0-brightgreen) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/source-academy/modules/github%20pages?label=Build) +![License](https://img.shields.io/badge/License-Apache%202.0-brightgreen) [![pages-build-deployment](https://github.com/source-academy/modules/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/source-academy/modules/actions/workflows/pages/pages-build-deployment) -This repository contains the default modules of the Source Academy and their documentation, deployed to the default module site at . +This repository contains the default modules of the Source Academy and their documentation, alongside all the libraries and tooling required for development. The [Source Academy](https://sourceacademy.org) and [Source Academy @ NUS](https://sourceacademy.nus.edu.sg) are configured to access the default module site when evaluating `import` directives. -[Documentation of Source Academy modules](https://source-academy.github.io/modules/documentation). - -## Information for Module Developers - -See the modules [wiki](https://github.com/source-academy/modules/wiki) for more details. - -### Terminology - -| **Term** | **Description** | -| ---------- | ------------------------------------------------------------------ | -| **Module** | A set of **one** bundle _with the same name_ and **some/no** tabs. | -| **Bundle** | The suite of functions that are provided by the module. | -| **Tab** | A user interface used by the module. | - -### Getting Started - -The following set of instructions explain how to clone and set up a copy of the `modules` code repository on your local development machine. Following the steps below will create a `modules` directory in your local development machine and install the necessary dependencies of the project. - -The recommended version of [Node.js](https://nodejs.org/en/) for local development is Node.js 20. You may use a Node.js version manager such as [nvm](https://github.com/creationix/nvm#installation) _(macOS/Linux)_ or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to switch Node.js versions between different projects. - -This project also uses [Yarn](https://yarnpkg.com/) as a package manager. You may install and enable Yarn by running the following command. -```bash -corepack enable -``` - -If the above does not work, you can manually install the Yarn package manager through [NPM](https://www.npmjs.com/) using the following command. - -```bash -npm install -g yarn -``` - -You may also require [Python](https://www.python.org/downloads/) to run build scripts and install project dependencies. We recommend either using Python 2.7 or Python 3.8-3.11. - -Clone the repository on your local development machine and navigate to it using your favourite command line or shell tool. - -```bash -git clone https://github.com/source-academy/modules.git -cd modules -``` - -Install all the dependencies of the project into `node_modules` in the root folder of your project directory. - -```bash -yarn install -``` - -If you encounter errors with esbuild dependencies like the following while building: - -```plaintext -Error: The package "@esbuild/darwin-arm64" could not be found, and is needed by esbuild. +## Quick Links +| Site | Link | +| ---- | ---- | +| Source Academy | https://sourceacademy.org | +| Default Modules Deployment | https://source-academy.github.io/modules | +| Default Modules Documentation | https://source-academy.github.io/modules/documentation | +| Developer Wiki | https://github.com/source-academy/modules/wiki | +| `js-slang` | https://github.com/source-academy/js-slang | +| Frontend | https://github.com/source-academy/frontend | + +If you are looking for the documentation for the default modules, they can be found [here](https://source-academy.github.io/modules/documentation). + +If you are a developer working with this repository, the developer documentation can be found [here](https://github.com/source-academy/modules/wiki) + +## Repository Structure +The repository is designed as a monorepo, managed using Yarn workspaces. + +```txt +. +├── .github // Configuration for issue templates and workflows +├── .husky // Configuration for code that runs on Git Hooks +├── .vscode // Configuration for VSCode integration +├── build // Output directory for compiled assets +├── devserver // Modules Development Server +├── docs // Developer Documentation and Server +├── lib +│ ├── buildtools // Scripts for compiling bundles and tabs +│ ├── lintplugin // ESLint Plugin for SA Modules +│ └── modules-lib // Common utilities and React components for SA Modules +├── src +│ ├── bundles // Source code for Bundles +│ ├── tabs // Source code for Tabs +│ └── java // Assets for Source Java +├── eslint.config.js // ESLint configuration for the entire repository +├── vitest.config.js // Vitest configuration for the entire repository +└── yarn.config.cjs // Yarn constraints configuration ``` - -You will need to delete the `node_modules` folder and rerun `yarn install` to fix the issue. - -### Serve Modules - -The following set of instructions explain how to transpile and serve the modules from your local development machine's code repository. Following the steps below will transpile all the modules in your project directory into JavaScript files located in the `build` folder. Thereafter, you will serve all the contents of the build folder in a server on your local development machine. - -To transpile the modules' files from `src` into JavaScript files in `build`, run the following command. - -```bash -yarn run build:all -``` - -To start the server that serves all the contents of the `build` folder in the root directory of the project, run the following command. By default, running this command serves the contents of the `build` folder on . - -```bash -yarn run serve -``` - -### Development with Source Academy `frontend` - -The following set of instructions explains how to use a local copy of the Source Academy [frontend](https://github.com/source-academy/frontend) with a local copy of the modules code repository. Following the steps below will configure the environment of the Source Academy frontend to use your locally served modules instead of the publicly available ones. Doing this will allow you to develop and modify modules without affecting the currently publicly available ones. - -You will need to already have a local instance of Source Academy frontend set up. If you do not, you can follow the instructions [here](https://github.com/source-academy/frontend#getting-started) to setup an instance of Source Academy frontend on your local development machine. - -Ensure that the environment variable `REACT_APP_MODULE_BACKEND_URL` in the `.env` file of the Source Academy frontend is configured to the URL of the module site that you are trying to retrieve modules from. At the same time, make sure that the server hosting the modules site is running. By default, the local server started by running `yarn run serve` is on . The default modules are implemented in the repository and deployed to the modules site . - -Upon starting the local instance of Source Academy frontend, the Source Academy will connect to the configured modules site. - -### Development Guide - -Please refer to the Modules Development Guide located in the modules wiki [here](https://github.com/source-academy/modules/wiki/Development-Guide) for more information regarding how to create your own module including its own bundle and tab. - -## License - -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - -All sources in this repository are licensed under the [Apache License Version 2][apache2]. - -[apache2]: https://www.apache.org/licenses/LICENSE-2.0.txt diff --git a/devserver/vite.config.ts b/devserver/vite.config.ts index da60c4136a..5dc1145972 100644 --- a/devserver/vite.config.ts +++ b/devserver/vite.config.ts @@ -69,6 +69,9 @@ export default defineConfig(({ mode }) => { "vite-plugin-node-polyfills/shims/buffer", "vite-plugin-node-polyfills/shims/global", "vite-plugin-node-polyfills/shims/process", + ], + exclude: [ + '../build/tabs/*.js' ] }, test: { diff --git a/docs/package.json b/docs/package.json index 225679c979..3db97c423a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,12 +5,14 @@ "version": "1.0.0", "type": "module", "devDependencies": { + "@sourceacademy/modules-lib": "workspace:^", "vitepress": "^1.6.3", "vitepress-sidebar": "^1.31.1" }, "scripts": { "build": "vitepress build .", "dev": "vitepress dev .", - "preview": "vitepress preview ." + "preview": "vitepress preview .", + "prepare": "yarn workspaces foreach -A --include \"@sourceacademy/modules-lib\" run docs" } } diff --git a/docs/src/index.md b/docs/src/index.md index 95662159d2..281b566e71 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -3,9 +3,8 @@ layout: home hero: - name: "Modules Developer Documentation" - text: "Developer documentation for the Source Academy modules repository" - tagline: My great project tagline + name: Modules Developer Documentation + tagline: Developer documentation for the Source Academy modules repository actions: - theme: brand text: Get Started @@ -17,15 +16,18 @@ hero: link: /api-examples features: - - title: Creating a bundle or tab + - title: Your first bundle or tab details: Instructions for creating a bundle or tab - link: /modules/getting-started/start + link: /modules/1-getting-started/1-overview - - title: Common Modules Library - details: idk + - title: Common Modules Libraries + details: Libraries intended to be shared between SA Modules link: /lib - title: Build Tools - details: Details behind the tooling that compiles Source Academy modules + details: Details behind the tooling that compiles SA Modules link: /buildtools + + - title: Repository Tools + details: Details for the tools used to aid in developing SA Modules --- diff --git a/docs/src/lib/lintplugin.md b/docs/src/lib/lintplugin/1-overview.md similarity index 97% rename from docs/src/lib/lintplugin.md rename to docs/src/lib/lintplugin/1-overview.md index 6590490ea1..064a3068ca 100644 --- a/docs/src/lib/lintplugin.md +++ b/docs/src/lib/lintplugin/1-overview.md @@ -1,3 +1,6 @@ +--- +title: Overview +--- # Modules ESLint Plugin `@sourceacademy/modules-lintplugin` provides the ability for developers to write their own custom linting rules for SA Modules. diff --git a/docs/src/lib/lintplugin/2-rules.md b/docs/src/lib/lintplugin/2-rules.md new file mode 100644 index 0000000000..13c9ac1177 --- /dev/null +++ b/docs/src/lib/lintplugin/2-rules.md @@ -0,0 +1,58 @@ +# Rules Reference + +## `tab-type` + +Enforces that types have a default export using the `defineTab` helper. + +Examples of **incorrect** code for this rule: +```tsx +export default 0; + +export default { + body: () => , + toSpawn: () => false, + iconName: 'icon', + label: 'tab' +} +``` +Examples of **correct** code for this rule: +```tsx +import { defineTab } from '@sourceacademy/modules-lib/tabs'; + +export default defineTab{ + body: () => , + toSpawn: () => false, + iconName: 'icon', + label: 'tab' +}); +``` + +The rule considers the code below **correct** even if the import is aliased: +```tsx +import { defineTab as tabHelper } from '@sourceacademy/modules-lib/tabs'; + +export default tabHelper({ + body: () => , + toSpawn: () => false, + iconName: 'icon', + label: 'tab' +}); +``` + +## Options +This rule accepts a configuration array with two elements: +- The first option represents the expected import source. This is by default `@sourceacademy/modules-lib/tabs` but can be changed to whatever is in use +- The second option is the name of the imported helper. This is by default `defineTab`. + +Examples of **correct** code using these options: +```tsx +/* eslint @sourceacademy/tab-type: ['error', '@sourceacademy/modules-lib/tabs/utils', 'tabHelper'] */ +import { tabHelper } from '@sourceacademy/modules-lib/tabs/utils'; + +export default tabHelper({ + body: () => , + toSpawn: () => false, + iconName: 'icon', + label: 'tab' +}); +``` diff --git a/docs/src/lib/lintplugin/index.md b/docs/src/lib/lintplugin/index.md new file mode 100644 index 0000000000..8fe5cbd856 --- /dev/null +++ b/docs/src/lib/lintplugin/index.md @@ -0,0 +1,3 @@ +--- +title: ESLint Plugin +--- diff --git a/docs/src/modules/1-getting-started/1-overview.md b/docs/src/modules/1-getting-started/1-overview.md index bb3e5a803e..a7d5596e12 100644 --- a/docs/src/modules/1-getting-started/1-overview.md +++ b/docs/src/modules/1-getting-started/1-overview.md @@ -1,5 +1,8 @@ # Modules Overview -This page contains information regarding the overview of the Source Modules system. +This page contains information regarding the overview of the Source Modules system. If you want to skip this overview, navigate to the bottom of the page +where the **next page** button is located. + +## Terminology The module system imitates ESM Javascript, allowing the use of `import` statements to import external code into Source programs: @@ -9,8 +12,6 @@ import { draw_connected } from 'curve'; > [!NOTE] > If you're familiar with the Javascript ecosystem, you may know that there are other module formats that are in common use. Source Modules are written in the ECMAScript _(ESM)_ format (i.e using `import` and `export`). However, there are limitations. For example, top-level await is not supported. -## Terminology - These are the 3 main terms the project will be using to refer to the individual components of the Source Modules system. Please follow the set of definitions below to avoid any inconsistencies. | **Term** | **Description** | **Links** | | ---------- | ------------------------------------------------------------------ | ---------------- | diff --git a/docs/src/modules/2-bundle/1-overview/1-overview.md b/docs/src/modules/2-bundle/1-overview/1-overview.md index c7f306df72..5d3c797f7b 100644 --- a/docs/src/modules/2-bundle/1-overview/1-overview.md +++ b/docs/src/modules/2-bundle/1-overview/1-overview.md @@ -134,6 +134,10 @@ This file controls the behaviour of Typescript. By default, it should look like ``` In general, there should not be a need for you to modify this file. A full explanation on how to use `tsconfig.json` can be found [here](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). Note that the `typedocOptions` field is a custom field used by `typedoc` for its configuration. Refer to [here](https://typedoc.org/documents/Options.Configuration.html#compileroptions) for more information. +> [!WARNING] +> You should not remove or modify the `typedocOptions` section from your `tsconfig.json` unless you provide the name of your bundle to Typedoc via its other configuration methods. Generating documentation for your bundle +> requires that the name of your bundle be set correctly. + ::: details Missing types for dependencies and overriding `tsconfig.json` Sometimes, your bundle might depend on packages that have published their types differently. For example, the `communication` bundle requires `mqtt`: ```ts diff --git a/docs/src/repotools/devserver/index.md b/docs/src/repotools/devserver/index.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/buildtools/src/templates/bundle.ts b/lib/buildtools/src/templates/bundle.ts index 9864c1c267..ba12de7346 100644 --- a/lib/buildtools/src/templates/bundle.ts +++ b/lib/buildtools/src/templates/bundle.ts @@ -59,9 +59,9 @@ export async function addNew(bundlesDir: string, rl: Interface) { }; await Promise.all([ - fs.writeFile(`${bundleDestination}/package.json`, JSON.stringify(packageJson, null, 2)), - fs.writeFile(`${bundleDestination}/manifest.json`, JSON.stringify(bundleManifest, null, 2)), - fs.writeFile(`${bundleDestination}/tsconfig.json`, JSON.stringify(sampleTsconfig, null, 2)), + fs.writeFile(`${bundleDestination}/package.json`, JSON.stringify(packageJson, null, 2) + '\n'), + fs.writeFile(`${bundleDestination}/manifest.json`, JSON.stringify(bundleManifest, null, 2) + '\n'), + fs.writeFile(`${bundleDestination}/tsconfig.json`, `// ${moduleName} tsconfig\n${JSON.stringify(sampleTsconfig, null, 2)}\n`), ]); success(`Bundle for module ${moduleName} created at ${bundleDestination}.`); diff --git a/lib/.gitignore b/lib/lintplugin/.gitignore similarity index 100% rename from lib/.gitignore rename to lib/lintplugin/.gitignore diff --git a/lib/lintplugin/src/rules/tabType.ts b/lib/lintplugin/src/rules/tabType.ts index 3a3a1f795a..e55e3daeff 100644 --- a/lib/lintplugin/src/rules/tabType.ts +++ b/lib/lintplugin/src/rules/tabType.ts @@ -7,13 +7,22 @@ const tabType = { docs: { description: 'Enforces typing for Source Academy tabs' }, - schema: [{ type: 'string' }, { type: 'string' }], + schema: [ + { + description: 'Expected Import Source', + type: 'string' + }, + { + description: 'Name of the import', + type: 'string' + } + ], messages: { noExport: 'Your tab should export an object using the defineTab helper', useHelper: 'Use the defineTab helper from {{ source }}' }, defaultOptions: [ - '@sourceacademy/modules-lib/tabs/utils', + '@sourceacademy/modules-lib/tabs', 'defineTab' ], }, diff --git a/lib/modules-lib/package.json b/lib/modules-lib/package.json index 5faf3b40dd..50e58d690e 100644 --- a/lib/modules-lib/package.json +++ b/lib/modules-lib/package.json @@ -26,8 +26,12 @@ "react": "^18.3.1", "react-dom": "^18.3.1" }, + "scripts-info": { + "docs": "Build the documentation for documentation server using Typedoc" + }, "scripts": { "build": "tsc --project ./tsconfig.prod.json", + "docs": "typedoc", "lint": "eslint src", "prepare": "yarn build", "tsc": "tsc --project ./tsconfig.json", diff --git a/lib/modules-lib/src/__tests__/hextocolor.test.ts b/lib/modules-lib/src/__tests__/hextocolor.test.ts index 93fbb69466..9351cadbcc 100644 --- a/lib/modules-lib/src/__tests__/hextocolor.test.ts +++ b/lib/modules-lib/src/__tests__/hextocolor.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from 'vitest'; +import { describe, expect, it, test } from 'vitest'; import { hexToColor } from '../utilities'; describe('Test hexToColor', () => { @@ -7,7 +7,6 @@ describe('Test hexToColor', () => { ['ffffff', [1, 1, 1]], ['0088ff', [0, 0.53, 1]], ['#000000', [0, 0, 0]], - ['#GGGGGG', [0, 0, 0]], ['888888', [0.53, 0.53, 0.53]] ])('Testing %s', (c, expected) => { const result = hexToColor(c); @@ -15,4 +14,8 @@ describe('Test hexToColor', () => { expect(result[i]).toBeCloseTo(expected[i]); } }); + + it('throws an error when an invalid hex string is passed', () => { + expect(() => hexToColor('GGGGGG')).toThrowErrorMatchingInlineSnapshot('[Error: Invalid color hex string: GGGGGG]'); + }); }); diff --git a/lib/modules-lib/src/tabs/ButtonComponent.tsx b/lib/modules-lib/src/tabs/ButtonComponent.tsx index a74230b146..3e21211fc5 100644 --- a/lib/modules-lib/src/tabs/ButtonComponent.tsx +++ b/lib/modules-lib/src/tabs/ButtonComponent.tsx @@ -1,5 +1,4 @@ -import { AnchorButton, Button, Intent } from '@blueprintjs/core'; -import type { MouseEventHandler, ReactNode } from 'react'; +import { AnchorButton, type AnchorButtonProps, Button, type ButtonProps, Intent } from '@blueprintjs/core'; const defaultOptions = { className: '', @@ -9,11 +8,10 @@ const defaultOptions = { minimal: true }; -export type ButtonComponentProps = { - onClick?: MouseEventHandler, - disabled?: boolean, - children?: ReactNode, -}; +/** + * Refer to {@link https://blueprintjs.com/docs/#core/components/buttons.anchorbutton|this link} for more details + */ +export type ButtonComponentProps = AnchorButtonProps & ButtonProps; /** * Button Component that retains interactability even when disabled. Refer to diff --git a/lib/modules-lib/src/tabs/PlayButton.tsx b/lib/modules-lib/src/tabs/PlayButton.tsx index b78cd15943..3a0c74ae09 100644 --- a/lib/modules-lib/src/tabs/PlayButton.tsx +++ b/lib/modules-lib/src/tabs/PlayButton.tsx @@ -1,10 +1,10 @@ /* [Imports] */ -import { Icon, type ButtonProps, Tooltip } from '@blueprintjs/core'; +import { Icon, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import ButtonComponent from './ButtonComponent'; +import ButtonComponent, { type ButtonComponentProps } from './ButtonComponent'; /* [Exports] */ -export type PlayButtonProps = ButtonProps & { +export type PlayButtonProps = ButtonComponentProps & { isPlaying: boolean, // onClickCallback: () => void, }; diff --git a/lib/modules-lib/src/tabs/WebglCanvas.tsx b/lib/modules-lib/src/tabs/WebglCanvas.tsx index 072c74caf5..0a0730e5c9 100644 --- a/lib/modules-lib/src/tabs/WebglCanvas.tsx +++ b/lib/modules-lib/src/tabs/WebglCanvas.tsx @@ -1,19 +1,21 @@ -import React from 'react'; +import { forwardRef, type CanvasHTMLAttributes, type DetailedHTMLProps } from 'react'; import { CANVAS_MAX_WIDTH } from './css_constants'; const defaultStyle = { width: '100%', maxWidth: CANVAS_MAX_WIDTH, aspectRatio: '1' -} as React.CSSProperties; +}; + +export type WebGLCanvasProps = DetailedHTMLProps, HTMLCanvasElement>; /** * Canvas component used by the curve and rune modules. Standardizes the * appearances of canvases for both modules. */ -const WebGLCanvas = React.forwardRef( - (props: any, ref) => { - const style +const WebGLCanvas = forwardRef( + (props, ref) => { + const style: Partial = props.style !== undefined ? { ...defaultStyle, diff --git a/lib/modules-lib/src/tabs/index.ts b/lib/modules-lib/src/tabs/index.ts index bae6b4ba06..4a4a0c068c 100644 --- a/lib/modules-lib/src/tabs/index.ts +++ b/lib/modules-lib/src/tabs/index.ts @@ -3,12 +3,12 @@ * @module Tabs * @title Tabs Library */ -export { default as AnimationCanvas } from './AnimationCanvas'; -export { default as AutoLoopSwitch } from './AutoLoopSwitch'; -export { default as ButtonComponent } from './ButtonComponent'; +export { default as AnimationCanvas, type AnimCanvasProps } from './AnimationCanvas'; +export { default as AutoLoopSwitch, type AutoLoopSwitchProps } from './AutoLoopSwitch'; +export { default as ButtonComponent, type ButtonComponentProps } from './ButtonComponent'; export * from './css_constants'; -export { default as ModalDiv } from './ModalDiv'; -export { default as MultiItemDisplay } from './MultiItemDisplay'; -export { default as PlayButton } from './PlayButton'; +export { default as ModalDiv, type ModalProps } from './ModalDiv'; +export { default as MultiItemDisplay, type MultiItemDisplayProps } from './MultiItemDisplay'; +export { default as PlayButton, type PlayButtonProps } from './PlayButton'; export * from './utils'; -export { default as WebGLCanvas } from './WebGLCanvas'; +export { default as WebGLCanvas, type WebGLCanvasProps } from './WebGLCanvas'; diff --git a/lib/modules-lib/src/type_map.ts b/lib/modules-lib/src/type_map.ts index 8ff6a8e41b..651f813d69 100644 --- a/lib/modules-lib/src/type_map.ts +++ b/lib/modules-lib/src/type_map.ts @@ -4,7 +4,7 @@ */ /** @hidden */ -type Decorator = (...args: any[]) => any; +export type Decorator = (...args: any[]) => any; /** * Utility function for creating type maps diff --git a/lib/modules-lib/src/utilities.ts b/lib/modules-lib/src/utilities.ts index 8e4077a6ab..bace35799f 100644 --- a/lib/modules-lib/src/utilities.ts +++ b/lib/modules-lib/src/utilities.ts @@ -1,28 +1,37 @@ -import type { DebuggerContext } from './types'; +/** + * Common utility functions that can be used across bundles and tabs + * @module Utilities + * @title Utilities + */ -/* [Exports] */ +/** + * Converts an angle in degrees into radians + * @param degrees Angle in degrees + * @returns Angle in radians + */ export function degreesToRadians(degrees: number): number { return (degrees / 360) * (2 * Math.PI); } -export function hexToColor(hex: string): [number, number, number] { +/** + * Converts a color hex value string to its red, green and blue components, each normalized to + * between 0 and 1. The leading `#` is optional. + * @returns Tuple of three numbers representing the R, G and B components + */ +export function hexToColor(hex: string, func_name?: string): [number, number, number] { const regex = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/igu; const groups = regex.exec(hex); - if (groups == undefined) return [0, 0, 0]; + if (groups == undefined) { + if (func_name === undefined) { + throw new Error(`Invalid color hex string: ${hex}`); + } + throw new Error(`${func_name}: Invalid color hex string: ${hex}`); + }; + return [ parseInt(groups[1], 16) / 0xff, parseInt(groups[2], 16) / 0xff, parseInt(groups[3], 16) / 0xff ]; } - -export function mockDebuggerContext(state: T, module: string) { - return { - context: { - [module]: { - state - } - } - } as unknown as DebuggerContext; -} diff --git a/lib/modules-lib/typedoc.config.js b/lib/modules-lib/typedoc.config.js index 444c5d8bc9..d9c85a8d9a 100644 --- a/lib/modules-lib/typedoc.config.js +++ b/lib/modules-lib/typedoc.config.js @@ -1,7 +1,11 @@ import { OptionDefaults } from 'typedoc'; /** - * @type {import('typedoc').TypeDocOptions & import('typedoc-plugin-markdown').PluginOptions & import('typedoc-plugin-frontmatter').PluginOptions} + * @type { + * import('typedoc').TypeDocOptions & + * import('typedoc-plugin-markdown').PluginOptions & + * import('typedoc-plugin-frontmatter').PluginOptions + * } */ const typedocOptions = { entryPoints: [ diff --git a/src/bundles/tsconfig.json b/src/bundles/tsconfig.json index 9b91d69119..d854d09221 100644 --- a/src/bundles/tsconfig.json +++ b/src/bundles/tsconfig.json @@ -1,3 +1,4 @@ +// Bundles tsconfig { "compilerOptions": { "declaration": true, diff --git a/src/tabs/tsconfig.json b/src/tabs/tsconfig.json index 21bfdccc61..2c5e8d8751 100644 --- a/src/tabs/tsconfig.json +++ b/src/tabs/tsconfig.json @@ -1,8 +1,11 @@ +// Tabs tsconfig { "extends": "../tsconfig.json", "compilerOptions": { + // Tabs should never need to emit any kind of files "declaration": false, - "jsx": "react-jsx", - "noEmit": true + "noEmit": true, + + "jsx": "react-jsx" } } diff --git a/yarn.config.cjs b/yarn.config.cjs index 64a37ed98e..e8d6faa7e2 100644 --- a/yarn.config.cjs +++ b/yarn.config.cjs @@ -23,5 +23,13 @@ module.exports = defineConfig({ otherDep.update(workspaceDep.range); } } + + for (const dep of Yarn.dependencies()) { + // Dependencies that are from this workspace should use + // the correct version + if (dep.ident.startsWith('@sourceacademy')) { + dep.update('workspace:^'); + } + } } }); diff --git a/yarn.lock b/yarn.lock index 8985bf8dca..c64522e716 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3216,6 +3216,7 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/modules-devdocs@workspace:docs" dependencies: + "@sourceacademy/modules-lib": "workspace:^" vitepress: "npm:^1.6.3" vitepress-sidebar: "npm:^1.31.1" languageName: unknown From 412b6b4209ee82ac63ad3eeed2a0f9db023ac025 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 24 Jun 2025 23:41:13 +0800 Subject: [PATCH 081/112] Continue to fix broken tests --- lib/modules-lib/src/utilities.ts | 20 ++++++++++++++++++++ package.json | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/modules-lib/src/utilities.ts b/lib/modules-lib/src/utilities.ts index bace35799f..b817086484 100644 --- a/lib/modules-lib/src/utilities.ts +++ b/lib/modules-lib/src/utilities.ts @@ -4,6 +4,8 @@ * @title Utilities */ +import type { DebuggerContext } from './types'; + /** * Converts an angle in degrees into radians * @param degrees Angle in degrees @@ -35,3 +37,21 @@ export function hexToColor(hex: string, func_name?: string): [number, number, nu parseInt(groups[3], 16) / 0xff ]; } + +/** + * Returns a partial {@link DebuggerContext} object that contains a context which contains the mocked + * module state for the given module. + * Function intended for testing use only. + */ +export function mockDebuggerContext(moduleState: T, name: string) { + return { + context: { + moduleContexts: { + [name]: { + state: moduleState, + tabs: [] + } + } + } + } as unknown as DebuggerContext; +} diff --git a/package.json b/package.json index bb4b0682d1..46249fe9e5 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "test:devserver": "vitest --project \"Dev Server\"", "test:libs": "yarn workspaces foreach -ptW --from \"./lib/*\" run test", "test:modules": "yarn workspaces foreach -ptW --from \"./src/{bundles,tabs}/*\" run test", - "tsc:all": "yarn workspaces foreach -pt --all run tsc", + "tsc:all": "yarn workspaces foreach -pt --all --exclude \"@sourceacademy/modules\" run tsc", "tsc:devserver": "tsc --project ./devserver/tsconfig.json", "tsc:modules": "yarn workspaces foreach -ptW --from \"./src/{bundles,tabs}/*\" run tsc" }, From 6d1514535256fbd49c0b3d7620d2e8ad177ee368 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 24 Jun 2025 23:46:39 +0800 Subject: [PATCH 082/112] Add modules-lib as a dependency for devserver --- devserver/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/devserver/package.json b/devserver/package.json index f78da9b09e..91ff083d06 100644 --- a/devserver/package.json +++ b/devserver/package.json @@ -11,6 +11,7 @@ "dependencies": { "@blueprintjs/core": "^5.10.2", "@blueprintjs/icons": "^5.9.0", + "@sourceacademy/modules-lib": "workspace:^", "@vitejs/plugin-react": "^4.5.1", "ace-builds": "^1.25.1", "classnames": "^2.3.1", diff --git a/yarn.lock b/yarn.lock index c64522e716..ca8516b1e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3229,6 +3229,7 @@ __metadata: "@blueprintjs/core": "npm:^5.10.2" "@blueprintjs/icons": "npm:^5.9.0" "@commander-js/extra-typings": "npm:^13.0.0" + "@sourceacademy/modules-lib": "workspace:^" "@types/react": "npm:^18.3.1" "@types/react-dom": "npm:^18.3.1" "@vitejs/plugin-react": "npm:^4.5.1" From ec6c04329e51a10e657f3f6c50655dfb799a36da Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Tue, 24 Jun 2025 23:58:22 +0800 Subject: [PATCH 083/112] Continue fixing broken references --- src/tabs/Curve/src/index.tsx | 4 +--- src/tabs/Rune/src/index.tsx | 4 +--- src/tabs/Sound/index.tsx | 2 +- src/tabs/StereoSound/index.tsx | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/tabs/Curve/src/index.tsx b/src/tabs/Curve/src/index.tsx index a3129857d6..e61f3dccee 100644 --- a/src/tabs/Curve/src/index.tsx +++ b/src/tabs/Curve/src/index.tsx @@ -1,8 +1,6 @@ import { IconNames } from '@blueprintjs/icons'; import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; -import AnimationCanvas from '@sourceacademy/modules-lib/tabs/AnimationCanvas'; -import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; diff --git a/src/tabs/Rune/src/index.tsx b/src/tabs/Rune/src/index.tsx index 2d2e9677b6..6b5fa7a776 100644 --- a/src/tabs/Rune/src/index.tsx +++ b/src/tabs/Rune/src/index.tsx @@ -1,7 +1,5 @@ import { type RuneModuleState, isHollusionRune } from '@sourceacademy/bundle-rune/functions'; -import AnimationCanvas from '@sourceacademy/modules-lib/tabs/AnimationCanvas'; -import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; import { glAnimation, type ModuleTab } from '@sourceacademy/modules-lib/types'; import HollusionCanvas from './hollusion_canvas'; diff --git a/src/tabs/Sound/index.tsx b/src/tabs/Sound/index.tsx index 7b28a35483..e37d7e148a 100644 --- a/src/tabs/Sound/index.tsx +++ b/src/tabs/Sound/index.tsx @@ -1,5 +1,5 @@ import type { SoundModuleState } from '@sourceacademy/bundle-sound/types'; -import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; +import { MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; diff --git a/src/tabs/StereoSound/index.tsx b/src/tabs/StereoSound/index.tsx index 172594efdd..84e04645b7 100644 --- a/src/tabs/StereoSound/index.tsx +++ b/src/tabs/StereoSound/index.tsx @@ -1,5 +1,5 @@ import type { StereoSoundModuleState } from '@sourceacademy/bundle-stereo_sound/types'; -import MultiItemDisplay from '@sourceacademy/modules-lib/tabs/MultItemDisplay'; +import { MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; import type { ModuleTab } from '@sourceacademy/modules-lib/types'; From a3cec8e74c6cdbdc9a30358bd5b1a5f115a4c50a Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 00:02:19 +0800 Subject: [PATCH 084/112] Fix a broken file name --- lib/modules-lib/src/tabs/{WebglCanvas.tsx => WebGLCanvas.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/modules-lib/src/tabs/{WebglCanvas.tsx => WebGLCanvas.tsx} (100%) diff --git a/lib/modules-lib/src/tabs/WebglCanvas.tsx b/lib/modules-lib/src/tabs/WebGLCanvas.tsx similarity index 100% rename from lib/modules-lib/src/tabs/WebglCanvas.tsx rename to lib/modules-lib/src/tabs/WebGLCanvas.tsx From 621dfc1d5afa0e3526ed0071905cf4fa1add9c6f Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 01:01:22 +0800 Subject: [PATCH 085/112] Fix more broken tests and add documentation for repo tools --- .vscode/settings.json | 11 +++- docs/.vitepress/config.ts | 3 +- docs/src/index.md | 10 +-- docs/src/repotools/devserver/devserver.md | 17 +++++ docs/src/repotools/docserver/docserver.md | 60 ++++++++++++++++++ docs/src/repotools/docserver/index.md | 0 docs/src/repotools/docserver/menu.png | Bin 0 -> 14382 bytes docs/src/repotools/index.md | 5 ++ docs/src/repotools/vscode.md | 10 +++ docs/src/repotools/yarn.md | 40 ++++++++++++ .../src/rules/__tests__/tabType.test.ts | 8 +-- 11 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 docs/src/repotools/devserver/devserver.md create mode 100644 docs/src/repotools/docserver/docserver.md create mode 100644 docs/src/repotools/docserver/index.md create mode 100644 docs/src/repotools/docserver/menu.png create mode 100644 docs/src/repotools/index.md create mode 100644 docs/src/repotools/vscode.md create mode 100644 docs/src/repotools/yarn.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 031eb0e79e..5943c6376e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,8 +6,15 @@ "url": "./lib/buildtools/src/build/modules/manifest.schema.json" }, { - "fileMatch": ["tsconfig.*.json"], - "url": "http://json.schemastore.org/tsconfig" + "fileMatch": ["tsconfig.json", "tsconfig.*.json"], + "schema": { + "allOf": [{ "$ref": "http://json.schemastore.org/tsconfig" }], + "properties": { + "typedocOptions": { + "allOf": [{ "$ref": "https://typedoc.org/schema.json" }] + } + } + } } ] } diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 837afb0ab3..5af04dd5d8 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -46,7 +46,8 @@ const sidebarConfigs: Record = { buildtools: {}, modules: {}, lib: {}, - '/lib/modules-lib': {} + 'lib/modules-lib': {}, + repotools: {} }; const sideBarOptions = Object.entries(sidebarConfigs).map(([startPath, options]): VitePressSidebarOptions => ({ diff --git a/docs/src/index.md b/docs/src/index.md index 281b566e71..2822ab24cd 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,15 +5,6 @@ layout: home hero: name: Modules Developer Documentation tagline: Developer documentation for the Source Academy modules repository - actions: - - theme: brand - text: Get Started - link: /modules/1_getting-started - - theme: brand - text: Repo Overview - - theme: alt - text: Developer Instructions - link: /api-examples features: - title: Your first bundle or tab @@ -30,4 +21,5 @@ features: - title: Repository Tools details: Details for the tools used to aid in developing SA Modules + link: /repotools --- diff --git a/docs/src/repotools/devserver/devserver.md b/docs/src/repotools/devserver/devserver.md new file mode 100644 index 0000000000..6c08d612e5 --- /dev/null +++ b/docs/src/repotools/devserver/devserver.md @@ -0,0 +1,17 @@ +# Modules Development Server + +This server relies on [`Vite`](https://vitejs.dev) to create a server that automatically reloads when it detects file system changes. This allows Source Academy developers to make changes to their tabs without having to use the frontend, and have it render code changes live. + +The components here were copied from the frontend's playground and then simplified. + +## Compiled Tabs vs Hot Reload +Normally, the Source Academy frontend uses the bundled version of the tabs. However, the development server relies on the raw Typescript files by default so as to enable hot reloading. If you wish to test the compiled versions, use the settings button to toggle using compiled tabs. + +## `mockModuleContext.ts` +For compiled tabs, `js-slang/context` is not an import that should be required. However, in hot-reload mode, because some tabs rely on their corresponding bundles, `js-slang/context` is an import that needs to be provided. + +The `vite` config aliases `js-slang/context` to the `mockModuleContext.ts` file, which can then be used to simulate the evaluation context. + +## Vitest Testing +The `vitest` framework integrates with `playwright` and allows us to actually simulate user actions on the browser page. This is used to test the functionality of the development server. + diff --git a/docs/src/repotools/docserver/docserver.md b/docs/src/repotools/docserver/docserver.md new file mode 100644 index 0000000000..0823a6ba27 --- /dev/null +++ b/docs/src/repotools/docserver/docserver.md @@ -0,0 +1,60 @@ +# Developer Documentation Server + +Originally most of the developer documentation was contained within the Github repository's wiki. However, as the documentation became more comprehensive in scope and complex in design, +it was necessary to migrate to a more advanced and configurable solution. + +This documentation server is powered by [Vitepress](https://vitepress.dev), which takes Markdown files and renders them into a static site. + +## File Structure +All pages for the server are contained under `src`. + +[`vitepress-sidebar`](https://vitepress-sidebar.cdget.com) is being used to generate the sidebar for the server. This means that entries in the sidebar appear sorted according to the file/folder names from which they originate. This is why +some folder have contents that are labelled `1-something`, `2-something` etc. Each item's value is taken from file's frontmatter, or if not present, the file's first header. + +Folders produce item groups, the name of which can be customized using an `index.md` file within that folder. + +```txt +bundles +├── index.md +├── 1-getting-started +│ ├── 1-overview.md +│ ├── 2-start.md +│ ├── 3.cheat.md +│ ├── 4-faq.md +│ └── index.md +└── 2-bundle + ├── 1-overview + │ └── 1-overview.md + ├── 2-creating + │ └── 2-creating.md + ├── 3-editing.md + ├── 4-documentation + │ └── 4-documentation.md + ├── 5-type_map.md + ├── 6-compiling.md + └── index.md +``` + +The above file structure produces the menu below: +![image](./menu.png) + +Each item takes its title value from either the frontmatter (if available) or the first heading of each page. + +`index.md` files are only used to title the menus, they are not intended for navigation, though are navigable to if the user enters the address manually, so it is still recommended to keep some basic +documentation in those pages or create rewrites so that users are automatically redirected away from those pages. Here are the contents of `2-bundle/index.md`: + +<<< ../../modules/2-bundle/index.md + +Instead, the links in the sidebar link to the page (if it is a markdown file), or to a child within the folder that has the same name as that folder. + +The specific configuration for this behaviour can be found in the Vitepress config file, and the documentation for those options can be found on the Vitepress website: + +<<< ../../../.vitepress/config.ts {33-43} + +## Documentation for `modules-lib` +The documentation for `@sourceacademy/modules-lib` is automatically generated by Typedoc. The configuration options for this generation are found in that folder. The documentation for `@sourceacademy/modules-lib` should be built before this server is built. + +::: details help pls +Supposedly Vitepress can embed React components, which would be nice for us to have working demonstrations of our React components. Unfortunately, I have never +been able to get that to work. You can start [here](https://github.com/vuejs/vitepress/discussions/2183) if you want to help figure this out. +::: diff --git a/docs/src/repotools/docserver/index.md b/docs/src/repotools/docserver/index.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/src/repotools/docserver/menu.png b/docs/src/repotools/docserver/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..2468cdbc399ebec472ecde4318339ba43192fa9a GIT binary patch literal 14382 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 literal 0 HcmV?d00001 diff --git a/docs/src/repotools/index.md b/docs/src/repotools/index.md new file mode 100644 index 0000000000..4475ce1c0d --- /dev/null +++ b/docs/src/repotools/index.md @@ -0,0 +1,5 @@ +# Repository Development Tools + +- [Development Server](./devserver/) +- [Docs Server](./docserver/docserver) +- [Yarn](./yarn) diff --git a/docs/src/repotools/vscode.md b/docs/src/repotools/vscode.md new file mode 100644 index 0000000000..ad3657d756 --- /dev/null +++ b/docs/src/repotools/vscode.md @@ -0,0 +1,10 @@ +# VSCode Integration +This page is dedicated to explaining all the VSCode integrations available from this repository. + +Since most developers here are assumed to be using Visual Studio Code, this repository automatically provides some extra tooling that integrates with VSCode, all found within the `.vscode` folder. + +## JSON Schemas +By default, VSCode doesn't apply any kind of validation to JSON files that don't have explicit schemas. Using the `settings.json` file, we can apply JSON schemas to specific JSON files. + +For this repository, this has been configured for `tsconfig.*.json` files to extend both the original `tsconfig` schema, but also to include the Typedoc schema. Bundle manifest files +should also automatically receive validation and IntelliSense autocompletion. diff --git a/docs/src/repotools/yarn.md b/docs/src/repotools/yarn.md new file mode 100644 index 0000000000..71bb09f043 --- /dev/null +++ b/docs/src/repotools/yarn.md @@ -0,0 +1,40 @@ +# Yarn + +This page is dedicated to explaing how Yarn has been configured to work in this repository. + +## Workspaces +Yarn, this repository's package manager, supports a feature called [workspaces](https://yarnpkg.com/features/workspaces#global-scripts), which are a way for packages to refer to one another within the same repository. + +This has several benefits: +- **Reduced install size.** Yarn workspaces support focused installs, which means that a single team working on a single bundle or tab won't have to install the entire repository's worth of packages that won't be relevant to them +- **Dependency Management.** If tabs (or other bundles) rely on a specific bundle, Yarn's dependency resolution automatically determines what needs to be rebuilt and in what order. +- **Automatic Worktree Detection.** If so desired, Yarn automatically detects which bundles/tabs have changed with respect to the main git branch and won't rebuild those that haven't. +- **More Configurability:** Each bundle/tab can maintain its own set of dependencies now, separate from other bundles. This allows us to have patches local to a specific bundle (like the `@jscad/modeling` patch for `csg`). Also each bundle/tab can customize its own `tsconfig` etc.. + +At the root repository, this is defined using the `workspaces` field in `package.json`. + +## Yarn Constraints +Yarn also supports a feature known as [constraints](https://yarnpkg.com/features/workspaces#constraints) for workspaces. As of this moment, the feature can be used to ensure that all workspaces within a repository have fields in their `package.json`s set to specific values. We use this feature to ensure that: + +### 1. All workspaces have `"type": "module"`. +As part of ensuring consistency, everything in this repository has been designed to use ESM first and to move away from legacy reliance on CommonJS modules. + +### 2. Dependencies, if shared, have the correct versions. +Tabs, for example, should all be using the same versions of `react` and `react-dom`: the one in use by the frontend. If a dependency is specified in the root `package.json`, then the constraints file +requires that all child workspaces use the version of that dependency. + +For example, the root package specifies `react@^18.3.1` as a depdendency, so all workspaces that require React must also use that version spec. + +This validation is not carried out across child workspaces, however. Two different bundles could use two different versions of the same package. This should not cause an issue **unless** the bundles are somehow dependendent on each other. + +## Parallel Execution of Scripts +Using the various options of the `yarn workspaces foreach` command, you can execute multiple tasks in parallel, which is how many of the commands in the root repository have been set up. + +The `-t` option runs the scripts in topological order, which means you don't have to manually figure out which packages have to be built before others; Yarn figures it out for you. + +::: details ESLint out of memory? +For some reason when I migrated this repository over to Yarn workspaces, I kept finding that linting all the bundles at once in parallel +would cause NodeJS to exceed its default memory limit of 4906MB. I guess if you wanted to run linting for all bundles in parallel you could +just run NodeJS with `--max-old-space-size`, but I just found that limiting the number of jobs running in parallel using `-j` to 5 was +sufficient to get around this issue. +::: diff --git a/lib/lintplugin/src/rules/__tests__/tabType.test.ts b/lib/lintplugin/src/rules/__tests__/tabType.test.ts index 01644ed624..4fdecfd36f 100644 --- a/lib/lintplugin/src/rules/__tests__/tabType.test.ts +++ b/lib/lintplugin/src/rules/__tests__/tabType.test.ts @@ -9,18 +9,18 @@ describe('Test collateTypeImports', () => { { valid: [{ code: ` - import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; + import { defineTab } from '@sourceacademy/modules-lib/tabs'; export default defineTab({}) ` }, { code: ` - import { defineTab as definer } from '@sourceacademy/modules-lib/tabs/utils'; + import { defineTab as definer } from '@sourceacademy/modules-lib/tabs'; export default definer({}) ` }, { code: ` import { stuff } from 'somewhere'; - import { defineTab as definer } from '@sourceacademy/modules-lib/tabs/utils'; + import { defineTab as definer } from '@sourceacademy/modules-lib/tabs'; export default definer({ stuff }) `}], invalid: [{ @@ -34,7 +34,7 @@ describe('Test collateTypeImports', () => { errors: 1 }, { code: ` - import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; + import { defineTab } from '@sourceacademy/modules-lib/tabs'; export default 0; `, errors: 1 From 06cc233886c0268814dcc19745e3d98d921d29dd Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 11:23:42 +0800 Subject: [PATCH 086/112] Fix broken test --- .../src/commands/__tests__/template.test.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/buildtools/src/commands/__tests__/template.test.ts b/lib/buildtools/src/commands/__tests__/template.test.ts index 82211157ca..54884cf6f2 100644 --- a/lib/buildtools/src/commands/__tests__/template.test.ts +++ b/lib/buildtools/src/commands/__tests__/template.test.ts @@ -114,14 +114,19 @@ describe('Test adding new module', () => { }); expect(tsconfigPath).toEqual('/src/bundles/new_module/tsconfig.json'); - const tsconfig = JSON.parse(rawTsconfig as string); - expect(tsconfig).toMatchObject({ - include: ['./src'], - extends: '../tsconfig.json', - typedocOptions: { - name: 'new_module' + expect(rawTsconfig).toMatchInlineSnapshot(` + "// new_module tsconfig + { + "extends": "../tsconfig.json", + "include": [ + "./src" + ], + "typedocOptions": { + "name": "new_module" + } } - }); + " + `); }); }); From def7151be14ae6bc37482605aa1ea459f506ad20 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 11:32:34 +0800 Subject: [PATCH 087/112] Change the defineTab import --- src/tabs/ArcadeTwod/index.tsx | 2 +- src/tabs/AugmentedReality/src/index.tsx | 2 +- src/tabs/CopyGc/src/index.tsx | 2 +- src/tabs/Csg/src/index.tsx | 2 +- src/tabs/Curve/src/index.tsx | 3 +-- src/tabs/Game/src/index.tsx | 2 +- src/tabs/MarkSweep/src/index.tsx | 2 +- src/tabs/Nbody/index.tsx | 2 +- src/tabs/Painter/index.tsx | 3 +-- src/tabs/Physics2D/src/index.tsx | 2 +- src/tabs/Pixnflix/index.tsx | 2 +- src/tabs/Plotly/index.tsx | 3 +-- src/tabs/Repeat/index.tsx | 2 +- src/tabs/Repl/index.tsx | 2 +- src/tabs/RobotSimulation/src/index.tsx | 2 +- src/tabs/Rune/src/index.tsx | 3 +-- src/tabs/Sound/index.tsx | 3 +-- src/tabs/SoundMatrix/index.tsx | 2 +- src/tabs/StereoSound/index.tsx | 3 +-- src/tabs/Unittest/index.tsx | 2 +- src/tabs/UnityAcademy/index.tsx | 2 +- 21 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/tabs/ArcadeTwod/index.tsx b/src/tabs/ArcadeTwod/index.tsx index 68e35ed218..ac8668a76b 100644 --- a/src/tabs/ArcadeTwod/index.tsx +++ b/src/tabs/ArcadeTwod/index.tsx @@ -1,6 +1,6 @@ import { Button, ButtonGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import Phaser from 'phaser'; import React from 'react'; diff --git a/src/tabs/AugmentedReality/src/index.tsx b/src/tabs/AugmentedReality/src/index.tsx index 9ebd333850..107a80af24 100644 --- a/src/tabs/AugmentedReality/src/index.tsx +++ b/src/tabs/AugmentedReality/src/index.tsx @@ -1,6 +1,6 @@ import { IconNames } from '@blueprintjs/icons'; import { getModuleState } from '@sourceacademy/bundle-ar/AR'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import { ScreenStateContext } from 'saar/libraries/screen_state_library/ScreenStateContext'; import { StartButton } from './StartButton'; diff --git a/src/tabs/CopyGc/src/index.tsx b/src/tabs/CopyGc/src/index.tsx index f1f9e3974b..124d19e6bf 100644 --- a/src/tabs/CopyGc/src/index.tsx +++ b/src/tabs/CopyGc/src/index.tsx @@ -1,6 +1,6 @@ import { Slider, Icon } from '@blueprintjs/core'; import { COMMAND } from '@sourceacademy/bundle-copy_gc/types'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import { ThemeColor } from './style'; diff --git a/src/tabs/Csg/src/index.tsx b/src/tabs/Csg/src/index.tsx index 29ba5cd40f..2f2203d2ad 100644 --- a/src/tabs/Csg/src/index.tsx +++ b/src/tabs/Csg/src/index.tsx @@ -2,7 +2,7 @@ import { IconNames } from '@blueprintjs/icons'; import { Core } from '@sourceacademy/bundle-csg/core'; import type { CsgModuleState } from '@sourceacademy/bundle-csg/utilities'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; import type { ReactElement } from 'react'; import CanvasHolder from './canvas_holder'; diff --git a/src/tabs/Curve/src/index.tsx b/src/tabs/Curve/src/index.tsx index e61f3dccee..b13421d0e8 100644 --- a/src/tabs/Curve/src/index.tsx +++ b/src/tabs/Curve/src/index.tsx @@ -1,7 +1,6 @@ import { IconNames } from '@blueprintjs/icons'; import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; -import { AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; -import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, getModuleState, AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; import CurveCanvas3D from './canvas_3d_curve'; diff --git a/src/tabs/Game/src/index.tsx b/src/tabs/Game/src/index.tsx index e2b3a0281f..5a6d0cb835 100644 --- a/src/tabs/Game/src/index.tsx +++ b/src/tabs/Game/src/index.tsx @@ -1,4 +1,4 @@ -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import { Links } from './constants'; diff --git a/src/tabs/MarkSweep/src/index.tsx b/src/tabs/MarkSweep/src/index.tsx index d78adbdcda..80ccb356e8 100644 --- a/src/tabs/MarkSweep/src/index.tsx +++ b/src/tabs/MarkSweep/src/index.tsx @@ -1,5 +1,5 @@ import { Slider, Icon } from '@blueprintjs/core'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import { ThemeColor } from './style'; diff --git a/src/tabs/Nbody/index.tsx b/src/tabs/Nbody/index.tsx index 7f1f9df90c..e4bb80e7da 100644 --- a/src/tabs/Nbody/index.tsx +++ b/src/tabs/Nbody/index.tsx @@ -1,6 +1,6 @@ import { Button, ButtonGroup, NumericInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import type { Simulation } from 'nbody'; import React from 'react'; diff --git a/src/tabs/Painter/index.tsx b/src/tabs/Painter/index.tsx index 7c5235d86e..e166d11ba1 100644 --- a/src/tabs/Painter/index.tsx +++ b/src/tabs/Painter/index.tsx @@ -1,6 +1,5 @@ import type { LinePlot } from '@sourceacademy/bundle-painter/painter'; -import Modal from '@sourceacademy/modules-lib/tabs/ModalDiv'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, ModalDiv as Modal } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = { diff --git a/src/tabs/Physics2D/src/index.tsx b/src/tabs/Physics2D/src/index.tsx index d0050b626c..2cb8627e86 100644 --- a/src/tabs/Physics2D/src/index.tsx +++ b/src/tabs/Physics2D/src/index.tsx @@ -1,4 +1,4 @@ -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import DebugDrawCanvas from './DebugDrawCanvas'; /** diff --git a/src/tabs/Pixnflix/index.tsx b/src/tabs/Pixnflix/index.tsx index fa2828f496..493332204c 100644 --- a/src/tabs/Pixnflix/index.tsx +++ b/src/tabs/Pixnflix/index.tsx @@ -18,7 +18,7 @@ import { InputFeed, type TabsPacket } from '@sourceacademy/bundle-pix_n_flix/types'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React, { type ChangeEvent, type DragEvent } from 'react'; type Props = { diff --git a/src/tabs/Plotly/index.tsx b/src/tabs/Plotly/index.tsx index 5d8bee4f58..bb76f22183 100644 --- a/src/tabs/Plotly/index.tsx +++ b/src/tabs/Plotly/index.tsx @@ -1,6 +1,5 @@ import type { DrawnPlot } from '@sourceacademy/bundle-plotly/plotly'; -import Modal from '@sourceacademy/modules-lib/tabs/ModalDiv'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, ModalDiv as Modal } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = { diff --git a/src/tabs/Repeat/index.tsx b/src/tabs/Repeat/index.tsx index 668447c478..62e1eb0fc6 100644 --- a/src/tabs/Repeat/index.tsx +++ b/src/tabs/Repeat/index.tsx @@ -1,4 +1,4 @@ -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = { diff --git a/src/tabs/Repl/index.tsx b/src/tabs/Repl/index.tsx index 58a3f4bdde..0400b6fb09 100644 --- a/src/tabs/Repl/index.tsx +++ b/src/tabs/Repl/index.tsx @@ -9,7 +9,7 @@ import { IconNames } from '@blueprintjs/icons'; import { FONT_MESSAGE, MINIMUM_EDITOR_HEIGHT } from '@sourceacademy/bundle-repl/config'; import type { ProgrammableRepl } from '@sourceacademy/bundle-repl/programmable_repl'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import AceEditor from 'react-ace'; diff --git a/src/tabs/RobotSimulation/src/index.tsx b/src/tabs/RobotSimulation/src/index.tsx index 87727d3e6d..5dcb792bf0 100644 --- a/src/tabs/RobotSimulation/src/index.tsx +++ b/src/tabs/RobotSimulation/src/index.tsx @@ -1,4 +1,4 @@ -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import { Main } from './components/Main'; /** diff --git a/src/tabs/Rune/src/index.tsx b/src/tabs/Rune/src/index.tsx index 6b5fa7a776..70e789b66f 100644 --- a/src/tabs/Rune/src/index.tsx +++ b/src/tabs/Rune/src/index.tsx @@ -1,6 +1,5 @@ import { type RuneModuleState, isHollusionRune } from '@sourceacademy/bundle-rune/functions'; -import { AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; -import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, getModuleState, AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import { glAnimation, type ModuleTab } from '@sourceacademy/modules-lib/types'; import HollusionCanvas from './hollusion_canvas'; diff --git a/src/tabs/Sound/index.tsx b/src/tabs/Sound/index.tsx index e37d7e148a..f9edc7c10b 100644 --- a/src/tabs/Sound/index.tsx +++ b/src/tabs/Sound/index.tsx @@ -1,6 +1,5 @@ import type { SoundModuleState } from '@sourceacademy/bundle-sound/types'; -import { MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; -import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { getModuleState, defineTab, MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; /** diff --git a/src/tabs/SoundMatrix/index.tsx b/src/tabs/SoundMatrix/index.tsx index 2200d31942..e197912131 100644 --- a/src/tabs/SoundMatrix/index.tsx +++ b/src/tabs/SoundMatrix/index.tsx @@ -1,5 +1,5 @@ import { Button, Classes } from '@blueprintjs/core'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import classNames from 'classnames'; import React from 'react'; diff --git a/src/tabs/StereoSound/index.tsx b/src/tabs/StereoSound/index.tsx index 84e04645b7..42867a66c5 100644 --- a/src/tabs/StereoSound/index.tsx +++ b/src/tabs/StereoSound/index.tsx @@ -1,6 +1,5 @@ import type { StereoSoundModuleState } from '@sourceacademy/bundle-stereo_sound/types'; -import { MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; -import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab, getModuleState, MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; import type { ModuleTab } from '@sourceacademy/modules-lib/types'; /** diff --git a/src/tabs/Unittest/index.tsx b/src/tabs/Unittest/index.tsx index aacbc22b05..d407e7a5bf 100644 --- a/src/tabs/Unittest/index.tsx +++ b/src/tabs/Unittest/index.tsx @@ -1,5 +1,5 @@ import type { SuiteResult, TestContext } from '@sourceacademy/bundle-unittest/types'; -import { getModuleState, defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { getModuleState, defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; /** diff --git a/src/tabs/UnityAcademy/index.tsx b/src/tabs/UnityAcademy/index.tsx index 77d1871cd2..44c54e25af 100644 --- a/src/tabs/UnityAcademy/index.tsx +++ b/src/tabs/UnityAcademy/index.tsx @@ -8,7 +8,7 @@ import { Button, NumericInput, Checkbox } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { getInstance } from '@sourceacademy/bundle-unity_academy/UnityAcademy'; import { UNITY_ACADEMY_BACKEND_URL } from '@sourceacademy/bundle-unity_academy/config'; -import { defineTab } from '@sourceacademy/modules-lib/tabs/utils'; +import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = {}; From b113ce3c762e9665575a00f4007372df0166c264 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 11:48:56 +0800 Subject: [PATCH 088/112] Fix incorrect snapshots --- .../__tests__/__snapshots__/Curve.tsx.snap | 58 ++++++++++--------- src/tabs/Rune/src/__tests__/Rune.tsx | 2 +- .../src/__tests__/__snapshots__/Rune.tsx.snap | 23 ++++---- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap b/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap index 43e7cad2a2..3662ac091a 100644 --- a/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap +++ b/src/tabs/Curve/src/__tests__/__snapshots__/Curve.tsx.snap @@ -5,19 +5,22 @@ exports[`Curve 3D animations error gracefully 1`] = ` context={ { "context": { - "curve": { - "state": { - "drawnCurves": [ - AnimatedCurve { - "angle": 0, - "drawer": [Function], - "duration": 1, - "fps": 60, - "func": [Function], - "is3D": true, - "toReplString": [Function], - }, - ], + "moduleContexts": { + "curve": { + "state": { + "drawnCurves": [ + AnimatedCurve { + "angle": 0, + "drawer": [Function], + "duration": 1, + "fps": 60, + "func": [Function], + "is3D": true, + "toReplString": [Function], + }, + ], + }, + "tabs": [], }, }, }, @@ -31,19 +34,22 @@ exports[`Curve animations error gracefully 1`] = ` context={ { "context": { - "curve": { - "state": { - "drawnCurves": [ - AnimatedCurve { - "angle": 0, - "drawer": [Function], - "duration": 1, - "fps": 60, - "func": [Function], - "is3D": false, - "toReplString": [Function], - }, - ], + "moduleContexts": { + "curve": { + "state": { + "drawnCurves": [ + AnimatedCurve { + "angle": 0, + "drawer": [Function], + "duration": 1, + "fps": 60, + "func": [Function], + "is3D": false, + "toReplString": [Function], + }, + ], + }, + "tabs": [], }, }, }, diff --git a/src/tabs/Rune/src/__tests__/Rune.tsx b/src/tabs/Rune/src/__tests__/Rune.tsx index 3d89e511be..cb6e3765dd 100644 --- a/src/tabs/Rune/src/__tests__/Rune.tsx +++ b/src/tabs/Rune/src/__tests__/Rune.tsx @@ -6,7 +6,7 @@ import { RuneTab } from '..'; test('Ensure that rune animations error gracefully', () => { const badAnimation = animate_rune(1, 60, _t => 1 as any); - const mockContext = mockDebuggerContext({ drawnRunes: [badAnimation ]}, 'rune'); + const mockContext = mockDebuggerContext({ drawnRunes: [badAnimation]}, 'rune'); expect() .toMatchSnapshot(); }); diff --git a/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap b/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap index 29f22fb8f0..b91f167fda 100644 --- a/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap +++ b/src/tabs/Rune/src/__tests__/__snapshots__/Rune.tsx.snap @@ -5,16 +5,19 @@ exports[`Ensure that rune animations error gracefully 1`] = ` context={ { "context": { - "rune": { - "state": { - "drawnRunes": [ - AnimatedRune { - "duration": 1, - "fps": 60, - "func": [Function], - "toReplString": [Function], - }, - ], + "moduleContexts": { + "rune": { + "state": { + "drawnRunes": [ + AnimatedRune { + "duration": 1, + "fps": 60, + "func": [Function], + "toReplString": [Function], + }, + ], + }, + "tabs": [], }, }, }, From d94bca0a21bd4a52c2823d665036ac42db63e5c4 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 12:42:17 +0800 Subject: [PATCH 089/112] Fix weirdness with WebGLCanvas import path name --- src/tabs/Physics2D/src/DebugDrawCanvas.tsx | 2 +- src/tabs/Rune/src/hollusion_canvas.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tabs/Physics2D/src/DebugDrawCanvas.tsx b/src/tabs/Physics2D/src/DebugDrawCanvas.tsx index fdd4308cc8..24f5260c6c 100644 --- a/src/tabs/Physics2D/src/DebugDrawCanvas.tsx +++ b/src/tabs/Physics2D/src/DebugDrawCanvas.tsx @@ -4,7 +4,7 @@ import { DrawShapes, type b2World } from '@box2d/core'; import { DebugDraw } from '@box2d/debug-draw'; import type { PhysicsWorld } from '@sourceacademy/bundle-physics_2d/PhysicsWorld'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebGLCanvas'; import React from 'react'; type DebugDrawCanvasProps = { diff --git a/src/tabs/Rune/src/hollusion_canvas.tsx b/src/tabs/Rune/src/hollusion_canvas.tsx index 70a8487b33..c02b977d43 100644 --- a/src/tabs/Rune/src/hollusion_canvas.tsx +++ b/src/tabs/Rune/src/hollusion_canvas.tsx @@ -1,5 +1,5 @@ import type { HollusionRune } from '@sourceacademy/bundle-rune/functions'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; +import { WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; /** From 20a475b225c9d71a12170fe774356d28b9230e3c Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 14:03:10 +0800 Subject: [PATCH 090/112] Please finally fix the issues with the tab exports --- lib/modules-lib/package.json | 1 + src/bundles/csg/src/jscad/renderer.ts | 2 +- src/tabs/Csg/src/canvas_holder.tsx | 2 +- src/tabs/Csg/src/hover_control_hint.tsx | 2 +- src/tabs/Curve/src/animation_canvas_3d_curve.tsx | 14 +++++++++----- src/tabs/Curve/src/canvas_3d_curve.tsx | 4 +--- src/tabs/Physics2D/src/DebugDrawCanvas.tsx | 2 +- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/modules-lib/package.json b/lib/modules-lib/package.json index 50e58d690e..381d139f36 100644 --- a/lib/modules-lib/package.json +++ b/lib/modules-lib/package.json @@ -15,6 +15,7 @@ "vitest": "^3.2.3" }, "exports": { + "./tabs/*": null, "./*": "./dist/*.js", "./tabs": "./dist/tabs/index.js", "./types": "./dist/types/index.js" diff --git a/src/bundles/csg/src/jscad/renderer.ts b/src/bundles/csg/src/jscad/renderer.ts index e2b8edf407..f2a493e402 100644 --- a/src/bundles/csg/src/jscad/renderer.ts +++ b/src/bundles/csg/src/jscad/renderer.ts @@ -7,7 +7,7 @@ import { entitiesFromSolids, prepareRender } from '@jscad/regl-renderer'; -import { ACE_GUTTER_BACKGROUND_COLOR, ACE_GUTTER_TEXT_COLOR, BP_TEXT_COLOR } from '@sourceacademy/modules-lib/tabs/css_constants'; +import { ACE_GUTTER_BACKGROUND_COLOR, ACE_GUTTER_TEXT_COLOR, BP_TEXT_COLOR } from '@sourceacademy/modules-lib/tabs'; import { DEFAULT_COLOR, GRID_PADDING, diff --git a/src/tabs/Csg/src/canvas_holder.tsx b/src/tabs/Csg/src/canvas_holder.tsx index 473b12e92e..c9a1c69677 100644 --- a/src/tabs/Csg/src/canvas_holder.tsx +++ b/src/tabs/Csg/src/canvas_holder.tsx @@ -4,7 +4,7 @@ import { IconNames } from '@blueprintjs/icons'; import { Core } from '@sourceacademy/bundle-csg/core'; import StatefulRenderer from '@sourceacademy/bundle-csg/stateful_renderer'; import type { RenderGroup } from '@sourceacademy/bundle-csg/utilities'; -import { BP_CARD_BORDER_RADIUS, BP_TAB_BUTTON_MARGIN, BP_TAB_PANEL_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; +import { BP_CARD_BORDER_RADIUS, BP_TAB_BUTTON_MARGIN, BP_TAB_PANEL_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import type { CanvasHolderProps, CanvasHolderState } from '../types'; import HoverControlHint from './hover_control_hint'; diff --git a/src/tabs/Csg/src/hover_control_hint.tsx b/src/tabs/Csg/src/hover_control_hint.tsx index bd5fb3d7d2..adc1ed4820 100644 --- a/src/tabs/Csg/src/hover_control_hint.tsx +++ b/src/tabs/Csg/src/hover_control_hint.tsx @@ -1,6 +1,6 @@ /* [Imports] */ import { Icon, Tooltip } from '@blueprintjs/core'; -import { BP_ICON_COLOR, SA_TAB_BUTTON_WIDTH, SA_TAB_ICON_SIZE } from '@sourceacademy/modules-lib/tabs/css_constants'; +import { BP_ICON_COLOR, SA_TAB_BUTTON_WIDTH, SA_TAB_ICON_SIZE } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import type { HintProps } from '../types'; diff --git a/src/tabs/Curve/src/animation_canvas_3d_curve.tsx b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx index 13774aec3d..fcaadf3b5c 100644 --- a/src/tabs/Curve/src/animation_canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx @@ -1,11 +1,15 @@ import { Icon, Slider, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import type { AnimatedCurve } from '@sourceacademy/bundle-curve/types'; -import AutoLoopSwitch from '@sourceacademy/modules-lib/tabs/AutoLoopSwitch'; -import ButtonComponent from '@sourceacademy/modules-lib/tabs/ButtonComponent'; -import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; +import { + BP_TAB_BUTTON_MARGIN, + BP_TEXT_MARGIN, + CANVAS_MAX_WIDTH, + AutoLoopSwitch, + ButtonComponent, + PlayButton, + WebGLCanvas +} from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = { diff --git a/src/tabs/Curve/src/canvas_3d_curve.tsx b/src/tabs/Curve/src/canvas_3d_curve.tsx index bae4fd8a5a..4132dc19dc 100644 --- a/src/tabs/Curve/src/canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/canvas_3d_curve.tsx @@ -1,8 +1,6 @@ import { Slider } from '@blueprintjs/core'; import type { CurveDrawn } from '@sourceacademy/bundle-curve/curves_webgl'; -import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebglCanvas'; -import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs/css_constants'; +import { PlayButton, WebGLCanvas, BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs'; import { degreesToRadians } from '@sourceacademy/modules-lib/utilities'; import React from 'react'; diff --git a/src/tabs/Physics2D/src/DebugDrawCanvas.tsx b/src/tabs/Physics2D/src/DebugDrawCanvas.tsx index 24f5260c6c..4056d0a808 100644 --- a/src/tabs/Physics2D/src/DebugDrawCanvas.tsx +++ b/src/tabs/Physics2D/src/DebugDrawCanvas.tsx @@ -4,7 +4,7 @@ import { DrawShapes, type b2World } from '@box2d/core'; import { DebugDraw } from '@box2d/debug-draw'; import type { PhysicsWorld } from '@sourceacademy/bundle-physics_2d/PhysicsWorld'; -import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebGLCanvas'; +import{ WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type DebugDrawCanvasProps = { From 889b31bc40b92534af4cda464956c24681c83824 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 14:13:31 +0800 Subject: [PATCH 091/112] guess it was still broken --- lib/modules-lib/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/modules-lib/package.json b/lib/modules-lib/package.json index 381d139f36..50e58d690e 100644 --- a/lib/modules-lib/package.json +++ b/lib/modules-lib/package.json @@ -15,7 +15,6 @@ "vitest": "^3.2.3" }, "exports": { - "./tabs/*": null, "./*": "./dist/*.js", "./tabs": "./dist/tabs/index.js", "./types": "./dist/types/index.js" From 9c6fea1b05678343a68c803b61ad6e3ba1021193 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Wed, 25 Jun 2025 14:26:33 +0800 Subject: [PATCH 092/112] Trying to fix devserver tests this time --- devserver/vite.config.ts | 5 ++--- lib/modules-lib/src/types/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/devserver/vite.config.ts b/devserver/vite.config.ts index 5dc1145972..c95bcf3a10 100644 --- a/devserver/vite.config.ts +++ b/devserver/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig(({ mode }) => { return mergeConfig( rootConfig, { + root: import.meta.dirname, plugins: [ nodePolyfills({ include: ['path'] @@ -69,10 +70,8 @@ export default defineConfig(({ mode }) => { "vite-plugin-node-polyfills/shims/buffer", "vite-plugin-node-polyfills/shims/global", "vite-plugin-node-polyfills/shims/process", - ], - exclude: [ '../build/tabs/*.js' - ] + ], }, test: { name: 'Dev Server', diff --git a/lib/modules-lib/src/types/index.ts b/lib/modules-lib/src/types/index.ts index 79e72ee67e..3fdcba45ab 100644 --- a/lib/modules-lib/src/types/index.ts +++ b/lib/modules-lib/src/types/index.ts @@ -1,6 +1,6 @@ import type { IconName } from '@blueprintjs/icons'; import type { Context } from 'js-slang'; -import type { FC } from 'react'; +import type React from 'react'; /** * Represents an animation drawn using WebGL @@ -49,7 +49,7 @@ export interface ReplResult { toReplString: () => string; } -export type ModuleTab = FC<{ context: DebuggerContext }>; +export type ModuleTab = (props: { context: DebuggerContext }) => React.ReactNode; export type ModuleSideContent = { /** From 4ff050cb78eb092e51cb47489790f7032916d9b3 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Thu, 26 Jun 2025 00:18:22 +0800 Subject: [PATCH 093/112] Update configuration for docserver --- .github/workflows/pull-request.yml | 14 ++- docs/.vitepress/config.ts | 31 ++++++- docs/package.json | 2 +- .../buildtools/{2-command.md => 1-command.md} | 0 .../{3-structure.md => 2-structure.md} | 0 .../{1-builders => 3-builders}/1-modules.md | 0 .../{1-builders => 3-builders}/2-docs.md | 0 .../{1-builders => 3-builders}/3-manifest.md | 0 .../{1-builders => 3-builders}/index.md | 2 +- docs/src/buildtools/4-prebuild.md | 47 ++++++++++ docs/src/buildtools/5-testing.md | 88 +++++++++++++++++++ docs/src/buildtools/6-templates.md | 7 ++ docs/src/buildtools/index.md | 12 +-- docs/src/lib/index.md | 4 +- .../modules/1-getting-started/1-overview.md | 4 +- .../modules/2-bundle/2-creating/2-creating.md | 2 +- docs/src/modules/2-bundle/3-editing.md | 2 +- docs/src/modules/2-bundle/5-type_map.md | 4 +- docs/src/modules/3-tabs/2-creating.md | 4 +- docs/src/modules/3-tabs/3-editing.md | 4 +- .../modules/3-tabs/6-devserver/6-devserver.md | 4 +- docs/src/modules/index.md | 2 +- docs/src/repotools/workflows.md | 38 ++++++++ eslint.config.js | 5 ++ package.json | 4 +- yarn.lock | 20 ++--- 26 files changed, 260 insertions(+), 40 deletions(-) rename docs/src/buildtools/{2-command.md => 1-command.md} (100%) rename docs/src/buildtools/{3-structure.md => 2-structure.md} (100%) rename docs/src/buildtools/{1-builders => 3-builders}/1-modules.md (100%) rename docs/src/buildtools/{1-builders => 3-builders}/2-docs.md (100%) rename docs/src/buildtools/{1-builders => 3-builders}/3-manifest.md (100%) rename docs/src/buildtools/{1-builders => 3-builders}/index.md (82%) create mode 100644 docs/src/buildtools/4-prebuild.md create mode 100644 docs/src/buildtools/5-testing.md create mode 100644 docs/src/buildtools/6-templates.md create mode 100644 docs/src/repotools/workflows.md diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7ade8e81b2..322a252834 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -27,11 +27,15 @@ jobs: - 'lib/**' modules: - src/{bundles,tabs}/** + docs: + - docs/** outputs: # if the workflow file was modified, then we rerun the entire job devserver: ${{ steps.filter.outputs.devserver || steps.filter.outputs.workflows }} - modules: ${{ steps.filter.outputs.modules || steps.filter.outputs.workflows }} + # if the devserver needs to be tested bundles and tabs need to bre rebuilt + modules: ${{ steps.filter.outputs.modules || steps.filter.outputs.workflows || steps.filter.outputs.devserver }} libraries: ${{ steps.filter.outputs.libraries || steps.filter.outputs.workflows }} + docs: ${{ steps.filter.outputs.docs || steps.filter.outputs.workflows }} test: name: Verify all tests pass and build success @@ -70,7 +74,7 @@ jobs: run: yarn test:libs - name: Build, lint and run tsc for bundles and tabs - if: needs.paths-filter.outputs.devserver == 'true' || needs.paths-filter.outputs.modules == 'true' + if: needs.paths-filter.outputs.devserver == 'true' run: yarn workspaces foreach -j 5 -ptW --from "./src/{bundles,tsc}/*" run build --tsc --lint - name: Test bundles and tabs @@ -78,8 +82,12 @@ jobs: run: yarn test:modules - name: Build manifest - if: needs.paths-filter.outputs.devserver == 'true' || needs.paths-filter.outputs.modules == 'true' + if: needs.paths-filter.outputs.devserver == 'true' run: yarn buildtools build manifest + + - name: Build Docs Server + if: needs.paths-filter.outputs.docs == 'true' + run: yarn workspaces foreach -A --include "@sourceacademy/mdoules-docserver" run build - name: Run tsc for Dev Server if: needs.paths-filter.outputs.devserver == 'true' diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 5af04dd5d8..477f224645 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -5,16 +5,43 @@ import _package from '../../package.json' with { type: 'json' }; // https://vitepress.dev/reference/site-config const vitepressOptions: UserConfig = { + base: '/devdocs/', description: 'Developer documentation for the Source Academy modules repository', + ignoreDeadLinks: 'localhostLinks', + outDir: `${import.meta.dirname}/../../build/devdocs`, srcDir: 'src', title: 'Modules Developer Documentation', themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Home', link: '/' }, - { text: 'Module Development', link: '/modules/' }, + { + text: 'Module Development', + items: [ + { + text: 'Overview', + link: '/modules/1-getting-started/1-overview' + }, + { + text: 'Getting Started', + link: '/modules/1-getting-started/2-overview' + } + ] + }, { text: 'Common Library', link: '/lib' }, - { text: 'Build Tools', link: '/buildtools' } + { + text: 'Dev Tools', + items: [ + { + text: 'Build Tools', + link: '/buildtools' + }, + { + text: 'Repo Tools', + link: '/repotools' + }, + ] + } ], siteTitle: 'SA Modules', socialLinks: [ diff --git a/docs/package.json b/docs/package.json index 3db97c423a..7b71835e32 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,5 +1,5 @@ { - "name": "@sourceacademy/modules-devdocs", + "name": "@sourceacademy/modules-docserver", "description": "Developer documentation for the Source Academy modules repository", "private": true, "version": "1.0.0", diff --git a/docs/src/buildtools/2-command.md b/docs/src/buildtools/1-command.md similarity index 100% rename from docs/src/buildtools/2-command.md rename to docs/src/buildtools/1-command.md diff --git a/docs/src/buildtools/3-structure.md b/docs/src/buildtools/2-structure.md similarity index 100% rename from docs/src/buildtools/3-structure.md rename to docs/src/buildtools/2-structure.md diff --git a/docs/src/buildtools/1-builders/1-modules.md b/docs/src/buildtools/3-builders/1-modules.md similarity index 100% rename from docs/src/buildtools/1-builders/1-modules.md rename to docs/src/buildtools/3-builders/1-modules.md diff --git a/docs/src/buildtools/1-builders/2-docs.md b/docs/src/buildtools/3-builders/2-docs.md similarity index 100% rename from docs/src/buildtools/1-builders/2-docs.md rename to docs/src/buildtools/3-builders/2-docs.md diff --git a/docs/src/buildtools/1-builders/3-manifest.md b/docs/src/buildtools/3-builders/3-manifest.md similarity index 100% rename from docs/src/buildtools/1-builders/3-manifest.md rename to docs/src/buildtools/3-builders/3-manifest.md diff --git a/docs/src/buildtools/1-builders/index.md b/docs/src/buildtools/3-builders/index.md similarity index 82% rename from docs/src/buildtools/1-builders/index.md rename to docs/src/buildtools/3-builders/index.md index fc4325aa2d..0efbe7435f 100644 --- a/docs/src/buildtools/1-builders/index.md +++ b/docs/src/buildtools/3-builders/index.md @@ -4,6 +4,6 @@ title: Builders # Builders Overview The following pages contains information on how each asset type is produced -- [Bundles and Tabs](./1-modules.md) +- [Bundles and Tabs](./1-modules) - [Documentation](./2-docs) - [Manifest](./3-manifest) diff --git a/docs/src/buildtools/4-prebuild.md b/docs/src/buildtools/4-prebuild.md new file mode 100644 index 0000000000..8ab4ac6711 --- /dev/null +++ b/docs/src/buildtools/4-prebuild.md @@ -0,0 +1,47 @@ +# Prebuild Tasks + +There are two "prebuild" tasks, linting and type checking that need to be run before any kind of building takes place. + +The buildtools call both ESLint and `tsc` in parallel since they are not dependent on each others' outputs. + +## Running ESLint from the Command Line +ESLint provides [documentation](https://eslint.org/docs/latest/integrate/nodejs-api) detailing how to use its Node API. Below is the code that does just that: + +<<< ../../../lib/buildtools/src/prebuild/lint.ts {ts:line-numbers} + +Because the configuration file for the repository is located at the root of the repository, we need to set the `cwd` to the path to the root of the repository when +initializing the `ESLint` instance. This allows ESLint to resolve the configuration correctly. + +Linting warnings and errors come in two types, fixable and non-fixable. Fixable errors don't cause a non-zero exit code when ESLint is run with `--fix`, while non-fixable +errors always cause a non-zero exit code. + +ESLint provides several [formatters](https://eslint.org/docs/latest/use/formatters/) for processing the results objects it returns. To produce the human readable output that is printed to the command line, the `stylish` formatter +is loaded and used. + +## Calling Typescript from Node + +Most of the code for running Typescript functionality from Node was taken from [this](https://github.com/Microsoft/TypeScript/issues/6387) Github issue. + +<<< ../../../lib/buildtools/src/prebuild/tsc.ts {ts:line-numbers} + +The high level overview of this process is as follows: +1. Read the raw text from the `tsconfig.json` +2. Parse the `tsconfig.json` into a JSON object using `ts.parseConfigFileTextToJson` +3. Parse the JSON object into actual compiler options using `ts.parseJsonConfigFileContent`. This also returns an array of file names for parsing. +4. Use `ts.createProgram` to get the preliminary program for type checking only. +5. Call `typecheckProgram.emit()` to produce the typechecking results. +6. Combine the results with `ts.getPreEmitDiagonstics`. +7. If there were no typechecking errors and the `tsconfig.json` did not specify noEmit: true, use `ts.createProgram` again with the typecheck program to perform compilation and declaration file emission excluding test files. +8. Format the diagnostic objects using `ts.formatDiagnosticsWithColorAndContext` + +### Reading and Parsing `tsconfig.json` +The first three steps in the process involve reading the raw text from the `tsconfig.json` and then parsing it. At the end of it, `ts.parseJsonConfigFileContent` resolves all the inherited options and produces the compiler options +in use, as well as the file paths to the files that are to be processed. + +### Type Checking +The first time `ts.createProgram` is called, it is called with every single file as returned from `ts.parseJsonConfigFileContent`. However, it is called with `noEmit: true`. This prevents any Javascript and Typescript declaration files from being written. +This is important because we want test files to be type checked, but we don't want them to be compiled into Javascript and exported with the rest of the code. If they were included and the `tsconfig` was configured to produce outputs, the test files would end +up being written to the `outDir`. `typecheckProgram.emit` is called to perform the type checking. + +If there are no errors and the `tsconfig` was configured to produce outputs, `ts.createProgram` is called again. This time, test files are filtered out. `ts.createProgram` has a parameter for passing in the previous program object, allowing it to reuse +an existing program so it doesn't have to reinitialize the entire object again. `program.emit` is called to produce any compiled Javascript and Typescript declaration files. diff --git a/docs/src/buildtools/5-testing.md b/docs/src/buildtools/5-testing.md new file mode 100644 index 0000000000..8f8cbaeff3 --- /dev/null +++ b/docs/src/buildtools/5-testing.md @@ -0,0 +1,88 @@ +# Testing Using `Vitest` + +`vitest` comes with its own [Node API](https://vitest.dev/advanced/api/) that can be used to run tests from Node. To reduce the number of configuration files required, +the buildtools provide their own default `vitest` configuration for bundles and tabs. + +`vitest` supports a similar concept to workspaces known as [projects](https://vitest.dev/guide/projects.html). However, each `vitest` configuration file assumes that it isn't a child project under another root configuration. +So, when we use `mergeConfig` to inherit test configuration options from the root config, `vitest` tries to resolve `projects` field relative to that file. For example, if we have the following `vitest.config.ts`: + +```ts +// src/vitest.config.ts +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + name: 'Root Test Project', + projects: [ + 'src/bundles/*/vitest.config.ts' + ] + } +}) +``` + +And then we have a child config that inherits from the root config: +```ts +// src/bundles/curve/vitest.config.ts + +import { mergeConfig, defineProject } from 'vitest/config'; +import rootConfig from '../../vitest.config.ts'; + +export default mergeConfig( + rootConfig, + defineProject({ + test: { + name: 'Child Project' + } + }) +) +``` + +If we run `vitest` from the root of the repository, it will accurately detect `"Child Project"` is a valid project name. However, the buildtools need to be able to be executed from within the bundle's directory too. + +If we run the following command within the bundles directory, we will find that `vitest` cannot find a project with the name "Child Project": + +```sh +yarn vitest --project "Child Project" +``` + +This can be solved by configuring the root `vitest.config.ts`'s root parameter: + +```ts {6} +// src/vitest.config.ts +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + root: import.meta.dirname, + name: 'Root Test Project', + projects: [ + 'src/bundles/*/vitest.config.ts' + ] + } +}) +``` +This causes `vitest` to resolve the configuration correctly every time. + +## Vitest for specific Bundle/Tab + +However, even with the configuration above, we face an issue. `vitest` requires that each project specification matches a specific file. The pattern `src/bundles/*/vitest.config.ts` matches all `vitest.config.ts` files under the +top-level directory of each bundle. This is equivalent to `src/bundles/*`, since by default, `vitest` will try to find a file named `vitest.config`. However, it does mean that the specific configuration file must exist in that folder for it to be considered a project. + +This means that for a bundle/tab's tests to be detected, that bundle or tab would need to maintain its own `vitest` config. Alternatively, the specific configuration for that bundle/tab could be added directly to the root config. Neither of these solutions are particularly ideal. +The former would run the risk of misconfiguration, and the latter would cause the root configuration to become cluttered and defeat the purpose of splitting the repository into workspaces. + +To remove the need for each bundle/tab to contain its own Vitest config, the buildtools provide the configuration manually: + +<<< ../../../lib/buildtools/src/testing.ts + +Within the `runIndividualVitest` function, the bundle/tab you are trying to run tests for gets configured as its own test project that inherits options from the root configuration file. + +With this, running `yarn test` within a bundle or tab's directory structure will only run the tests for that tab or bundle without requiring the `--project` or directory filters. + +## Vitest for all bundles or all tabs or both + +The approach above works when you know that the current tab or bundle has tests that need to be run. In order to run tests for **all** bundles or **all** tabs at once, the buildtools would have to determine +which bundles and tabs have tests and which don't (as Vitest considers it an error if you give it a test project that it can't find tests for). + +Instead, all bundles and all tabs are configured as a test project each. Hence, under `src/bundles` and `src/tabs` you can find a `vitest.config.js` that is specific to bundle and tabs respectively. This shifts +the responsibility for determining which bundles and tabs contain tests onto `vitest` itself, and so it will not fail (unless for some reason every single bundle and tab test was removed). diff --git a/docs/src/buildtools/6-templates.md b/docs/src/buildtools/6-templates.md new file mode 100644 index 0000000000..2150aa37a5 --- /dev/null +++ b/docs/src/buildtools/6-templates.md @@ -0,0 +1,7 @@ +# Templates + +The template command is designed to produce simple scaffolding for developers trying to create a new bundle/tab. It is based around the `readline:promises` library to get user input. + +When run, `fs.cp` is called to copy the template directory to the new directory of the bundle or tab, and then the three json files (`tsconfig`, `manifest` and `package`) are written to that folder. +The command will read the version of Typescript from the root `package.json` and use it for both bundles and tabs, while specifically for tabs the versions of `@blueprintjs`, React, `react-dom` and their +types are used. diff --git a/docs/src/buildtools/index.md b/docs/src/buildtools/index.md index d39f3f412b..3d4c2075f3 100644 --- a/docs/src/buildtools/index.md +++ b/docs/src/buildtools/index.md @@ -8,15 +8,15 @@ The Source Academy Modules build tools are written in Typescript and designed to The build tools are comprised of several sections: -- [Command Handlers](./2-command) +- [Command Handlers](./1-command) - The actual code that runs command line argument parsing -- [Builders](./1-builders) +- [Builders](./3-builders/) - The code that converts a bundle or tab into its outputs -- [Prebuild Tasks](./prebuild) +- [Prebuild Tasks](./4-prebuild) - Code that is intended to be run before builders -- [Testing](./testing) +- [Testing](./5-testing) - Code for integrating `vitest` -- [Templates](./templates) +- [Templates](./6-templates) - Code for the template command -Explanations on how the build tools repository has been structured can be found [here](./structure) +Explanations on how the build tools repository has been structured can be found [here](./2-structure) diff --git a/docs/src/lib/index.md b/docs/src/lib/index.md index 246c4e4170..6e5834b377 100644 --- a/docs/src/lib/index.md +++ b/docs/src/lib/index.md @@ -1,4 +1,4 @@ # Common Modules Libraries -- [Lint Plugin](./lintplugin) -- [Modules Lib](./modules-lib/modules) +- [Lint Plugin](./lintplugin/) +- [Modules Lib](./modules-lib/) diff --git a/docs/src/modules/1-getting-started/1-overview.md b/docs/src/modules/1-getting-started/1-overview.md index a7d5596e12..20aace764d 100644 --- a/docs/src/modules/1-getting-started/1-overview.md +++ b/docs/src/modules/1-getting-started/1-overview.md @@ -16,8 +16,8 @@ These are the 3 main terms the project will be using to refer to the individual | **Term** | **Description** | **Links** | | ---------- | ------------------------------------------------------------------ | ---------------- | | **Module** | A set of **one** bundle and **zero or more** tabs. | | -| **Bundle** | The suite of functions that are provided by the module. | [Docs](./2_bundle/1_overview/1_overview) | -| **Tab** | A user interface used by the module. | [Docs](./3_tabs/1_overview/1_overview) | +| **Bundle** | The suite of functions that are provided by the module. | [Docs](../2-bundle/1-overview/1-overview) | +| **Tab** | A user interface used by the module. | [Docs](../3-tabs/1-overview) | ## Aims diff --git a/docs/src/modules/2-bundle/2-creating/2-creating.md b/docs/src/modules/2-bundle/2-creating/2-creating.md index 412d2b268b..5e76a59cc7 100644 --- a/docs/src/modules/2-bundle/2-creating/2-creating.md +++ b/docs/src/modules/2-bundle/2-creating/2-creating.md @@ -1,5 +1,5 @@ # Creating a Bundle -This page contains instructions for creating a new bundle from scratch. If you are looking to edit an existing bundle refer to [these](../editing) instructions instead. +This page contains instructions for creating a new bundle from scratch. If you are looking to edit an existing bundle refer to [these](../3-editing) instructions instead. ## Running the Template Command > [!TIP] > If necessary, before running the `template` command, you can run diff --git a/docs/src/modules/2-bundle/3-editing.md b/docs/src/modules/2-bundle/3-editing.md index a0e211ef3e..34be9dc1c5 100644 --- a/docs/src/modules/2-bundle/3-editing.md +++ b/docs/src/modules/2-bundle/3-editing.md @@ -1,5 +1,5 @@ # Editing an Existing Bundle -This page contains instructions for modifying an existing bundle. If you are creating a new bundle from scratch, refer to [these](../creating/) instructions instead. +This page contains instructions for modifying an existing bundle. If you are creating a new bundle from scratch, refer to [these](./2-creating/2-creating) instructions instead. ## Installing Dependencies To install **only** the dependencies required by the bundle you are modifying, use the command below: diff --git a/docs/src/modules/2-bundle/5-type_map.md b/docs/src/modules/2-bundle/5-type_map.md index 721df08ad4..3b6617353a 100644 --- a/docs/src/modules/2-bundle/5-type_map.md +++ b/docs/src/modules/2-bundle/5-type_map.md @@ -10,7 +10,7 @@ Regardless of the situation, type maps are a way for bundle authors to control t > Currently `js-slang` only performs any kind of type checking for modules when the typed Source variant is used. > This means that type maps are only used by the typed Source Variant. > -> This also means that documentation (mentioned [here](./documentation)) will not reflect the types specified by the type map +> This also means that documentation (mentioned [here](./4-documentation/4-documentation)) will not reflect the types specified by the type map Type Maps are opt-in. If the bundle does not provide a type map, then no type checking is performed on its exports, similar to how `skipLibCheck: true` is used in Typescript. @@ -79,7 +79,7 @@ export const translate = RuneFunctions.translate; > Notice that when re-exporting, the documentation is attached to the constant declaration and not the declaration in the class. This is so that > the documentation is properly applied to the exported function. > -> Also notice that the `@function` tag has been applied. More information about why this is necessary can be found [here](./documentation/#use-of-function) +> Also notice that the `@function` tag has been applied. More information about why this is necessary can be found [here](./4-documentation/4-documentation#use-of-function) Remember to export your type map from the bundle's entry point: ```ts diff --git a/docs/src/modules/3-tabs/2-creating.md b/docs/src/modules/3-tabs/2-creating.md index 47405f8564..afc947aa33 100644 --- a/docs/src/modules/3-tabs/2-creating.md +++ b/docs/src/modules/3-tabs/2-creating.md @@ -1,5 +1,5 @@ # Creating a Tab -This page contains instructions for creating a new bundle from scratch. If you are looking to edit an existing bundle refer to [these](./editing) instructions instead. +This page contains instructions for creating a new bundle from scratch. If you are looking to edit an existing bundle refer to [these](./3-editing) instructions instead. All tabs require at least one parent bundle. Before you create your tab, make sure you identify which bundle(s) are supposed to be the parent of your tab. @@ -47,4 +47,4 @@ NewTab This will create a new folder `src/tab/NewTab` with all the necessary files for creating your tab. -When you are ready to compile you can refer to [this](../compiling) page. +When you are ready to compile you can refer to [this](./5-compiling) page. diff --git a/docs/src/modules/3-tabs/3-editing.md b/docs/src/modules/3-tabs/3-editing.md index 1898a272f9..51cfe6d334 100644 --- a/docs/src/modules/3-tabs/3-editing.md +++ b/docs/src/modules/3-tabs/3-editing.md @@ -1,5 +1,5 @@ # Editing an Existing Tab -This page contains instructions for modifying an existing tab. If you are creating a new tab from scratch, refer to [these](./creating/) instructions instead. +This page contains instructions for modifying an existing tab. If you are creating a new tab from scratch, refer to [these](./2-creating) instructions instead. ## Installing Dependencies To install **only** the dependencies required by the tab you are modifying, use the command below: @@ -42,4 +42,4 @@ This adds the dependency to `devDependencies` instead. There are several React components defined under `@sourceacademy/modules-lib/tabs` which you are highly encouraged to use to help us retain a unified feel and look across all tabs. -You can see the documentation for these components [here]() +You can see the documentation for these components [here](/lib/modules-lib/) diff --git a/docs/src/modules/3-tabs/6-devserver/6-devserver.md b/docs/src/modules/3-tabs/6-devserver/6-devserver.md index 2c8cfb5b93..f079bb83dc 100644 --- a/docs/src/modules/3-tabs/6-devserver/6-devserver.md +++ b/docs/src/modules/3-tabs/6-devserver/6-devserver.md @@ -1,6 +1,6 @@ # Using the Development Server -Normally, tabs need to be displayed using the frontend. If you wish to test your tabs using a copy of the frontend, follow the instructions [here](../frontend). +Normally, tabs need to be displayed using the frontend. If you wish to test your tabs using a copy of the frontend, follow the instructions [here](../7-frontend). The Development Server (or dev server, for short) is a lightweight version of the frontend that provides developers with a copy of the Source Academy playground so that they can run their bundle code and see the actual graphic interface of their tabs. @@ -13,7 +13,7 @@ yarn devserver > [!NOTE] > Because the dev server relies on Vite which needs to pre-bundle its dependencies, you will need to have compiled your tab -> using the steps listed [here](./compiling) before starting the dev server. +> using the steps listed [here](../5-compiling) before starting the dev server. You will also need to start the modules server using `yarn serve`. diff --git a/docs/src/modules/index.md b/docs/src/modules/index.md index b08da30778..49d758a8b7 100644 --- a/docs/src/modules/index.md +++ b/docs/src/modules/index.md @@ -2,4 +2,4 @@ Refer to the sidebar on the left for information on the different aspects of developing a module for Source Academy. -Alternatively, you may want to view the [Modules Overview](./1-getting-started/2-overview.md) before beginning. +Alternatively, you may want to view the [Modules Overview](./1-getting-started/1-overview) before beginning. diff --git a/docs/src/repotools/workflows.md b/docs/src/repotools/workflows.md new file mode 100644 index 0000000000..e3f97c5fb8 --- /dev/null +++ b/docs/src/repotools/workflows.md @@ -0,0 +1,38 @@ +# Github Workflows + +This repository relies on three different workflows: + +1. On pull requests to the main branch +2. On pushing to any branch +3. Deployment to Github pages + +## Pull Request Workflow + +The full definition of the workflow is below: + +<<< ../../../.github/workflows/pull-request.yml + +The first job, `paths-filter`, determines what files are being changed during the pull request. This then allows us to determine if we need to run +the entire workflow, or only some jobs. For example, if no devserver code was modified, the `tsc`, tests and linting of the devserver is skipped. + +However, since the devserver does rely on the compiled tabs, it becomes necessary to build the bundles and tabs before performing devserver actions. + +## `pre-push` Git Hook +This hook is run every time you try to push to a branch on the remote. The pre-push git hook is configured as follows: + +<<< ../../../.husky/pre-push {sh} + +The first thing it does is validate that the repository's constraints have been fulfilled using `yarn constraints`. + + +Then it runs the `tsc`, linting and testing for the bundles and tabs that have been modified (with reference to `master`). This feature is provided by Yarn using the `--since` filter. + +Finally, it runs `tsc`, lints and tests the libraries and devserver, but also only if they have been modified with reference to `master`. + +## Deployment to Github Pages + +Upon any successful merge into `master`, this action runs to deploy the modules site onto Github pages to automatically serve the default modules and their documentation. + +Its configuration is shown below: + +<<< ../../../.github/workflows/pages-deploy.yml diff --git a/eslint.config.js b/eslint.config.js index 7df8d97701..7c12a41c2d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -26,6 +26,7 @@ export default tseslint.config( 'lib/buildtools/bin', 'lib/buildtools/src/build/__test_mocks__', 'lib/lintplugin/dist.js', + 'docs/.vitepress/cache', 'devserver/vite.config.ts', '**/dist/**', 'src/**/samples/**', @@ -59,6 +60,10 @@ export default tseslint.config( name: 'Markdown Files', extends: [markdown.configs.recommended], files: ['**/*.md'], + ignores: [ + // These are generated via Typedoc, we don't have to lint them + 'docs/src/lib/modules-lib/**/*.md' + ], language: 'markdown/gfm', languageOptions: { // @ts-expect-error typescript eslint doesn't recognize this property diff --git a/package.json b/package.json index 46249fe9e5..6032651c0f 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,11 @@ "tsc:modules": "Run the Typescript compiler for bundle and tabs only" }, "scripts": { - "build:docs": "buildtools build docs", + "build:docs": "buildtools build docs && yarn workspaces foreach -A --include \"@sourceacademy/modules-docserver\" run build", "build:libs": "yarn workspaces foreach -ptW --from \"./lib/*\" run build", "build:modules": "buildtools build all", "devserver": "devserver", - "lint:all": "eslint lib devserver && yarn lint:modules", + "lint:all": "eslint lib devserver docs && yarn lint:modules", "lint:inspect": "yarn dlx eslint --inspect-config", "lint:modules": "yarn workspaces foreach -j 5 -pW --from \"./src/{bundles,tabs}\" run lint", "preinstall": "yarn build:libs", diff --git a/yarn.lock b/yarn.lock index ca8516b1e6..5b9a6d312b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3212,16 +3212,6 @@ __metadata: languageName: unknown linkType: soft -"@sourceacademy/modules-devdocs@workspace:docs": - version: 0.0.0-use.local - resolution: "@sourceacademy/modules-devdocs@workspace:docs" - dependencies: - "@sourceacademy/modules-lib": "workspace:^" - vitepress: "npm:^1.6.3" - vitepress-sidebar: "npm:^1.31.1" - languageName: unknown - linkType: soft - "@sourceacademy/modules-devserver@workspace:^, @sourceacademy/modules-devserver@workspace:devserver": version: 0.0.0-use.local resolution: "@sourceacademy/modules-devserver@workspace:devserver" @@ -3255,6 +3245,16 @@ __metadata: languageName: unknown linkType: soft +"@sourceacademy/modules-docserver@workspace:docs": + version: 0.0.0-use.local + resolution: "@sourceacademy/modules-docserver@workspace:docs" + dependencies: + "@sourceacademy/modules-lib": "workspace:^" + vitepress: "npm:^1.6.3" + vitepress-sidebar: "npm:^1.31.1" + languageName: unknown + linkType: soft + "@sourceacademy/modules-lib@workspace:^, @sourceacademy/modules-lib@workspace:lib/modules-lib": version: 0.0.0-use.local resolution: "@sourceacademy/modules-lib@workspace:lib/modules-lib" From fda04fd0f06ade2bb00470c98089dc068d9feeab Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Fri, 27 Jun 2025 22:23:56 +0800 Subject: [PATCH 094/112] Incorporate JSON validation errors properly --- lib/buildtools/src/__tests__/fixtures.ts | 9 + lib/buildtools/src/__tests__/utils.test.ts | 8 +- lib/buildtools/src/build/index.ts | 10 +- lib/buildtools/src/build/manifest.ts | 219 ------------ .../{ => manifest}/__tests__/manifest.test.ts | 117 ++++--- .../src/build/manifest/formatters.ts | 6 + lib/buildtools/src/build/manifest/index.ts | 327 ++++++++++++++++++ lib/buildtools/src/build/manifest/types.ts | 59 ++++ .../build/modules/__tests__/building.test.ts | 2 +- .../src/build/modules/__tests__/tab.test.ts | 15 +- lib/buildtools/src/build/modules/index.ts | 2 +- .../src/build/modules/manifest.schema.json | 5 - .../src/commands/__tests__/buildAll.test.ts | 2 +- .../src/commands/__tests__/lint.test.ts | 2 +- .../src/commands/__tests__/list.test.ts | 125 +++++++ .../src/commands/__tests__/template.test.ts | 9 +- lib/buildtools/src/commands/build.ts | 57 ++- lib/buildtools/src/commands/commandUtils.ts | 6 +- lib/buildtools/src/commands/list.ts | 37 +- lib/buildtools/src/commands/main.ts | 3 +- lib/buildtools/src/commands/prebuild.ts | 51 ++- lib/buildtools/src/commands/testing.ts | 2 +- .../src/prebuild/__tests__/tsc.test.ts | 1 + lib/buildtools/src/prebuild/lint.ts | 117 ++++++- lib/buildtools/src/templates/bundle.ts | 12 +- lib/buildtools/src/templates/tab.ts | 13 +- lib/buildtools/src/utils.ts | 21 ++ 27 files changed, 881 insertions(+), 356 deletions(-) delete mode 100644 lib/buildtools/src/build/manifest.ts rename lib/buildtools/src/build/{ => manifest}/__tests__/manifest.test.ts (57%) create mode 100644 lib/buildtools/src/build/manifest/formatters.ts create mode 100644 lib/buildtools/src/build/manifest/index.ts create mode 100644 lib/buildtools/src/build/manifest/types.ts create mode 100644 lib/buildtools/src/commands/__tests__/list.test.ts diff --git a/lib/buildtools/src/__tests__/fixtures.ts b/lib/buildtools/src/__tests__/fixtures.ts index fa34790771..18c909862f 100644 --- a/lib/buildtools/src/__tests__/fixtures.ts +++ b/lib/buildtools/src/__tests__/fixtures.ts @@ -1,3 +1,12 @@ import { resolve } from 'path'; +import { expect } from 'vitest'; export const testMocksDir = resolve(import.meta.dirname, '..', '__test_mocks__'); + +export function expectIsSuccess(value: string): asserts value is 'success' { + expect(value).toEqual('success'); +} + +export function expectIsError(severity: string): asserts severity is 'error' { + expect(severity).toEqual('error'); +} diff --git a/lib/buildtools/src/__tests__/utils.test.ts b/lib/buildtools/src/__tests__/utils.test.ts index 4bd9f40e9f..d5fbb22505 100644 --- a/lib/buildtools/src/__tests__/utils.test.ts +++ b/lib/buildtools/src/__tests__/utils.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from 'vitest'; -import { compareSeverity, findSeverity, type Severity } from '../utils.js'; +import { compareSeverity, filterAsync, findSeverity, type Severity } from '../utils.js'; describe('test findSeverity', () => { const cases: Severity[][] = [ @@ -32,3 +32,9 @@ describe('test compareSeverity', () => { expect(compareSeverity(lhs, rhs)).toEqual(expected); }); }); + +test('filterAsync', async () => { + const objects = [1, 2, 3, 4, 5, 6]; + const results = await filterAsync(objects, each => Promise.resolve(each % 2 == 0)); + expect(results).toMatchObject([2, 4, 6]); +}); diff --git a/lib/buildtools/src/build/index.ts b/lib/buildtools/src/build/index.ts index 17a2273dd3..c26f661186 100644 --- a/lib/buildtools/src/build/index.ts +++ b/lib/buildtools/src/build/index.ts @@ -1,13 +1,15 @@ import fs from 'fs/promises'; import { getTabsDir } from '../getGitRoot.js'; import type { PrebuildOptions } from '../prebuild/index.js'; +import type { ResolvedBundle } from '../types.js'; import { buildDocs } from './docs/index.js'; -import { resolveAllBundles, writeManifest } from './manifest.js'; +import { writeManifest } from './manifest/index.js'; import { buildBundles, buildTabs } from './modules/index.js'; -export default async function buildAll(bundlesDir: string, opts: PrebuildOptions, outDir: string) { - const manifest = await resolveAllBundles(bundlesDir); - +/** + * Build bundles, tabs, documentation and writes the manifest to the output directory + */ +export default async function buildAll(manifest: Record, opts: PrebuildOptions, outDir: string) { await fs.mkdir(outDir, { recursive: true }); return Promise.all([ diff --git a/lib/buildtools/src/build/manifest.ts b/lib/buildtools/src/build/manifest.ts deleted file mode 100644 index 6835adfe3b..0000000000 --- a/lib/buildtools/src/build/manifest.ts +++ /dev/null @@ -1,219 +0,0 @@ -import fs from 'fs/promises'; -import pathlib from 'path'; -import { validate } from 'jsonschema'; -import uniq from 'lodash/uniq.js'; -import { getTabsDir } from '../getGitRoot.js'; -import type { BundleManifest, ManifestResult, ModulesManifest, ResolvedBundle, ResolvedTab } from '../types.js'; -import { isNodeError } from '../utils.js'; -import { createBuilder } from './buildUtils.js'; -import manifestSchema from './modules/manifest.schema.json' with { type: 'json' }; - -/** - * Checks that the given bundle manifest was correctly specified - */ -export async function getBundleManifest(directory: string, tabCheck?: boolean): Promise { - let manifestStr: string; - - try { - manifestStr = await fs.readFile(`${directory}/manifest.json`, 'utf-8'); - } catch (error) { - if (isNodeError(error) && error.code === 'ENOENT') { - return undefined; - } - throw error; - } - - let versionStr: string | undefined; - try { - const rawPackageJson = await fs.readFile(`${directory}/package.json`, 'utf-8') - ;({ version: versionStr } = JSON.parse(rawPackageJson)); - } catch (error) { - if (isNodeError(error) && error.code === 'ENOENT') { - return undefined; - } - throw error; - } - - const rawManifest = JSON.parse(manifestStr) as BundleManifest; - validate(rawManifest, manifestSchema, { throwError: true }); - - const manifest: BundleManifest = { - ...rawManifest, - tabs: !rawManifest.tabs ? rawManifest.tabs : rawManifest.tabs.map(each => each.trim()), - version: versionStr, - }; - - // Make sure that all the tabs specified exist - if (tabCheck && manifest.tabs) { - const tabsDir = await getTabsDir(); - await Promise.all(manifest.tabs.map(async tabName => { - const resolvedTab = await resolveSingleTab(`${tabsDir}/${tabName}`); - if (resolvedTab === undefined) throw new Error(`Failed to find tab with name '${tabName}'!`); - })); - } - - return manifest; -} - -/** - * Get all bundle manifests - */ -export async function getBundleManifests(bundlesDir: string, tabCheck?: boolean): Promise { - let subdirs: string[]; - try { - subdirs = await fs.readdir(bundlesDir); - } catch (error) { - if (isNodeError(error) && error.code === 'ENOENT') { - return {}; - } - - throw error; - } - - const manifests = await Promise.all(subdirs.map(async fileName => { - const fullPath = pathlib.join(bundlesDir, fileName); - const stats = await fs.stat(fullPath); - if (stats.isDirectory()) { - const manifest = await getBundleManifest(fullPath, tabCheck); - if (manifest === undefined) return undefined; - return [fileName, manifest] as [string, BundleManifest]; - } - - return undefined; - })); - - return manifests.reduce((res, entry) => { - if (entry === undefined) return res; - - const [name, manifest] = entry; - - return { - ...res, - [name]: manifest - }; - }, {} as Record); -} - -export async function resolveSingleBundle(bundleDir: string): Promise { - const fullyResolved = pathlib.resolve(bundleDir); - - const stats = await fs.stat(fullyResolved); - if (!stats.isDirectory()) return undefined; - - const manifest = await getBundleManifest(fullyResolved); - if (!manifest) return undefined; - - try { - const entryPoint = `${fullyResolved}/src/index.ts`; - await fs.access(entryPoint, fs.constants.R_OK); - } catch (error) { - if (!isNodeError(error) || error.code !== 'ENOENT') throw error; - return undefined; - } - - const bundleName = pathlib.basename(fullyResolved); - return { - type: 'bundle', - name: bundleName, - manifest, - directory: fullyResolved - }; -} - -/** - * Find all the bundles with the given directory and returns their - * resolved information - */ -export async function resolveAllBundles(bundlesDir: string) { - const subdirs = await fs.readdir(bundlesDir); - const manifests = await Promise.all(subdirs.map(subdir => { - const fullPath = pathlib.join(bundlesDir, subdir); - return resolveSingleBundle(fullPath); - })); - - return manifests.reduce((res, entry) => { - if (entry === undefined) return res; - - return { - ...res, - [entry.name]: entry - }; - }, {} as Record); -} - -async function resolvePaths(...paths: string[]) { - for (const path of paths) { - try { - await fs.access(path, fs.constants.R_OK); - return path; - } catch (error) { - if (isNodeError(error) && error.code !== 'ENOENT') throw error; - } - } - - return undefined; -} - -export const { - builder: writeManifest, - formatter: formatManifestResult -} = createBuilder<[bundles: Record], ManifestResult>(async (outDir, resolvedBundles) => { - try { - const toWrite = Object.entries(resolvedBundles).reduce((res, [key, { manifest }]) => ({ - ...res, - [key]: manifest - }), {}); - const outpath = `${outDir}/modules.json`; - await fs.writeFile(outpath, JSON.stringify(toWrite, null, 2)); - return { - severity: 'success', - assetType: 'manifest', - message: `Manifest written to ${outpath}` - }; - } catch (error) { - return { - severity: 'error', - assetType: 'manifest', - message: `${error}` - }; - } -}); - -export async function resolveSingleTab(tabDir: string): Promise { - const fullyResolved = pathlib.resolve(tabDir); - const tabPath = await resolvePaths( - `${fullyResolved}/src/index.tsx`, - `${fullyResolved}/index.tsx` - ); - - if (tabPath === undefined) return undefined; - - return { - type: 'tab', - directory: fullyResolved, - entryPoint: tabPath, - name: pathlib.basename(fullyResolved) - }; -} - -export async function resolveAllTabs(bundlesDir: string, tabsDir: string) { - const bundlesManifest = await resolveAllBundles(bundlesDir); - const tabNames = uniq(Object.values(bundlesManifest).flatMap(({ manifest: { tabs } }) => tabs ?? [])); - - const resolvedTabs = await Promise.all(tabNames.map(tabName => resolveSingleTab(`${tabsDir}/${tabName}`))); - - return resolvedTabs.reduce((res, tab) => tab === undefined - ? res - : { - ...res, - [tab.name]: tab - }, {} as Record); -} - -export async function resolveEitherBundleOrTab(directory: string) { - const bundle = await resolveSingleBundle(directory); - if (bundle) return bundle; - - const tab = await resolveSingleTab(directory); - return tab; -} diff --git a/lib/buildtools/src/build/__tests__/manifest.test.ts b/lib/buildtools/src/build/manifest/__tests__/manifest.test.ts similarity index 57% rename from lib/buildtools/src/build/__tests__/manifest.test.ts rename to lib/buildtools/src/build/manifest/__tests__/manifest.test.ts index 1ef92329b9..6909c5986b 100644 --- a/lib/buildtools/src/build/__tests__/manifest.test.ts +++ b/lib/buildtools/src/build/manifest/__tests__/manifest.test.ts @@ -1,99 +1,118 @@ import fs from 'fs/promises'; +import { ValidationError } from 'jsonschema'; import { describe, expect, it, test, vi } from 'vitest'; -import { testMocksDir } from '../../__tests__/fixtures.js'; -import { getBundleManifest, getBundleManifests, resolveAllBundles, resolveSingleBundle } from '../manifest.js'; +import { expectIsError, expectIsSuccess, testMocksDir } from '../../../__tests__/fixtures.js'; +import { getBundleManifest, getBundleManifests, resolveAllBundles, resolveSingleBundle } from '../index.js'; +import { MissingTabError, type GetBundleManifestSuccess } from '../types.js'; -vi.mock(import('../../getGitRoot.js')); +vi.mock(import('../../../getGitRoot.js')); -describe('Test bundle manifest schema validation', () => { +describe('Test bundle manifest schema validation succeeds', () => { const mockedReadFile = vi.spyOn(fs, 'readFile'); test('Valid Schema', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": [] }'); mockedReadFile.mockResolvedValueOnce('{ "version": "1.0.0" }'); - await expect(getBundleManifest('yes')) - .resolves - .toMatchObject({ + const expected: GetBundleManifestSuccess = { + severity: 'success', + manifest: { tabs: [], version: '1.0.0' - }); + } + }; + + await expect(getBundleManifest('yes')) + .resolves + .toMatchObject(expected); }); - test('Valid Schema with tabs without verification', async () => { + test('Valid Schema with tabs without verification succeeds', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": ["tab0", "tab1"] }'); mockedReadFile.mockResolvedValueOnce('{ "version": "1.0.0" }'); - await expect(getBundleManifest('yes', false)) - .resolves - .toMatchObject({ + const expected: GetBundleManifestSuccess = { + severity: 'success', + manifest: { tabs: ['tab0', 'tab1'], version: '1.0.0' - }); + } + }; + + await expect(getBundleManifest('yes', false)) + .resolves + .toMatchObject(expected); }); - test('Valid schema with tabs and verification', async () => { + test('Valid schema with tabs and verification succeeds', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": ["tab0"] }'); mockedReadFile.mockResolvedValueOnce('{}'); + const expected: GetBundleManifestSuccess = { + severity: 'success', + manifest: { + tabs: ['tab0'] + } + }; + await expect(getBundleManifest('yes', true)) .resolves - .toMatchObject({ - tabs: ['tab0'] - }); + .toMatchObject(expected); }); test('Valid schema with invalid tab verification', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": ["tab2"] }'); mockedReadFile.mockResolvedValueOnce('{}'); - await expect(getBundleManifest('yes', true)) - .rejects.toThrow(); + const result = await getBundleManifest('yes', true); + expect(result).not.toBeUndefined(); + expectIsError(result!.severity); + + expect(result!.errors.length).toEqual(1); + const [err] = result!.errors; + expect(err).toBeInstanceOf(MissingTabError); }); test('Schema with additional properties', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": ["tab0", "tab1"], "unknown": true }'); mockedReadFile.mockResolvedValueOnce('{}'); - await expect(getBundleManifest('yes')) - .rejects - .toThrow(); + const result = await getBundleManifest('yes'); + expect(result).not.toBeUndefined(); + expectIsError(result!.severity); + + expect(result!.errors.length).toEqual(1); + const [err] = result!.errors; + expect(err).toBeInstanceOf(ValidationError); }); test('Schema with invalid requires', async () => { mockedReadFile.mockResolvedValueOnce('{ "tabs": ["tab0", "tab1"], "requires": "yes" }'); mockedReadFile.mockResolvedValueOnce('{}'); - await expect(getBundleManifest('yes')) - .rejects - .toThrow(); + const result = await getBundleManifest('yes'); + expect(result).not.toBeUndefined(); + expectIsError(result!.severity); + + expect(result!.errors.length).toEqual(1); + const [err] = result!.errors; + expect(err).toBeInstanceOf(ValidationError); }); }); describe('Test getBundleManifests', () => { const mockedReadDir = vi.spyOn(fs, 'readdir'); - it('should return undefined when the bundles directory doesn\'t exist', async () => { - mockedReadDir.mockRejectedValueOnce(new class extends Error { + it('should return the error if readdir throws an error', async () => { + const errorObject = new class extends Error { code = 'ENOENT'; - }); + }; - await expect(getBundleManifests('/src/bundles')).resolves.toEqual({}); - }); - - it('should throw the error if the error code isn\'t ENOENT', async () => { - mockedReadDir.mockRejectedValueOnce(new class extends Error { - code = 'ENOPERM'; - }); - - await expect(getBundleManifests('/src/bundles')).rejects.toThrowError(); - }); - - it('should throw any other error', async () => { - const err = new Error('help me'); - mockedReadDir.mockRejectedValueOnce(err); + mockedReadDir.mockRejectedValueOnce(errorObject); - await expect(getBundleManifests('/src/bundles')).rejects.toThrowError(err); + const result = await getBundleManifests('/src/bundles'); + expectIsError(result.severity); + expect(result.errors[0]).toBe(errorObject); }); }); @@ -102,8 +121,10 @@ describe('Test resolveSingleBundle', () => { const resolved = await resolveSingleBundle(`${testMocksDir}/bundles/test0`); expect(resolved).not.toBeUndefined(); - expect(resolved!.name).toEqual('test0'); - expect(resolved!.manifest.tabs).toEqual(['tab0']); + expectIsSuccess(resolved!.severity); + + expect(resolved!.bundle.name).toEqual('test0'); + expect(resolved!.bundle.manifest.tabs).toEqual(['tab0']); }); it('Doesn\'t consider a directory missing a manifest as a bundle', async () => { @@ -129,7 +150,9 @@ describe('Test resolveAllBundles', () => { it('Properly detects bundles and non-bundles', async () => { const resolved = await resolveAllBundles(`${testMocksDir}/bundles`); - expect(resolved.test0).toMatchObject({ + expectIsSuccess(resolved.severity); + + expect(resolved.bundles.test0).toMatchObject({ name: 'test0', directory: `${testMocksDir}/bundles/test0`, manifest: { @@ -137,7 +160,7 @@ describe('Test resolveAllBundles', () => { } }); - expect(resolved.test1).toMatchObject({ + expect(resolved.bundles.test1).toMatchObject({ name: 'test1', directory: `${testMocksDir}/bundles/test1`, manifest: {} diff --git a/lib/buildtools/src/build/manifest/formatters.ts b/lib/buildtools/src/build/manifest/formatters.ts new file mode 100644 index 0000000000..9eec61e64e --- /dev/null +++ b/lib/buildtools/src/build/manifest/formatters.ts @@ -0,0 +1,6 @@ +import type { ResolveSingleBundleError, ResolveAllBundlesError } from './types.js'; + +export function formatResolveBundleErrors(result: ResolveAllBundlesError | ResolveSingleBundleError): string { + const strings = result.errors.map(each => `${each}`); + return strings.join('\n'); +} diff --git a/lib/buildtools/src/build/manifest/index.ts b/lib/buildtools/src/build/manifest/index.ts new file mode 100644 index 0000000000..5071756112 --- /dev/null +++ b/lib/buildtools/src/build/manifest/index.ts @@ -0,0 +1,327 @@ +import type { Dirent } from 'fs'; +import fs from 'fs/promises'; +import pathlib from 'path'; +import { validate } from 'jsonschema'; +import uniq from 'lodash/uniq.js'; +import { getTabsDir } from '../../getGitRoot.js'; +import type { BundleManifest, ManifestResult, ResolvedBundle, ResolvedTab } from '../../types.js'; +import { filterAsync, isNodeError } from '../../utils.js'; +import { createBuilder } from '../buildUtils.js'; +import manifestSchema from '../modules/manifest.schema.json' with { type: 'json' }; +import { + MissingEntryPointError, + MissingTabError, + type GetBundleManifestResult, + type ResolveAllBundlesResult, + type ResolveAllTabsResult, + type ResolveSingleBundleResult +} from './types.js'; + +/** + * Checks that the given directory has contains a bundle and that it has the correct + * format and structures. Returns `undefined` if no bundle was detected. + */ +export async function getBundleManifest(directory: string, tabCheck?: boolean): Promise { + let manifestStr: string; + + try { + manifestStr = await fs.readFile(`${directory}/manifest.json`, 'utf-8'); + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') { + return undefined; + } + return { severity: 'error', errors: [error] }; + } + + let versionStr: string | undefined; + try { + const rawPackageJson = await fs.readFile(`${directory}/package.json`, 'utf-8') + ;({ version: versionStr } = JSON.parse(rawPackageJson)); + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') { + return undefined; + } + return { severity: 'error', errors: [error] }; + } + + const rawManifest = JSON.parse(manifestStr) as BundleManifest; + const validateResult = validate(rawManifest, manifestSchema, { throwError: false }); + + if (validateResult.errors.length > 0) { + return { + severity: 'error', + errors: validateResult.errors + }; + } + + const manifest: BundleManifest = { + ...rawManifest, + tabs: !rawManifest.tabs ? rawManifest.tabs : rawManifest.tabs.map(each => each.trim()), + version: versionStr, + }; + + // Make sure that all the tabs specified exist + if (tabCheck && manifest.tabs) { + const tabsDir = await getTabsDir(); + const unknownTabs = await filterAsync(manifest.tabs, async tabName => { + const resolvedTab = await resolveSingleTab(`${tabsDir}/${tabName}`); + return resolvedTab === undefined; + }); + + if (unknownTabs.length > 0) { + return { + severity: 'error', + errors: unknownTabs.map(each => new MissingTabError(each)) + }; + } + } + + return { + severity: 'success', + manifest + }; +} + +type GetAllBundleManifestsResult = { + severity: 'error' + errors: unknown[] +} | { + severity: 'success' + manifests: Record +}; + +/** + * Get all bundle manifests + */ +export async function getBundleManifests(bundlesDir: string, tabCheck?: boolean): Promise { + let subdirs: Dirent[]; + try { + subdirs = await fs.readdir(bundlesDir, { withFileTypes: true }); + } catch (error) { + return { + severity: 'error', + errors: [error] + }; + } + + const manifests = await Promise.all(subdirs.map(async each => { + const fullPath = pathlib.join(bundlesDir, each.name); + if (each.isDirectory()) { + const manifest = await getBundleManifest(fullPath, tabCheck); + if (manifest === undefined) return undefined; + return [each.name, manifest] as [string, typeof manifest]; + } + + return undefined; + })); + + const [combinedManifests, errors] = manifests.reduce<[Record, unknown[]]>(([res, errors], entry) => { + if (entry === undefined) return [res, errors]; + const [name, manifest] = entry; + + if (manifest.severity === 'error') { + return [ + res, + [ + ...errors, + ...manifest.errors + ] + ]; + } + + return [ + { + ...res, + [name]: manifest.manifest, + }, + errors + ]; + + }, [{}, []]); + + if (errors.length > 0) { + return { + severity: 'error', + errors + }; + } + + return { + severity: 'success', + manifests: combinedManifests + }; +} + +/** + * Attempts to resolve the information at the given directory as a bundle. Returns `undefined` if no bundle was detected + * at the given directory. Otherwise returns with either the manifest of the bundle or corresponding errors. + */ +export async function resolveSingleBundle(bundleDir: string): Promise { + const fullyResolved = pathlib.resolve(bundleDir); + const bundleName = pathlib.basename(fullyResolved); + + const stats = await fs.stat(fullyResolved); + if (!stats.isDirectory()) return undefined; + + const manifest = await getBundleManifest(fullyResolved); + if (!manifest || manifest.severity === 'error') return manifest; + + try { + const entryPoint = `${fullyResolved}/src/index.ts`; + await fs.access(entryPoint, fs.constants.R_OK); + } catch (error) { + if (!isNodeError(error) || error.code !== 'ENOENT') throw error; + return { + severity: 'error', + errors: [new MissingEntryPointError('bundle', bundleName)] + }; + } + + return { + severity: 'success', + bundle: { + type: 'bundle', + name: bundleName, + manifest: manifest.manifest, + directory: fullyResolved + } + }; +} + +/** + * Find all the bundles with the given directory and returns their + * resolved information + */ +export async function resolveAllBundles(bundlesDir: string): Promise { + const subdirs = await fs.readdir(bundlesDir); + const manifests = await Promise.all(subdirs.map(subdir => { + const fullPath = pathlib.join(bundlesDir, subdir); + return resolveSingleBundle(fullPath); + })); + + const [combinedManifests, errors] = manifests.reduce<[Record, any[]]>(([res, errors], entry) => { + if (entry === undefined) return [res, errors]; + + if (entry.severity === 'error') { + return [ + res, + [ + ...errors, + ...entry.errors + ] + ]; + } + + return [ + { + ...res, + [entry.bundle.name]: entry.bundle + }, + errors + ]; + }, [{}, []]); + + if (errors.length > 0) { + return { + severity: 'error', + errors + }; + } + + return { + severity: 'success', + bundles: combinedManifests + }; +} + +async function resolvePaths(...paths: string[]) { + for (const path of paths) { + try { + await fs.access(path, fs.constants.R_OK); + return path; + } catch (error) { + if (isNodeError(error) && error.code !== 'ENOENT') throw error; + } + } + + return undefined; +} + +export const { + builder: writeManifest, + formatter: formatManifestResult +} = createBuilder<[bundles: Record], ManifestResult>(async (outDir, resolvedBundles) => { + try { + const toWrite = Object.entries(resolvedBundles).reduce((res, [key, { manifest }]) => ({ + ...res, + [key]: manifest + }), {}); + const outpath = `${outDir}/modules.json`; + await fs.writeFile(outpath, JSON.stringify(toWrite, null, 2)); + return { + severity: 'success', + assetType: 'manifest', + message: `Manifest written to ${outpath}` + }; + } catch (error) { + return { + severity: 'error', + assetType: 'manifest', + message: `${error}` + }; + } +}); + +export async function resolveSingleTab(tabDir: string): Promise { + const fullyResolved = pathlib.resolve(tabDir); + const tabPath = await resolvePaths( + `${fullyResolved}/src/index.tsx`, + `${fullyResolved}/index.tsx` + ); + + if (tabPath === undefined) return undefined; + + return { + type: 'tab', + directory: fullyResolved, + entryPoint: tabPath, + name: pathlib.basename(fullyResolved) + }; +} + +export async function resolveAllTabs(bundlesDir: string, tabsDir: string): Promise { + const bundlesManifest = await resolveAllBundles(bundlesDir); + if (bundlesManifest.severity === 'error') { + return bundlesManifest; + } + + const tabNames = uniq(Object.values(bundlesManifest.bundles).flatMap(({ manifest: { tabs } }) => tabs ?? [])); + + const resolvedTabs = await Promise.all(tabNames.map(tabName => resolveSingleTab(`${tabsDir}/${tabName}`))); + + const tabsManifest = resolvedTabs.reduce((res, tab) => tab === undefined + ? res + : { + ...res, + [tab.name]: tab + }, {} as Record); + + return { + severity: 'success', + tabs: tabsManifest + }; +} + +/** + * Attempts to resolve the given directory as a bundle or a tab. Returns `undefined` + * if it was unable to do either. + */ +export async function resolveEitherBundleOrTab(directory: string) { + const bundle = await resolveSingleBundle(directory); + if (!bundle || bundle.severity !== 'success') { + const tab = await resolveSingleTab(directory); + return tab; + } + + return bundle.bundle; +} diff --git a/lib/buildtools/src/build/manifest/types.ts b/lib/buildtools/src/build/manifest/types.ts new file mode 100644 index 0000000000..67aa7fd3ba --- /dev/null +++ b/lib/buildtools/src/build/manifest/types.ts @@ -0,0 +1,59 @@ +import type { ValidationError } from 'jsonschema'; +import type { BundleManifest, ResolvedBundle, ResolvedTab } from '../../types.js'; + +export interface GetBundleManifestError { + severity: 'error'; + errors: (ValidationError | unknown)[]; +} + +export interface GetBundleManifestSuccess { + severity: 'success'; + manifest: BundleManifest; +} + +export type GetBundleManifestResult = GetBundleManifestError | GetBundleManifestSuccess | undefined; + +interface ResolveSingleBundleSuccess { + severity: 'success'; + bundle: ResolvedBundle; +} + +export type ResolveSingleBundleError = GetBundleManifestError; + +export type ResolveSingleBundleResult = ResolveSingleBundleError | ResolveSingleBundleSuccess | undefined; + +export type ResolveAllBundlesError = GetBundleManifestError; + +interface ResolveAllBundlesSuccess { + severity: 'success', + bundles: Record +} + +export type ResolveAllBundlesResult = ResolveAllBundlesError | ResolveAllBundlesSuccess; + +interface ResolveAllTabsSuccess { + severity: 'success' + tabs: Record +} + +export type ResolveAllTabsError = GetBundleManifestError; +export type ResolveAllTabsResult = ResolveAllTabsError | ResolveAllTabsSuccess; + +export class MissingTabError extends Error { + constructor(public readonly name: string) { super(); } + + [Symbol.toStringTag]() { + return `Failed to find tab with name ${this.name}`; + } +} + +export class MissingEntryPointError extends Error { + constructor( + public readonly assetType: 'bundle' | 'tab', + public readonly name: string + ) { super(); } + + [Symbol.toStringTag]() { + return `Failed to find entry point for ${this.assetType} ${this.name}`; + } +} diff --git a/lib/buildtools/src/build/modules/__tests__/building.test.ts b/lib/buildtools/src/build/modules/__tests__/building.test.ts index 41158d4904..eff6792387 100644 --- a/lib/buildtools/src/build/modules/__tests__/building.test.ts +++ b/lib/buildtools/src/build/modules/__tests__/building.test.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import { beforeEach, expect, vi, test } from 'vitest'; import { testMocksDir } from '../../../__tests__/fixtures.js'; -import { writeManifest } from '../../manifest.js'; +import { writeManifest } from '../../manifest/index.js'; import { buildBundle } from '../bundle.js'; import { buildTab } from '../tab.js'; diff --git a/lib/buildtools/src/build/modules/__tests__/tab.test.ts b/lib/buildtools/src/build/modules/__tests__/tab.test.ts index 04ddecb5b6..0982119084 100644 --- a/lib/buildtools/src/build/modules/__tests__/tab.test.ts +++ b/lib/buildtools/src/build/modules/__tests__/tab.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, test, vi } from 'vitest'; -import { testMocksDir } from '../../../__tests__/fixtures.js'; -import { resolveSingleTab, resolveAllTabs } from '../../manifest.js'; +import { expectIsSuccess, testMocksDir } from '../../../__tests__/fixtures.js'; +import { resolveSingleTab, resolveAllTabs } from '../../manifest/index.js'; vi.mock(import('../../../getGitRoot.js')); @@ -31,15 +31,18 @@ describe('Test resolveSingleTab', () => { test('resolveAllTabs', async () => { const resolved = await resolveAllTabs(`${testMocksDir}/bundles`, `${testMocksDir}/tabs`); - expect(resolved).toHaveProperty('tab0'); - expect(resolved.tab0).toMatchObject({ + expectIsSuccess(resolved.severity); + const { tabs } = resolved; + + expect(tabs).toHaveProperty('tab0'); + expect(tabs.tab0).toMatchObject({ entryPoint: `${testMocksDir}/tabs/tab0/src/index.tsx`, directory: `${testMocksDir}/tabs/tab0`, name: 'tab0' }); - expect(resolved).toHaveProperty('tab1'); - expect(resolved.tab1).toMatchObject({ + expect(tabs).toHaveProperty('tab1'); + expect(tabs.tab1).toMatchObject({ entryPoint: `${testMocksDir}/tabs/tab1/index.tsx`, directory: `${testMocksDir}/tabs/tab1`, name: 'tab1' diff --git a/lib/buildtools/src/build/modules/index.ts b/lib/buildtools/src/build/modules/index.ts index 73f39c74fb..2d8665f1d4 100644 --- a/lib/buildtools/src/build/modules/index.ts +++ b/lib/buildtools/src/build/modules/index.ts @@ -2,7 +2,7 @@ import fs from 'fs/promises'; import uniq from 'lodash/uniq.js'; import { type PrebuildOptions, runBuilderWithPrebuild } from '../../prebuild/index.js'; import type { FullResult, ResolvedBundle, TabResultEntry } from '../../types.js'; -import { resolveSingleTab } from '../manifest.js'; +import { resolveSingleTab } from '../manifest/index.js'; import { buildBundle } from './bundle.js'; import { buildTab } from './tab.js'; diff --git a/lib/buildtools/src/build/modules/manifest.schema.json b/lib/buildtools/src/build/modules/manifest.schema.json index 97806a0011..51de167665 100644 --- a/lib/buildtools/src/build/modules/manifest.schema.json +++ b/lib/buildtools/src/build/modules/manifest.schema.json @@ -9,11 +9,6 @@ "type": "string" } }, - "version": { - "type": "string", - "description": "Version of your bundle", - "pattern": "^[0-9]+(\\.[0-9]){0,2}$" - }, "requires": { "enum": [1, 2, 3, 4], "description": "Minimum Source version required to run this bundle" diff --git a/lib/buildtools/src/commands/__tests__/buildAll.test.ts b/lib/buildtools/src/commands/__tests__/buildAll.test.ts index 5fbac36b1b..2057cccd3e 100644 --- a/lib/buildtools/src/commands/__tests__/buildAll.test.ts +++ b/lib/buildtools/src/commands/__tests__/buildAll.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest'; import * as html from '../../build/docs/html.js'; import * as json from '../../build/docs/json.js'; -import * as manifest from '../../build/manifest.js'; +import * as manifest from '../../build/manifest/index.js'; import * as bundles from '../../build/modules/bundle.js'; import * as tabs from '../../build/modules/tab.js'; import * as lint from '../../prebuild/lint.js'; diff --git a/lib/buildtools/src/commands/__tests__/lint.test.ts b/lib/buildtools/src/commands/__tests__/lint.test.ts index b9a3984d99..778a4c30be 100644 --- a/lib/buildtools/src/commands/__tests__/lint.test.ts +++ b/lib/buildtools/src/commands/__tests__/lint.test.ts @@ -1,6 +1,6 @@ import { ESLint } from 'eslint'; import { beforeEach, vi, expect, describe, test } from 'vitest'; -import * as manifest from '../../build/manifest.js'; +import * as manifest from '../../build/manifest/index.js'; import * as utils from '../../getGitRoot.js'; import { getLintCommand } from '../prebuild.js'; import { getCommandRunner } from './testingUtils.js'; diff --git a/lib/buildtools/src/commands/__tests__/list.test.ts b/lib/buildtools/src/commands/__tests__/list.test.ts new file mode 100644 index 0000000000..6c4fb27441 --- /dev/null +++ b/lib/buildtools/src/commands/__tests__/list.test.ts @@ -0,0 +1,125 @@ +import fs from 'fs/promises'; +import { describe, test, vi } from 'vitest'; +import { testMocksDir } from '../../__tests__/fixtures.js'; +import * as manifest from '../../build/manifest/index.js'; +import { getListBundlesCommand, getListTabsCommand } from '../list.js'; +import { getCommandRunner } from './testingUtils.js'; + +vi.mock(import('../../getGitRoot.js')); +vi.spyOn(console, 'log'); +vi.spyOn(console, 'error'); + +describe('Test list command with bundles', () => { + const mockedResolveSingleBundle = vi.spyOn(manifest, 'resolveSingleBundle'); + const mockedResolveAllBundles = vi.spyOn(manifest, 'resolveAllBundles'); + + const runCommand = getCommandRunner(getListBundlesCommand); + + test('Running command with no arguments', async ({ expect }) => { + await expect(runCommand()).commandSuccess(); + expect(console.log).toHaveBeenCalledTimes(1); + const [[data]] = vi.mocked(console.log).mock.calls; + expect(data).toMatch(/^Detected 2 bundles in .+:\n1. test0\n2. test1$/); + }); + + test('Running command when bundles directory has no bundles', async ({ expect }) => { + mockedResolveAllBundles.mockResolvedValueOnce({ + severity: 'success', + bundles: {} + }); + await expect(runCommand()).commandExit(); + + expect(console.error).toHaveBeenCalledTimes(1); + const [[data]] = vi.mocked(console.error).mock.calls; + expect(data).toMatch(/^No bundles in .+$/); + }); + + test('Running command in a directory that doesn\'t have a bundle', ({ expect }) => { + mockedResolveSingleBundle.mockResolvedValueOnce(undefined); + return expect(runCommand('somewhere')).commandExit(); + }); + + test('Running command in a directory that does have a bundle', async ({ expect }) => { + await expect(runCommand(`${testMocksDir}/bundles/test0`)).commandSuccess(); + + expect(console.log).toHaveBeenCalledTimes(1); + const [[data]] = vi.mocked(console.log).mock.calls; + const sanitized = (data as string).split('\n').join(''); + + const match = /^.+:(\{.+\})$/gm.exec(sanitized); + expect(match).not.toBeNull(); + + const parsed = JSON.parse(match![1]); + expect(parsed).toMatchObject({ + directory: `${testMocksDir}/bundles/test0`, + manifest: { + tabs: ['tab0'] + }, + name: 'test0' + }); + }); + + test('Bundle verification failure for a single bundle', async ({ expect }) => { + vi.spyOn(fs, 'readFile').mockResolvedValueOnce(`{ + "tabs": [], + "extraProperty": "" + }`); + + await expect(runCommand(`${testMocksDir}/bundles/test0`)).commandExit(); + }); + + test('Bundle verification failure for a single bundle, but when reading all bundles', async ({ expect }) => { + const mockedReadFile = vi.spyOn(fs, 'readFile'); + + mockedReadFile.mockResolvedValueOnce(`{ + "tabs": [], + "extraProperty": "" + }`); + + mockedReadFile.mockResolvedValueOnce(`{ + "version": "1.0.0" + }`); + + await expect(runCommand()).commandExit(); + }); +}); + +describe('Test list command with tabs', () => { + const mockedResolveSingleTab = vi.spyOn(manifest, 'resolveSingleTab'); + const mockedResolveAllTabs = vi.spyOn(manifest, 'resolveAllTabs'); + + const runCommand = getCommandRunner(getListTabsCommand); + + test('Running command with no arguments', async ({ expect }) => { + await expect(runCommand()).commandSuccess(); + + expect(console.log).toHaveBeenCalledTimes(1); + const [[data]] = vi.mocked(console.log).mock.calls; + expect(data).toMatch(/^Detected 2 tabs in .+:\n1. tab0\n2. tab1$/); + }); + + test('Running command when tabs directory has no tabs', async ({ expect }) => { + mockedResolveAllTabs.mockResolvedValueOnce({ + severity: 'success', + tabs: {} + }); + await expect(runCommand()).commandExit(); + + expect(console.error).toHaveBeenCalledTimes(1); + const [[data]] = vi.mocked(console.error).mock.calls; + expect(data).toMatch(/^No tabs in .+$/); + }); + + test('Running command in a directory that doesn\'t have a tab', ({ expect }) => { + mockedResolveSingleTab.mockResolvedValueOnce(undefined); + return expect(runCommand('somewhere')).commandExit(); + }); + + test('Running command in a directory that has a tab', async ({ expect }) => { + await expect(runCommand(`${testMocksDir}/tabs/tab0`)).commandSuccess(); + + expect(console.log).toHaveBeenCalledTimes(1); + const [[data]] = vi.mocked(console.log).mock.calls; + expect(data).toMatch(/^Tab 'tab0' found in .+$/); + }); +}); diff --git a/lib/buildtools/src/commands/__tests__/template.test.ts b/lib/buildtools/src/commands/__tests__/template.test.ts index 54884cf6f2..79fee0b83a 100644 --- a/lib/buildtools/src/commands/__tests__/template.test.ts +++ b/lib/buildtools/src/commands/__tests__/template.test.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import { beforeEach, describe, expect, it, test, vi } from 'vitest'; -import * as manifest from '../../build/manifest.js'; +import * as manifest from '../../build/manifest/index.js'; import * as gitRoot from '../../getGitRoot.js'; import { askQuestion } from '../../templates/print.js'; import getTemplateCommand from '../template.js'; @@ -30,8 +30,11 @@ vi.mock(import('../../templates/print.js'), async importActual => { }); vi.spyOn(manifest, 'getBundleManifests').mockResolvedValue({ - test0: { tabs: ['tab0'] }, - test1: {}, + severity: 'success', + manifests: { + test0: { tabs: ['tab0'] }, + test1: {}, + } }); const mockedAskQuestion = vi.mocked(askQuestion); diff --git a/lib/buildtools/src/commands/build.ts b/lib/buildtools/src/commands/build.ts index 9d60ebfe63..83d13452d0 100644 --- a/lib/buildtools/src/commands/build.ts +++ b/lib/buildtools/src/commands/build.ts @@ -3,7 +3,8 @@ import { Command } from '@commander-js/extra-typings'; import { initTypedocForSingleBundle } from '../build/docs/docsUtils.js'; import { buildDocs, buildJson } from '../build/docs/index.js'; import buildAll from '../build/index.js'; -import { resolveAllBundles, resolveSingleBundle , resolveSingleTab, writeManifest } from '../build/manifest.js'; +import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; +import { resolveAllBundles, resolveSingleBundle , resolveSingleTab, writeManifest } from '../build/manifest/index.js'; import { buildBundle, buildTab } from '../build/modules/index.js'; import { getBundlesDir, getOutDir } from '../getGitRoot.js'; import { runBuilderWithPrebuild } from '../prebuild/index.js'; @@ -20,10 +21,13 @@ export const getBuildBundleCommand = () => new Command('bundle') .addOption(lintOption) .option('--ci', 'Run in CI mode', !!process.env.CI) .action(async (bundleDir, opts) => { - const bundle = await resolveSingleBundle(bundleDir); - if (!bundle) logCommandErrorAndExit(`No bundle found at ${bundleDir}!`); + const result = await resolveSingleBundle(bundleDir); + if (!result) logCommandErrorAndExit(`No bundle found at ${bundleDir}!`); + else if (result.severity === 'error') { + logCommandErrorAndExit(formatResolveBundleErrors(result)); + } - const results = await runBuilderWithPrebuild(buildBundle, opts, bundle, outDir, 'bundle'); + const results = await runBuilderWithPrebuild(buildBundle, opts, result.bundle, outDir, 'bundle'); console.log(getResultString(results)); resultsProcessor(results, opts.ci); }); @@ -49,8 +53,11 @@ export const getBuildJsonCommand = () => new Command('json') .addOption(tscOption) .option('--ci', 'Run in CI mode', !!process.env.CI) .action(async (bundleDir, opts) => { - const bundle = await resolveSingleBundle(bundleDir); - if (!bundle) logCommandErrorAndExit(`No bundle found at ${bundleDir}!`); + const result = await resolveSingleBundle(bundleDir); + if (!result) logCommandErrorAndExit(`No bundle found at ${bundleDir}!`); + else if (result.severity === 'error') { + logCommandErrorAndExit(formatResolveBundleErrors(result)); + } const results = await runBuilderWithPrebuild( async (outDir, bundle: ResolvedBundle) => { @@ -58,7 +65,7 @@ export const getBuildJsonCommand = () => new Command('json') return buildJson(outDir, bundle, reflection); }, opts, - bundle, + result.bundle, outDir, 'json' ); @@ -71,22 +78,29 @@ export const getBuildDocsCommand = () => new Command('docs') .description('Build all documentation for all bundles') .option('--ci', 'Run in CI mode', !!process.env.CI) .action(async ({ ci }) => { - const manifest = await resolveAllBundles(bundlesDir); - await fs.mkdir(`${outDir}/jsons`, { recursive: true }); - const results = await buildDocs(manifest, outDir); - console.log(getResultString({ results })); - resultsProcessor({ results }, ci); + const manifestResult = await resolveAllBundles(bundlesDir); + if (manifestResult.severity === 'success') { + await fs.mkdir(`${outDir}/jsons`, { recursive: true }); + const results = await buildDocs(manifestResult.bundles, outDir); + console.log(getResultString({ results })); + resultsProcessor({ results }, ci); + } else { + logCommandErrorAndExit(formatResolveBundleErrors(manifestResult)); + } }); export const getBuildManifestCommand = () => new Command('manifest') .description('Write the module manifest to the output directory') .action(async () => { - const manifest = await resolveAllBundles(bundlesDir); - await fs.mkdir(outDir, { recursive: true }); - - const results = await writeManifest(outDir, manifest); - console.log(getResultString({ results })); - resultsProcessor({ results }, false); + const manifestResult = await resolveAllBundles(bundlesDir); + if (manifestResult.severity === 'success') { + await fs.mkdir(outDir, { recursive: true }); + const results = await writeManifest(outDir, manifestResult.bundles); + console.log(getResultString({ results })); + resultsProcessor({ results }, false); + } else { + logCommandErrorAndExit(formatResolveBundleErrors(manifestResult)); + } }); export const getBuildAllCommand = () => new Command('all') @@ -95,7 +109,12 @@ export const getBuildAllCommand = () => new Command('all') .addOption(lintOption) .option('--ci', 'Run in CI mode', !!process.env.CI) .action(async opts => { - const [manifest, [html, ...json], bundles, tabs] = await buildAll(bundlesDir, opts, outDir); + const manifestResult = await resolveAllBundles(bundlesDir); + if (manifestResult.severity === 'error') { + logCommandErrorAndExit(formatResolveBundleErrors(manifestResult)); + } + + const [manifest, [html, ...json], bundles, tabs] = await buildAll(manifestResult.bundles, opts, outDir); const results: FullResult[] = [ { results: manifest }, { results: html }, diff --git a/lib/buildtools/src/commands/commandUtils.ts b/lib/buildtools/src/commands/commandUtils.ts index 21488281a3..c5752e998f 100644 --- a/lib/buildtools/src/commands/commandUtils.ts +++ b/lib/buildtools/src/commands/commandUtils.ts @@ -2,7 +2,7 @@ import { Option } from '@commander-js/extra-typings'; import chalk from 'chalk'; import { formatHtmlResult } from '../build/docs/html.js'; import { formatJsonResult } from '../build/docs/json.js'; -import { formatManifestResult } from '../build/manifest.js'; +import { formatManifestResult } from '../build/manifest/index.js'; import { formatBundleResult } from '../build/modules/bundle.js'; import { formatTabResult } from '../build/modules/tab.js'; import { formatLintResult } from '../prebuild/lint.js'; @@ -103,7 +103,9 @@ export function resultsProcessor({ resu } } -export function logCommandErrorAndExit(message: string, code: number = 1): never { +export function logCommandErrorAndExit(message: unknown[], code?: number): never; +export function logCommandErrorAndExit(message: string, code?: number): never; +export function logCommandErrorAndExit(message: unknown[] | string, code: number = 1): never { console.error(chalk.redBright(message)); process.exit(code); } diff --git a/lib/buildtools/src/commands/list.ts b/lib/buildtools/src/commands/list.ts index 37856d159a..afc2331c2e 100644 --- a/lib/buildtools/src/commands/list.ts +++ b/lib/buildtools/src/commands/list.ts @@ -1,6 +1,8 @@ import { Command } from '@commander-js/extra-typings'; import chalk from 'chalk'; -import { resolveAllBundles, resolveAllTabs, resolveSingleBundle, resolveSingleTab } from '../build/manifest.js'; +import omit from 'lodash/omit.js'; +import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; +import { resolveAllBundles, resolveAllTabs, resolveSingleBundle, resolveSingleTab } from '../build/manifest/index.js'; import { getBundlesDir, getTabsDir } from '../getGitRoot.js'; import { logCommandErrorAndExit } from './commandUtils.js'; @@ -10,21 +12,29 @@ export const getListBundlesCommand = () => new Command('bundle') .action(async directory => { if (directory === undefined) { const bundlesDir = await getBundlesDir(); - const manifest = await resolveAllBundles(bundlesDir); - const bundleNames = Object.keys(manifest); + const manifestResult = await resolveAllBundles(bundlesDir); + if (manifestResult.severity === 'error') { + logCommandErrorAndExit(formatResolveBundleErrors(manifestResult)); + } + + const bundleNames = Object.keys(manifestResult.bundles); - if (bundleNames.length > 0 ) { + if (bundleNames.length > 0) { const bundlesStr = bundleNames.map((each, i) => `${i+1}. ${each}`).join('\n'); console.log(`${chalk.magentaBright(`Detected ${bundleNames.length} bundles in ${bundlesDir}:`)}\n${bundlesStr}`); } else { - console.log(chalk.redBright(`No bundles in ${bundlesDir}`)); + logCommandErrorAndExit(`No bundles in ${bundlesDir}!`); } } else { - const manifest = await resolveSingleBundle(directory); - if (!manifest) { + const manifestResult = await resolveSingleBundle(directory); + if (!manifestResult) { logCommandErrorAndExit(`No bundle found at ${directory}!`); + } else if (manifestResult.severity === 'error') { + logCommandErrorAndExit(formatResolveBundleErrors(manifestResult)); } else { - console.log(chalk.magentaBright(`Bundle '${manifest.name}' found in ${directory}`)); + const bundle = omit(manifestResult.bundle, 'type'); + const manifestStr = JSON.stringify(bundle, null, 2); + console.log(`${chalk.magentaBright(`Bundle '${manifestResult.bundle.name}' found in ${directory}`)}:\n${manifestStr}`); } } }); @@ -40,19 +50,22 @@ export const getListTabsCommand = () => new Command('tabs') ]); const tabsManifest = await resolveAllTabs(bundlesDir, tabsDir); - const tabNames = Object.keys(tabsManifest); + if (tabsManifest.severity === 'error') { + logCommandErrorAndExit(formatResolveBundleErrors(tabsManifest)); + } + + const tabNames = Object.keys(tabsManifest.tabs); if (tabNames.length > 0) { const tabsStr = tabNames.map((each, i) => `${i+1}. ${each}`).join('\n'); - console.log(`${chalk.magentaBright(`Detected ${tabNames.length} bundles in ${tabsDir}:`)}\n${tabsStr}`); + console.log(`${chalk.magentaBright(`Detected ${tabNames.length} tabs in ${tabsDir}:`)}\n${tabsStr}`); } else { - console.log(chalk.redBright(`No tabs in ${tabsDir}`)); + logCommandErrorAndExit(`No tabs in ${tabsDir}`); } } else { const resolvedTab = await resolveSingleTab(directory); if (!resolvedTab) { logCommandErrorAndExit(`No tab found in ${directory}`); - process.exit(1); } console.log(chalk.magentaBright(`Tab '${resolvedTab.name}' found in ${directory}`)); } diff --git a/lib/buildtools/src/commands/main.ts b/lib/buildtools/src/commands/main.ts index 0ec3e17dce..c01dca491b 100644 --- a/lib/buildtools/src/commands/main.ts +++ b/lib/buildtools/src/commands/main.ts @@ -1,13 +1,14 @@ import { Command } from '@commander-js/extra-typings'; import { getBuildCommand } from './build.js'; import { getListCommand } from './list.js'; -import { getLintCommand, getPrebuildAllCommand, getTscCommand } from './prebuild.js'; +import { getLintCommand, getLintGlobalCommand, getPrebuildAllCommand, getTscCommand } from './prebuild.js'; import getTemplateCommand from './template.js'; import { getTestCommand } from './testing.js'; export const getMainCommand = () => new Command() .addCommand(getBuildCommand()) .addCommand(getLintCommand()) + .addCommand(getLintGlobalCommand()) .addCommand(getListCommand()) .addCommand(getPrebuildAllCommand()) .addCommand(getTemplateCommand()) diff --git a/lib/buildtools/src/commands/prebuild.ts b/lib/buildtools/src/commands/prebuild.ts index 838c1f842b..0bac72dd69 100644 --- a/lib/buildtools/src/commands/prebuild.ts +++ b/lib/buildtools/src/commands/prebuild.ts @@ -1,23 +1,26 @@ -/* - * This command is provided as part of buildtools so that each bundle and tab doesn't - * have to install its own copy of ESLint - */ - import pathlib from 'path'; import { Command } from '@commander-js/extra-typings'; -import { resolveEitherBundleOrTab } from '../build/manifest.js'; +import chalk from 'chalk'; +import { resolveEitherBundleOrTab } from '../build/manifest/index.js'; import { runPrebuild } from '../prebuild/index.js'; -import { formatLintResult, runEslint } from '../prebuild/lint.js'; +import { formatLintResult, lintGlobal, runEslint } from '../prebuild/lint.js'; import { formatTscResult, runTsc } from '../prebuild/tsc.js'; +import { divideAndRound } from '../utils.js'; import { logCommandErrorAndExit } from './commandUtils.js'; +/* + * The lint command is provided as part of buildtools so that each bundle and tab doesn't + * have to install its own copy of ESLint + */ + export const getLintCommand = () => new Command('lint') .description('Run ESLint for the given directory, or the current directory if no directory is specified') .argument('[directory]', 'Directory to run ESLint in', process.cwd()) .option('--fix', 'Output linting fixes', false) - .option('--ci') + .option('--ci', process.env.CI) .action(async (directory, { fix, ci }) => { const fullyResolved = pathlib.resolve(directory); + const asset = await resolveEitherBundleOrTab(fullyResolved); if (!asset) { @@ -37,13 +40,41 @@ export const getLintCommand = () => new Command('lint') } }); +export const getLintGlobalCommand = () => new Command('lintglobal') + .description('Lints everything except tabs and bundles') + .option('--fix', 'Output linting fixes', false) + .option('--ci', process.env.CI) + .action(async ({ fix, ci }) => { + const result = await lintGlobal(fix); + const prefix = chalk.blueBright('[lintglobal]'); + const logs = [ + `${prefix} Took ${divideAndRound(result.filesElapsed, 1000)}s to find files`, + `${prefix} Took ${divideAndRound(result.lintElapsed, 1000)}s to lint files`, + result.fixElapsed === undefined + ? `${prefix} No fixes output` + : `${prefix} Took ${divideAndRound(result.fixElapsed, 1000)}s to output fixes`, + result.formatted + ]; + + console.log(logs.join('\n')); + + switch (result.severity) { + case 'warn': { + if (!ci) return; + } + case 'error': { + process.exit(1); + } + } + }); + // This command is provided as an augmented way to run tsc, automatically // filtering out test files export const getTscCommand = () => new Command('tsc') .description('Run tsc for the given directory, or the current directory if no directory is specified') .argument('[directory]', 'Directory to run tsc in', process.cwd()) .option('--no-emit', 'Prevent the typescript compiler from outputting files regardless of the tsconfig setting') - .option('--ci') + .option('--ci', process.env.CI) .action(async (directory, { emit, ci }) => { const fullyResolved = pathlib.resolve(directory); const asset = await resolveEitherBundleOrTab(fullyResolved); @@ -67,7 +98,7 @@ export const getTscCommand = () => new Command('tsc') export const getPrebuildAllCommand = () => new Command('prebuild') .argument('[directory]', 'Directory to prebuild tasks in', process.cwd()) - .option('--ci') + .option('--ci', process.env.CI) .action(async (directory, { ci }) => { const fullyResolved = pathlib.resolve(directory); const asset = await resolveEitherBundleOrTab(fullyResolved); diff --git a/lib/buildtools/src/commands/testing.ts b/lib/buildtools/src/commands/testing.ts index 73a8033d03..23cf1e03ac 100644 --- a/lib/buildtools/src/commands/testing.ts +++ b/lib/buildtools/src/commands/testing.ts @@ -1,7 +1,7 @@ import { resolve } from 'path'; import { Command, Option } from '@commander-js/extra-typings'; import type { VitestRunMode } from 'vitest/node'; -import { resolveEitherBundleOrTab } from '../build/manifest.js'; +import { resolveEitherBundleOrTab } from '../build/manifest/index.js'; import { runIndividualVitest } from '../testing.js'; import { logCommandErrorAndExit } from './commandUtils.js'; diff --git a/lib/buildtools/src/prebuild/__tests__/tsc.test.ts b/lib/buildtools/src/prebuild/__tests__/tsc.test.ts index bb8539db89..77be0295a5 100644 --- a/lib/buildtools/src/prebuild/__tests__/tsc.test.ts +++ b/lib/buildtools/src/prebuild/__tests__/tsc.test.ts @@ -12,6 +12,7 @@ vi.mock(import('typescript'), async importOriginal => { const createProgram: typeof original.createProgram = vi.fn((opts: ts.CreateProgramOptions) => { const program = original.createProgram(opts); const emit: typeof program.emit = (sourceFile, _, cancelToken, emitDts, transformers) => { + // We mock create program so that we can check what the writeFile callback is called with return program.emit(sourceFile, mockedWriteFile, cancelToken, emitDts, transformers); }; diff --git a/lib/buildtools/src/prebuild/lint.ts b/lib/buildtools/src/prebuild/lint.ts index afa1e8ef99..afca23485a 100644 --- a/lib/buildtools/src/prebuild/lint.ts +++ b/lib/buildtools/src/prebuild/lint.ts @@ -1,8 +1,10 @@ +import { type Dirent, promises as fs } from 'fs'; +import path from 'path'; import chalk from 'chalk'; import { ESLint } from 'eslint'; import { getGitRoot } from '../getGitRoot.js'; import type { ResolvedBundle, ResolvedTab } from '../types.js'; -import { findSeverity, type Severity } from '../utils.js'; +import { findSeverity, isNodeError, flatMapAsync, type Severity } from '../utils.js'; import { createPrebuilder } from './prebuildUtils.js'; export interface LintResults { @@ -11,6 +13,28 @@ export interface LintResults { input: ResolvedBundle | ResolvedTab } +function severityFinder({ warningCount, errorCount, fatalErrorCount, fixableWarningCount }: ESLint.LintResult, fix: boolean) { + if (fatalErrorCount > 0) return 'error'; + if (!fix && errorCount > 0) return 'error'; + + if (warningCount > 0) { + if (fix && fixableWarningCount === warningCount) { + return 'success'; + } + return 'warn'; + } + return 'success'; +} + +async function timePromise(f: () => Promise) { + const start = performance.now(); + const result = await f(); + return { + elapsed: performance.now() - start, + result + }; +} + export const { builder: runEslint, formatter: formatLintResult @@ -29,21 +53,7 @@ export const { const outputFormatter = await linter.loadFormatter('stylish'); const formatted = await outputFormatter.format(linterResults); - const severity = findSeverity(linterResults, ({ warningCount, errorCount, fatalErrorCount, fixableWarningCount }) => { - - if (fatalErrorCount > 0) return 'error'; - if (!fix && errorCount > 0) return 'error'; - - if (warningCount > 0) { - if (fix && fixableWarningCount === warningCount) { - return 'success'; - } - return 'warn'; - } - return 'success'; - }); - - console.log(severity); + const severity = findSeverity(linterResults, each => severityFinder(each, fix)); return { formatted, @@ -70,3 +80,78 @@ export const { return `${prefix} ${chalk.greenBright('successfully')}`; } }); + +interface LintGlobalResults { + severity: Severity + formatted: string + fixElapsed: number | undefined + lintElapsed: number + filesElapsed: number +} + +/** + * Function for linting the source directory excluding bundles and tabs. Since linting bundles and tabs + * altogether causes ESLint to run out of memory, we have this function that lints everything else + * so that the bundles and tabs can be linted separately. + */ +export async function lintGlobal(fix: boolean): Promise { + const gitRoot = await getGitRoot(); + + const linter = new ESLint({ fix, cwd: gitRoot }); + + /** + * Recursively determine what files to to lint. Just supplying folder paths + * doesn't quite work (they are still considered ignored) so we have to recurse + * into every subfolder to get the full path of every file and check if it is + * ignored + * + * @param filterSrc Filter out paths that contain `'src'`. Only necessary for the root directory. + * Subsequent recursive calls are allowed to recurse into src directories + */ + async function getFiles(dir: string, filterSrc: boolean): Promise { + let files: Dirent[]; + try { + files = await fs.readdir(dir, { withFileTypes: true }); + } catch (error) { + if (!isNodeError(error) || error.code !== 'ENOTDIR') throw error; + return []; + } + + return flatMapAsync(files, async each => { + if (filterSrc && each.name === 'src') return []; + + const fullPath = path.join(dir, each.name); + if (each.isFile()) { + const isIgnored = await linter.isPathIgnored(fullPath); + return isIgnored ? [] : [fullPath]; + } + + if (each.isDirectory()) { + return getFiles(fullPath, false); + } + + return []; + }); + } + + const { result: toLint, elapsed: filesElapsed } = await timePromise(() => getFiles(gitRoot, true)); + const { result: lintResults, elapsed: lintElapsed } = await timePromise(() => flatMapAsync(toLint, each => linter.lintFiles(each))); + + let fixElapsed: number | undefined = undefined; + if (fix) { + ;({ elapsed: fixElapsed } = await timePromise(() => ESLint.outputFixes(lintResults))); + } + + const formatter = await linter.loadFormatter('stylish'); + const formatted = await formatter.format(lintResults); + + const severity = findSeverity(lintResults, each => severityFinder(each, fix)); + + return { + formatted, + severity, + fixElapsed, + lintElapsed, + filesElapsed + }; +} diff --git a/lib/buildtools/src/templates/bundle.ts b/lib/buildtools/src/templates/bundle.ts index ba12de7346..c09fe4725c 100644 --- a/lib/buildtools/src/templates/bundle.ts +++ b/lib/buildtools/src/templates/bundle.ts @@ -1,10 +1,11 @@ import fs from 'fs/promises'; import type { Interface } from 'readline/promises'; import _package from '../../../../package.json' with { type: 'json' }; -import { getBundleManifests } from '../build/manifest.js'; +import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; +import { getBundleManifests } from '../build/manifest/index.js'; import type { ModulesManifest, BundleManifest } from '../types.js'; import sampleTsconfig from './bundle_tsconfig.json' with { type: 'json' }; -import { askQuestion, success, warn } from './print.js'; +import { askQuestion, error, success, warn } from './print.js'; import { check, isSnakeCase } from './utilities.js'; async function askModuleName(manifest: ModulesManifest, rl: Interface) { @@ -22,7 +23,12 @@ async function askModuleName(manifest: ModulesManifest, rl: Interface) { export async function addNew(bundlesDir: string, rl: Interface) { const manifest = await getBundleManifests(bundlesDir); - const moduleName = await askModuleName(manifest, rl); + if (manifest.severity === 'error') { + error(formatResolveBundleErrors(manifest)); + return; + } + + const moduleName = await askModuleName(manifest.manifests, rl); const bundleDestination = `${bundlesDir}/${moduleName}`; await fs.mkdir(bundlesDir, { recursive: true }); diff --git a/lib/buildtools/src/templates/tab.ts b/lib/buildtools/src/templates/tab.ts index 87ba0f514e..68e3cc5ee2 100644 --- a/lib/buildtools/src/templates/tab.ts +++ b/lib/buildtools/src/templates/tab.ts @@ -1,9 +1,10 @@ import fs from 'fs/promises'; import type { Interface } from 'readline/promises'; import _package from '../../../../package.json' with { type: 'json' }; -import { getBundleManifests } from '../build/manifest.js'; +import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; +import { getBundleManifests } from '../build/manifest/index.js'; import type { ModulesManifest, BundleManifest } from '../types.js'; -import { askQuestion, success, warn } from './print.js'; +import { askQuestion, error, success, warn } from './print.js'; import { check, isPascalCase } from './utilities.js'; async function askModuleName(manifest: ModulesManifest, rl: Interface) { @@ -35,7 +36,13 @@ async function askTabName(manifest: ModulesManifest, rl: Interface) { } export async function addNew(bundlesDir: string, tabsDir: string, rl: Interface) { - const manifest = await getBundleManifests(bundlesDir); + const manifestResult = await getBundleManifests(bundlesDir); + if (manifestResult.severity === 'error') { + error(formatResolveBundleErrors(manifestResult)); + return; + } + const manifest = manifestResult.manifests; + const moduleName = await askModuleName(manifest, rl); const tabName = await askTabName(manifest, rl); diff --git a/lib/buildtools/src/utils.ts b/lib/buildtools/src/utils.ts index b28d49c506..a5e540c1be 100644 --- a/lib/buildtools/src/utils.ts +++ b/lib/buildtools/src/utils.ts @@ -13,6 +13,10 @@ export function compareSeverity(lhs: Severity, rhs: Severity) { } } +/** + * Given an array of items, map them to {@link Severity | Severity} objects and then + * determine the overall severity of all the given items + */ export function findSeverity(items: T[], mapper: (each: T) => Severity): Severity { let output: Severity = 'success'; for (const item of items) { @@ -29,3 +33,20 @@ export const divideAndRound = (n: number, divisor: number) => (n / divisor).toFi export function isNodeError(error: unknown): error is NodeJS.ErrnoException { return error instanceof Error; } + +/** + * `Array.flatMap` but with a mapping function that returns a promise + */ +export async function flatMapAsync(items: T[], mapper: (item: T, index: number) => Promise) { + const results = await Promise.all(items.map(mapper)); + return results.flat(); +} + +/** + * `Array.filter`, but with a filtering function that returns a promise + */ +export async function filterAsync(items: T[], filter: (item: T, index: number) => Promise): Promise { + const results = await Promise.all(items.map(filter)); + + return items.reduce((res, item, i) => results[i] ? [...res, item] : res, [] as T[]); +} From 4634304214e6bf2b8eb1b6c9d3f379d7c9c03132 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sun, 29 Jun 2025 21:29:32 +0800 Subject: [PATCH 095/112] Add ability to use mermaid diagrams in docserver --- docs/.vitepress/config.ts | 20 +++- docs/package.json | 2 + docs/src/buildtools/1-devenv.md | 19 ++++ .../buildtools/{1-command.md => 2-command.md} | 0 .../{2-structure.md => 3-structure.md} | 0 docs/src/buildtools/4-resolution.md | 85 +++++++++++++++ .../{3-builders => 5-builders}/1-modules.md | 29 ++++-- .../{3-builders => 5-builders}/2-docs.md | 2 +- .../{3-builders => 5-builders}/3-manifest.md | 0 .../{3-builders => 5-builders}/index.md | 0 .../{4-prebuild.md => 6-prebuild.md} | 0 .../{6-templates.md => 7-templates.md} | 0 .../buildtools/{5-testing.md => 8-testing.md} | 6 +- docs/src/buildtools/index.md | 33 ++++-- docs/src/icons/github.png | Bin 0 -> 24758 bytes docs/src/icons/library.png | Bin 0 -> 38461 bytes docs/src/icons/tools.png | Bin 0 -> 33447 bytes docs/src/icons/tut.png | Bin 0 -> 23364 bytes docs/src/index.md | 20 +++- docs/src/lib/dev/curve.md | 98 ++++++++++++++++++ docs/src/lib/dev/devdocs.data.ts | 17 +++ docs/src/lib/dev/game.md | 38 +++++++ docs/src/lib/dev/index.md | 16 +++ docs/src/lib/index.md | 5 +- docs/src/modules/1-getting-started/4-faq.md | 32 +++--- .../4-documentation/4-documentation.md | 6 +- docs/src/modules/2-bundle/5-type_map.md | 25 ++++- docs/src/modules/4-advanced/cross-use.md | 2 +- docs/src/modules/4-advanced/testing.md | 2 +- docs/src/repotools/docserver/docserver.md | 8 +- docs/{.vitepress => }/tsconfig.json | 2 +- 31 files changed, 414 insertions(+), 53 deletions(-) create mode 100644 docs/src/buildtools/1-devenv.md rename docs/src/buildtools/{1-command.md => 2-command.md} (100%) rename docs/src/buildtools/{2-structure.md => 3-structure.md} (100%) create mode 100644 docs/src/buildtools/4-resolution.md rename docs/src/buildtools/{3-builders => 5-builders}/1-modules.md (84%) rename docs/src/buildtools/{3-builders => 5-builders}/2-docs.md (97%) rename docs/src/buildtools/{3-builders => 5-builders}/3-manifest.md (100%) rename docs/src/buildtools/{3-builders => 5-builders}/index.md (100%) rename docs/src/buildtools/{4-prebuild.md => 6-prebuild.md} (100%) rename docs/src/buildtools/{6-templates.md => 7-templates.md} (100%) rename docs/src/buildtools/{5-testing.md => 8-testing.md} (92%) create mode 100644 docs/src/icons/github.png create mode 100644 docs/src/icons/library.png create mode 100644 docs/src/icons/tools.png create mode 100644 docs/src/icons/tut.png create mode 100644 docs/src/lib/dev/curve.md create mode 100644 docs/src/lib/dev/devdocs.data.ts create mode 100644 docs/src/lib/dev/game.md create mode 100644 docs/src/lib/dev/index.md rename docs/{.vitepress => }/tsconfig.json (79%) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 477f224645..0fbb70962a 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,4 +1,6 @@ +// Vitepress config import { defineConfig, type UserConfig } from 'vitepress'; +import { withMermaid } from 'vitepress-plugin-mermaid'; import { withSidebar } from 'vitepress-sidebar'; import type { VitePressSidebarOptions } from 'vitepress-sidebar/types'; import _package from '../../package.json' with { type: 'json' }; @@ -7,12 +9,15 @@ import _package from '../../package.json' with { type: 'json' }; const vitepressOptions: UserConfig = { base: '/devdocs/', description: 'Developer documentation for the Source Academy modules repository', + head: [['link', { rel: 'icon', href: '/devdocs/favicon.ico' }]], ignoreDeadLinks: 'localhostLinks', + lastUpdated: true, outDir: `${import.meta.dirname}/../../build/devdocs`, srcDir: 'src', title: 'Modules Developer Documentation', themeConfig: { // https://vitepress.dev/reference/default-theme-config + logo: './favicon.ico', nav: [ { text: 'Home', link: '/' }, { @@ -24,7 +29,7 @@ const vitepressOptions: UserConfig = { }, { text: 'Getting Started', - link: '/modules/1-getting-started/2-overview' + link: '/modules/1-getting-started/2-start' } ] }, @@ -84,4 +89,15 @@ const sideBarOptions = Object.entries(sidebarConfigs).map(([startPath, options]) ...options })); -export default defineConfig(withSidebar(vitepressOptions, sideBarOptions)); +export default defineConfig( + withMermaid({ + ...withSidebar(vitepressOptions, sideBarOptions), + mermaid: { + // Use the default font that Vitepress uses + fontFamily: 'Inter', + flowchart: { + curve: 'linear', + } + } + }) +); diff --git a/docs/package.json b/docs/package.json index 7b71835e32..3bc2d74b87 100644 --- a/docs/package.json +++ b/docs/package.json @@ -6,7 +6,9 @@ "type": "module", "devDependencies": { "@sourceacademy/modules-lib": "workspace:^", + "mermaid": "^11.7.0", "vitepress": "^1.6.3", + "vitepress-plugin-mermaid": "^2.0.17", "vitepress-sidebar": "^1.31.1" }, "scripts": { diff --git a/docs/src/buildtools/1-devenv.md b/docs/src/buildtools/1-devenv.md new file mode 100644 index 0000000000..91b3fce94a --- /dev/null +++ b/docs/src/buildtools/1-devenv.md @@ -0,0 +1,19 @@ +# Working with the Build Tools +This page contains the instructions required for working with the build tools' source code. + +## Installing Dependencies for Build Tools +If you haven't already, follow the instructions [here](/modules/1-getting-started/2-start) to install NodeJS and Yarn. + +Running the command below will install the dependencies for **only** the build tools: + +```sh +yarn workspaces focus @sourceacademy/modules-buildtools +``` + +## Compiling the Build Tools + +Compiling the build tools is as simple as running `yarn build`. This by default produces a minified Javascript file, which is not very conducive to debugging. If necessary +you can run `yarn build --dev` instead to produce a non-minified build. + +## Testing +The build tools come with a comprehensive suite of tests. If you are planning to contribute, please write tests to cover your new/modified functionality. diff --git a/docs/src/buildtools/1-command.md b/docs/src/buildtools/2-command.md similarity index 100% rename from docs/src/buildtools/1-command.md rename to docs/src/buildtools/2-command.md diff --git a/docs/src/buildtools/2-structure.md b/docs/src/buildtools/3-structure.md similarity index 100% rename from docs/src/buildtools/2-structure.md rename to docs/src/buildtools/3-structure.md diff --git a/docs/src/buildtools/4-resolution.md b/docs/src/buildtools/4-resolution.md new file mode 100644 index 0000000000..de926d6030 --- /dev/null +++ b/docs/src/buildtools/4-resolution.md @@ -0,0 +1,85 @@ +# Bundle and Tab Resolution + +This page describes the process of how the buildtools determines what a valid bundle and what a valid tab is. + +::: details `modules.json` + +If you've been around for a while, you might remember that previously at the root of the module repository there was a file called `modules.json`. This JSON +file contained the list of all bundles and tabs. Effectively, only if a bundle and tab was listed in that file would it be recognized as a valid bundle or tab. + +This is no longer the case with the current repository structure. The buildtools determine what bundles and tabs are present so that developers do not need to +configure `modules.json` themselves. So long as your bundle is correctly configured, it will be registered correctly. +::: + +## Bundle Resolution + +A directory located in the `src/bundles` folder is considered a bundle if it has both a `package.json` and `manifest.json` file. + +For each subdirectory located in `src/bundles`, the following process is executed: +```mermaid +graph TD + A[Does it contain manifest.json] + B[Is the manifest valid] + C[Return undefined] + D[Return error] + E[Should execute tab check?] + F[Do all the specified tabs exist?] + G[Retrieve version from package.json] + H[Can the entry point be found?] + + A -- yes --> B + A -- no --> C + + B -- yes --> E + B -- no --> D + + F -- yes --> G + F -- no --> D + + E -- yes --> F + E -- no --> G + + G -- If package.json does not exist --> D + G -- No errors --> H + + H -- yes --> I[Return resolved bundle] + H -- no --> D +``` + +Bundle resolution involves loading its manifest, determining its version and name, using the JSON schema to validate the manifest as well as checking the tabs specified in the manifest indeed do exist. + +`resolveSingleBundle` executes the resolution process and returns the following type: + +<<< ../../../lib/buildtools/src/types.ts#ResolvedBundle + +### `resolveAllBundles` +`resolveAllBundles` is used to collate the manifests of every single bundle at once. If bundle(s) return an error during resolution, the resolution process will still proceed and the errors are collated together +and returned at the end. + +## Tab Resolution +First, the above process is executed to retrieve all manifests from all bundles. From there, we can retrieve the names of every +single tab that is supposed to be present. + +For each tab name, it is assumed that a directory of that name exists under `src/tabs`. For each of those directories, the process +below is executed: + +```mermaid +graph TD + A[Does index.tsx exist] + B[Does src/index.tsx exist] + C[Return undefined] + D[Return resolved tab] + + A -- yes --> D + A -- no --> B + + B -- yes --> D + B -- no --> C +``` +This process is also used when checking that the tabs specified the bundle manifest exist. + +`resolveSingleTab` used to perform tab resolution returns the following type: + +<<< ../../../lib/buildtools/src/types.ts#ResolvedTab + +Because the entry point for a tab can be either `src/index.tsx` or `index.tsx`, we do need to store the tab's entry point. diff --git a/docs/src/buildtools/3-builders/1-modules.md b/docs/src/buildtools/5-builders/1-modules.md similarity index 84% rename from docs/src/buildtools/3-builders/1-modules.md rename to docs/src/buildtools/5-builders/1-modules.md index 9612c5cdc3..a37818dc19 100644 --- a/docs/src/buildtools/3-builders/1-modules.md +++ b/docs/src/buildtools/5-builders/1-modules.md @@ -10,7 +10,11 @@ Currently, there are several bundlers available such as RollupJS, Babel and Webp [`esbuild`](https://esbuild.github.io) is a Javascript bundler that trades configurability for speed. It is magnitudes faster than most other bundlers and suits the modules repository just fine. We use it to transpile module code from Typescript to Javascript and perform bundling. ## How Source Modules are Bundled -Each Source module bundle is passed through `esbuild`, which converts it into an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE). Here is the `curve` module passed through `esbuild`: +Bundles and tabs are transpiled with esbuild using the following common options set: + +<<< ../../../../lib/buildtools/src/build/modules/commons.ts#esbuildOptions + +This converts each bundle and tab into an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE). Here is the `curve` bundle passed through `esbuild`: ```ts // All of the code within the curve bundle is combined into a single file @@ -32,16 +36,12 @@ var globalName = (() => { })(); ``` -Bundles and tabs are transpiled with esbuild using the following common options set: - -<<< ../../../../lib/buildtools/src/build/modules/commons.ts#esbuildOptions - ## Options Explained ### `bundle: true` Tell `esbuild` to bundle the code into a single file. ### `external` -Because the frontend is built using React, it is unnecessary to bundle React with the code for tabs. Similarly, `js-slang/context` is an import provided by `js-slang` at runtime, so it is not bundled with the code for bundles. +This option is used to mark which packages not to include when bundling. This is useful for dependencies that are available at runtime (e.g The Frontend provides React). ### `format: 'iife'` Tell `esbuild` to output the code as an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE). @@ -78,7 +78,7 @@ Similarly, because we are bundling for the browser, it becomes necessary to defi Tell `esbuild` that we are bundling for the browser, and that we need to compile code down to the ES6 standard, which is the Javascript standard targeted by Source. ### `write: false` -`write: false` causes `esbuild` to its compiled code into memory instead of to disk, which is necessary to finish building the bundle or tab. +`write: false` causes `esbuild` to output its compiled code into memory instead of to disk, which is necessary to finish building the bundle or tab. ## After Esbuild After esbuild bundling, both bundles and tabs are parsed using [`acorn`](https://github.com/acornjs/acorn) to produce an AST. Esbuild will produce an IIFE that looks like the following: @@ -92,7 +92,7 @@ var module = (function() { return exports; })() ``` -Which are then transformed by the `outputBundleOrTab` function, which produces output that looks like this: +The AST is then transformed by the `outputBundleOrTab` function, which produces output that looks like this: ```js export default require => { var exports = {} @@ -104,6 +104,8 @@ export default require => { } ``` +This is what is finally written to the output folders. + Consumers of this compiled version of bundles and tabs can retrieve the IIFE by using the default export. When bundles and tabs are loaded, the IIFE is called with a function that simulates the `require()` function in CommonJS to provide the dependencies marked as external (that have to be provided at runtime). @@ -112,3 +114,14 @@ in CommonJS to provide the dependencies marked as external (that have to be prov at compile time, which means that typings have to be provided for it. This is achieved using a Typescript declaration file. + +## Summary of Build Process +```mermaid +graph LR; + A[Bundle Resolution] + B[Esbuild Bundling] + C[AST Manipulation] + D[Write to Output] + + A --> B --> C --> D +``` diff --git a/docs/src/buildtools/3-builders/2-docs.md b/docs/src/buildtools/5-builders/2-docs.md similarity index 97% rename from docs/src/buildtools/3-builders/2-docs.md rename to docs/src/buildtools/5-builders/2-docs.md index 24cf40f578..25069e75c8 100644 --- a/docs/src/buildtools/3-builders/2-docs.md +++ b/docs/src/buildtools/5-builders/2-docs.md @@ -20,7 +20,7 @@ as its children, each of which represents a different bundle. > `typedoc` simply returns `undefined` when `Application.convert` is called. ## HTML Generation -It is not possible to generate the HTML documentation on a per-bundle basis. Thus, when HTML documentation needs to be regenerated, the source files of every single bundle gets processed. +It is not possible to generate the HTML documentation on a per-bundle basis. Thus, when HTML documentation needs to be regenerated, the source files of every single bundle needs to be processed. ## JSON Generation `typedoc` represents each bundle as a [`DeclarationReflection`](https://typedoc.org/api/classes/Models.DeclarationReflection.html) internally. Each of these `DeclarationReflection`s contains an array of "children" elements which diff --git a/docs/src/buildtools/3-builders/3-manifest.md b/docs/src/buildtools/5-builders/3-manifest.md similarity index 100% rename from docs/src/buildtools/3-builders/3-manifest.md rename to docs/src/buildtools/5-builders/3-manifest.md diff --git a/docs/src/buildtools/3-builders/index.md b/docs/src/buildtools/5-builders/index.md similarity index 100% rename from docs/src/buildtools/3-builders/index.md rename to docs/src/buildtools/5-builders/index.md diff --git a/docs/src/buildtools/4-prebuild.md b/docs/src/buildtools/6-prebuild.md similarity index 100% rename from docs/src/buildtools/4-prebuild.md rename to docs/src/buildtools/6-prebuild.md diff --git a/docs/src/buildtools/6-templates.md b/docs/src/buildtools/7-templates.md similarity index 100% rename from docs/src/buildtools/6-templates.md rename to docs/src/buildtools/7-templates.md diff --git a/docs/src/buildtools/5-testing.md b/docs/src/buildtools/8-testing.md similarity index 92% rename from docs/src/buildtools/5-testing.md rename to docs/src/buildtools/8-testing.md index 8f8cbaeff3..695c78def4 100644 --- a/docs/src/buildtools/5-testing.md +++ b/docs/src/buildtools/8-testing.md @@ -66,7 +66,7 @@ This causes `vitest` to resolve the configuration correctly every time. ## Vitest for specific Bundle/Tab However, even with the configuration above, we face an issue. `vitest` requires that each project specification matches a specific file. The pattern `src/bundles/*/vitest.config.ts` matches all `vitest.config.ts` files under the -top-level directory of each bundle. This is equivalent to `src/bundles/*`, since by default, `vitest` will try to find a file named `vitest.config`. However, it does mean that the specific configuration file must exist in that folder for it to be considered a project. +top-level directory of each bundle. This is equivalent to `src/bundles/*`, since by default, `vitest` will try to find a file named `vitest.config`. This requires that the specific configuration file exists in that folder for it to be considered a project. This means that for a bundle/tab's tests to be detected, that bundle or tab would need to maintain its own `vitest` config. Alternatively, the specific configuration for that bundle/tab could be added directly to the root config. Neither of these solutions are particularly ideal. The former would run the risk of misconfiguration, and the latter would cause the root configuration to become cluttered and defeat the purpose of splitting the repository into workspaces. @@ -84,5 +84,5 @@ With this, running `yarn test` within a bundle or tab's directory structure will The approach above works when you know that the current tab or bundle has tests that need to be run. In order to run tests for **all** bundles or **all** tabs at once, the buildtools would have to determine which bundles and tabs have tests and which don't (as Vitest considers it an error if you give it a test project that it can't find tests for). -Instead, all bundles and all tabs are configured as a test project each. Hence, under `src/bundles` and `src/tabs` you can find a `vitest.config.js` that is specific to bundle and tabs respectively. This shifts -the responsibility for determining which bundles and tabs contain tests onto `vitest` itself, and so it will not fail (unless for some reason every single bundle and tab test was removed). +Instead, all bundles and all tabs are configured as a test project each. Hence, under `src/bundles` and `src/tabs` you can find a `vitest.config.js` that is specific to bundles and tabs respectively. This shifts +the responsibility for determining which bundles and tabs contain tests onto `vitest` itself and so it will not fail (unless for some reason every single bundle and tab test was removed). diff --git a/docs/src/buildtools/index.md b/docs/src/buildtools/index.md index 3d4c2075f3..d15915562d 100644 --- a/docs/src/buildtools/index.md +++ b/docs/src/buildtools/index.md @@ -8,15 +8,36 @@ The Source Academy Modules build tools are written in Typescript and designed to The build tools are comprised of several sections: -- [Command Handlers](./1-command) +- [Command Handlers](./2-command) - The actual code that runs command line argument parsing -- [Builders](./3-builders/) +- [Builders](./5-builders/) - The code that converts a bundle or tab into its outputs -- [Prebuild Tasks](./4-prebuild) +- [Prebuild Tasks](./6-prebuild) - Code that is intended to be run before builders -- [Testing](./5-testing) +- [Testing](./8-testing) - Code for integrating `vitest` -- [Templates](./6-templates) +- [Templates](./7-templates) - Code for the template command -Explanations on how the build tools repository has been structured can be found [here](./2-structure) +## Structure of the Build Tools package +```txt +uildtools +├── bin +│ ├── templates // Actual template files, retrieved at runtime +│ ├── docsreadme.md // Docs HTML readme, retrieved at runtime +│ └── index.js // The compiled buildtools +├── src +│ ├── ___test_mocks__ // Contains a set of mock bundles and tabs for testing +│ ├── build +│ │ ├── docs // Docs Builders +│ │ ├── modules +│ │ │ ├── bundle // Bundle Builder +│ │ │ ├── tab // Tab Builder +│ │ │ └── manifest.schema.json // Schema used to validate bundle manifests +│ │ └── manifest // Code related to manipulating manifests +│ ├── commands // Code where command handlers are defined +│ ├── prebuild // Code for running ESLint and tsc +│ ├── templates // Code for the template commands +│ └── testing.ts // Code for running Vitest from Node +└── workspacer.py // Python code for updating JSON files across bundles and tabs +``` diff --git a/docs/src/icons/github.png b/docs/src/icons/github.png new file mode 100644 index 0000000000000000000000000000000000000000..c2aa73049f1ec56ab94b9d4c9eb35db71cb984cf GIT binary patch literal 24758 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 literal 0 HcmV?d00001 diff --git a/docs/src/icons/tools.png b/docs/src/icons/tools.png new file mode 100644 index 0000000000000000000000000000000000000000..fa36f2c73b748205713f69d9bdae9af511de6ff5 GIT binary patch literal 33447 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 +Not a developer? Documentation for the Source Modules themselves is here +

    diff --git a/docs/src/lib/dev/curve.md b/docs/src/lib/dev/curve.md new file mode 100644 index 0000000000..ecde18555a --- /dev/null +++ b/docs/src/lib/dev/curve.md @@ -0,0 +1,98 @@ +--- +title: curve +--- + +# Introduction +The `curves` module allow user to create, transform and render curves of various shapes and colours onto a `canvas` element. This `canvas` spawns in a dedicated tab when the program is run. + + + + + + + + +
    + + + +
    + +## Gallery +

    + +

    + +Click the images for the sample code. + +## Documentation +Refer to the module's [documentation](https://source-academy.github.io/modules/documentation/modules/curve.html) for more information on each exposed function's specifications. + +## Pre-requisites +- The project requires some basic knowledge about **React**, including the usage of props/state. +- The current API that `curves` uses is **WebGL**. WebGL is a powerful tool for drawing on canvas elements, though limited by its primitive nature. Refer to the [online tutorials](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial) for more information. +- While going through the tutorial, you may come across a few matrix manipulation and constructor functions, such as `mat4.create()` and `rotate`. All these functions come from package called **glMatrix**, one of the most important dependencies in dealing with WebGL. More info can be found [here](https://glmatrix.net/). +- If there is an interest in doing fancier and cooler things for 3D graphics (e.g. surface rendering, orbit control, lighting, shadow casting etc.), the recommended way forward is to study [ThreeJS](https://threejs.org/). Since Source Academy is built on react, **React Three Fiber** is also a relevant package to look into. + +# Repository +## Library +All functions (private and exposed) are stored within `src/bundles/curves`. The folder contains the following files: +1. `curves_webgl.ts` - Contains any utility function used in regards to handling WebGL contexts, but not exposed to the user. Create new files if there is a need to handle usage of other graphic libraries in the future (e.g. `polyhedra_threejs.ts`). It is recommended to document all functions within the file. +2. `function.ts` - Contains only the implementation and documentation of all exposed functions of the module. Provide credits for contributing any functions into the library in the file tsdoc using `@author` tag. Every exposed function must be documented. +3. `index.ts` - The only use of index.ts is to export functions meant to be exposed to the user. +4. `types.ts` - Contains all declarations for types used in other files in the folder. Avoid using `any` for type parameters, or a general types for enumerable types (e.g. using `space : string` when `space : '2D' | '3D'`). Consider enum types for enumerable types containing more than 3 constants. It is recommended to provide tsdoc for declared types in this file. + +### Documentation Style +- All tsdoc description should start with capital letters and end with a full stop. +- Include `@param` and `@return` if any. Provide the corresponding description but do not end with a full-stop. +- It is recommended to include `@example` for exposed functions. Use code blocks to write the examples. +- Avoid going pass 80 character mark for each line. This is to facilitate split screen editing. + +Guidelines are subjected to changes, update the above accordingly and ensure they are followed. + +## Tab Component +The repository currently contains only the `index.ts` file. Feel free to add any files if there is a need to add more complex sub-components to the tab. + +### Spawning Mechanism +The canvas tab spawns when all the following conditions are fulfilled: +1. `curves` module is imported. +2. A call to a render function is executed (e.g. `draw_connected(100)(t => make_point(t, t));`). +3. The entire program finishes running successfully. + +### User Interface +The user interface consists of the following: +1. A html5 canvas element that displays the rendered curves. +2. A slider that controls the rotation of the rendered curves. +3. A play button that resumes the automated animation of the rotation. + +The slider's value ranges from `0` to `2*PI` inclusively. At the first instance when the program is run, 3D rendered curves are set to auto-rotate by themselves. The slider's value also updates according to the current curve's rotation. The animation stops when the user interacts with the slider and manually changes its value. To resume auto-rotation, click the play button. Spam clicking the play button should take no effect. + +The slider and the button will be disabled in cases where curves are rendered in 2D. + +### Prop/State Handling +Understanding the use of slider and button requires some basic knowledge on [Blueprint](https://blueprintjs.com/docs/). Provide event handlers for these component as separate private functions if they are too complex, such as performing other executions other than modifying the state. Note that `setState` is executed asynchronously. Provide statements, which you want to execute only after the state is updated, as a callback function passed as an argument (e.g. `setState((prevState) => ({...}), () => {callback();})`). If there are common statements to be evaluated/executed after any update on the state, consider putting them under `componentDidUpdate()`. + +Part of the tradeoffs of allowing the user to control the rotation of rendered curves is that the react tab component must have a way to retrieve information about the curve, and have the liberty to re-render it whenever needed by state changes. This has been done through calling `.result.value` on the context given through props. This effectively restricts the user to write the render statement as the last statement of the program as mentioned in [Spawning Mechanism](#spawning-mechanism). diff --git a/docs/src/lib/dev/devdocs.data.ts b/docs/src/lib/dev/devdocs.data.ts new file mode 100644 index 0000000000..2ea202f123 --- /dev/null +++ b/docs/src/lib/dev/devdocs.data.ts @@ -0,0 +1,17 @@ +// Loader making use of build-time data loading: https://vitepress.dev/guide/data-loading + +import fs from 'fs/promises'; +import pathlib from 'path'; +import { createContentLoader } from 'vitepress'; + +const files = await fs.readdir(import.meta.dirname, { withFileTypes: true }); + +const filesToInclude = files.filter(each => { + if (!each.isFile()) return false; + if (pathlib.extname(each.name) !== '.md') return false; + return each.name !== 'index.md'; +}).map(each => { + return pathlib.join('/lib/dev', each.name); +}); + +export default createContentLoader(filesToInclude); diff --git a/docs/src/lib/dev/game.md b/docs/src/lib/dev/game.md new file mode 100644 index 0000000000..3ea1ba12c2 --- /dev/null +++ b/docs/src/lib/dev/game.md @@ -0,0 +1,38 @@ +--- +title: game +--- + +# Introduction + +The `game` module is wrapper of the Phaser 3 API, that allows users to program their own game rooms in the Source Academy game. Students are able to use the `game` module functions inside the Phaser lifecycle functions (`preload()`, `create()` and `update()`), to define the room behaviour they want. Student room code is later fetched and run in-game. + +## Documentation + +You can check out the module's [documentation](https://source-academy.github.io/modules/documentation/modules/game.html) for more information on each exposed function's specifications. + +## Module Files + +### 1. `index.ts` + +This is the entry point to the module and contains a single default export that returns the module's exposed functions. It does this by passing GameParams into `gameFuncs` in functions.ts. + +### 2. `functions.ts` + +`gameFuncs` in functions.ts contains implementation details for the module's private and public functions, and returns the public ones. + +### 3. `types.ts` + +Game module types are defined here and generally wrap Phaser GameObject types that are typically used in similar ways in the module. + +1) `RawGameElement`: Phaser Sprite or Phaser Text +2) `RawGameShape`: Phaser Rectangle or Phaser Ellipse +3) `RawGameObject`: RawGameElement or RawGameShape +4) `RawContainer`: Phaser Container +5) `RawInputObject`: Phaser InputPlugin or Phaser Keyboard.Key +6) `GameObject`: the GameObject's type string, together with a RawGameObject or RawInputObject or RawContainer +7) `GameParams`: Contains fields that will be passed in from frontend that are required for the module's functions +8) `__Params`: The module params passed into the module (from frontend) with a GameParams field + +## Game Tabs + +The Game Tab currently only displays a reminder for the student to save their work and see the effect of the roomcode in-game, and links to the API documentation and User Guide. diff --git a/docs/src/lib/dev/index.md b/docs/src/lib/dev/index.md new file mode 100644 index 0000000000..fc6c6fb9e2 --- /dev/null +++ b/docs/src/lib/dev/index.md @@ -0,0 +1,16 @@ +--- +title: Modules Developer Docs +--- + + + +

    Bundles Developer Documentation

    +

    This an the index page for bundles that have documentation for developers working them

    + + diff --git a/docs/src/lib/index.md b/docs/src/lib/index.md index 6e5834b377..f2281597fe 100644 --- a/docs/src/lib/index.md +++ b/docs/src/lib/index.md @@ -1,4 +1,7 @@ -# Common Modules Libraries +## Modules Developer Documentation +If you're developing for a specific module, its developer documentation can be found under [this](./dev/) section. + +## Common Modules Libraries - [Lint Plugin](./lintplugin/) - [Modules Lib](./modules-lib/) diff --git a/docs/src/modules/1-getting-started/4-faq.md b/docs/src/modules/1-getting-started/4-faq.md index 2820a9cc97..7522fa8f9a 100644 --- a/docs/src/modules/1-getting-started/4-faq.md +++ b/docs/src/modules/1-getting-started/4-faq.md @@ -4,19 +4,17 @@ title: FAQ # Frequently Asked Questions * [**Infrastructure**](https://github.com/source-academy/modules/wiki/FAQs#infrastructure) - * [Could you explain more on the infrastructure of the modules system? Especially regarding how the bundle and tabs communicate.](https://github.com/source-academy/modules/wiki/FAQs#could-you-explain-more-on-the-infrastructure-of-the-modules-system-especially-regarding-how-the-bundle-and-tabs-communicate) -* [**Set Up and Configuration**](https://github.com/source-academy/modules/wiki/FAQs#set-up-and-configuration) - * [How do we use our own local version of the js-slang interpreter with the modules?](https://github.com/source-academy/modules/wiki/FAQs#how-do-we-use-our-own-local-version-of-the-js-slang-interpreter-with-the-modules) - * [Is it possible to be using modules served from more than one location simultaneously?](https://github.com/source-academy/modules/wiki/FAQs#is-it-possible-to-be-using-modules-served-from-more-than-one-location-simultaneously) -* [**Language Specifications**](https://github.com/source-academy/modules/wiki/FAQs#language-specifications) + * [Could you explain more on the infrastructure of the modules system? Especially regarding how the bundle and tabs communicate.](#could-you-explain-more-on-the-infrastructure-of-the-modules-system-especially-regarding-how-the-bundle-and-tabs-communicate) +* [**Set Up and Configuration**](#set-up-and-configuration) + * [How do we use our own local version of the js-slang interpreter with the modules?](#how-do-we-use-our-own-local-version-of-the-js-slang-interpreter-with-the-modules) + * [Is it possible to be using modules served from more than one location simultaneously?](#is-it-possible-to-be-using-modules-served-from-more-than-one-location-simultaneously) +* [**Language Specifications**](#language-specifications) * [Can a user on Source Academy import more than one module at once?](https://github.com/source-academy/modules/wiki/FAQs#can-a-user-on-source-academy-import-more-than-one-module-at-once) -* [**Tabs**](https://github.com/source-academy/modules/wiki/FAQs#tabs) - * [Why is my Tab not spawning on Source Academy?](https://github.com/source-academy/modules/wiki/FAQs#why-is-my-tab-not-spawning-on-source-academy) - * [How does the modules system handles css for the tabs?](https://github.com/source-academy/modules/wiki/Frequently-Asked-Questions-(FAQs)#how-does-the-modules-system-handles-css-for-the-tabs) -* [**Interaction with `js-slang`**](https://github.com/source-academy/modules/wiki/FAQs#interaction-with-js-slang) - * [How can we switch to the Source interpreter rather than the Source transpiler?](https://github.com/source-academy/modules/wiki/FAQs#how-can-we-switch-to-the-source-interpreter-rather-than-the-source-transpiler) - ---- +* [**Tabs**](#tabs) + * [Why is my Tab not spawning on Source Academy?](#why-is-my-tab-not-spawning-on-source-academy) + * [How does the modules system handles css for the tabs?](#how-does-the-modules-system-handles-css-for-the-tabs) +* [**Interaction with `js-slang`**](#interaction-with-js-slang) + * [How can we switch to the Source interpreter rather than the Source transpiler?](#how-can-we-switch-to-the-source-interpreter-rather-than-the-source-transpiler) ## Infrastructure @@ -26,7 +24,7 @@ title: FAQ > 1. `bundle/**/*.ts` is where we store all logical functions > 2. `tabs/**/*.tsx` is where we use all the react and jsx which will be shown in the frontend -A brief overview of the current infrastructure is explained [here](https://github.com/source-academy/modules/wiki/Flow-of-Module-System). However, I would like to explain in more detail how the `bundle` and `tab` interacts with `js-slang` and `cadet-frontend` respectively. +A brief overview of the current infrastructure is explained [here](../4-advanced/flow/flow). However, I would like to explain in more detail how the `bundle` and `tab` interacts with `js-slang` and `cadet-frontend` respectively. Firstly, a `bundle` is defined as the suite of functions that are provided by the module. More specifically, what we mean by this is that the bundle will provide all the functions and constants that are intended to be available for use by the cadet when programming in the Source language. ```ts @@ -35,11 +33,9 @@ import { install_filter, video_height, video_width, init } from "pix_n_flix"; install_filter((src, dest) => { ... }); init(); ``` -An example of the code that makes use of a module written in the Source language is given above. The main objective of the `bundle` is to provide the behind the scenes implementation of the `install_filter`, `video_height`, `video_width` and `init` functions so that the cadet programming in Source language can use it as a black box. In the above example, you can find the implementations of the said functions [here](https://github.com/source-academy/modules/blob/master/src/bundles/pix_n_flix/index.ts). The `bundle` however, does not store all logical functions that is required by your module like those needed only by the tab that are not provided to the cadets. To see how the implementation of a `bundle` looks like, refer to [here](https://github.com/source-academy/modules/wiki/Modules-Structure#bundles). +An example of the code that makes use of a module written in the Source language is given above. The main objective of the `bundle` is to provide the behind the scenes implementation of the `install_filter`, `video_height`, `video_width` and `init` functions so that the cadet programming in Source language can use it as a black box. In the above example, you can find the implementations of the said functions [here](https://github.com/source-academy/modules/blob/master/src/bundles/pix_n_flix/index.ts). The `bundle` however, does not store all logical functions that is required by your module like those needed only by the tab that are not provided to the cadets. -A `tab` on the other hand is more formally defined as an _optional_ user interface used by the module. A module can exist without a tab. The tab exists for developers of modules to make use of Source Academy's side content tabs to display user interfaces that are used with the module's bundle. The tab can optionally choose to make use of the result returned from the evaluation of the Source code. The tab can also even be entirely not dependent on the result from the evaluation of the Source code. To see how the implementation of a `tab` looks like, refer to [here](https://github.com/source-academy/modules/wiki/Modules-Structure#tabs). - -So how then does the `tab` make use of information from the `bundle`? The tab does this through the use of an object from `cadet-frontend` called the `debuggerContext`. When constructing the side content tabs, we would occasionally want to make use of front-end data like the code that was evaluated, the result of the code evaluated and Source version used. On `cadet-frontend`, all these information is stored within the `debuggerContext` object which is defined [here](https://github.com/source-academy/cadet-frontend/blob/master/src/commons/workspace/WorkspaceTypes.ts). Hence, all tabs will receieve the object `debuggerContext` as a component prop. +A `tab` on the other hand is more formally defined as an _optional_ user interface used by the module. A module can exist without a tab. The tab exists for developers of modules to make use of Source Academy's side content tabs to display user interfaces that are used with the module's bundle. The tab can optionally choose to make use of the result returned from the evaluation of the Source code. The tab can also even be entirely not dependent on the result from the evaluation of the Source code. So how then does the `tab` make use of information from the `bundle`? The tab does this through the use of an object from `cadet-frontend` called the `debuggerContext`. When constructing the side content tabs, we would occasionally want to make use of front-end data like the code that was evaluated, the result of the code evaluated and Source version used. On `cadet-frontend`, all these information is stored within the `debuggerContext` object which is defined [here](https://github.com/source-academy/cadet-frontend/blob/master/src/commons/workspace/WorkspaceTypes.ts). Hence, all tabs will receieve the object `debuggerContext` as a component prop. An example of an implementation of this is from the `pix_n_flix` module. The implementation can be found [here](https://github.com/source-academy/modules/blob/master/src/bundles/pix_n_flix/index.ts). In the module, a function `init()` is provided to the Source programmer. The specifications of the `pix_n_flix` module requires this `init()` function to be applied as the last statement of the Source program. As a result, the `js-slang` evaluator will return the return value of the `init()` function which is a JavaScript object with the type signature shown below. ```ts @@ -50,8 +46,6 @@ An example of an implementation of this is from the `pix_n_flix` module. The imp ``` As described in the paragraphs above, this return value of the `init()` function will be stored within the `debuggerContext` in `debuggerContext.result.value`. The `tab` associated with the rendering of the video and canvas element will then render the HTMLVideoElement and HTMLCanvasElement, before creating references to the respective elements and applying the `init()` function in `debuggerContext.result.value` in the component's `componentDidMount()` method. -See [here](https://github.com/source-academy/modules/wiki/System-Implementation-in-Source-Academy-Frontend#interaction-with-the-source-modules-bundle) for more information regarding this process. - ## Set Up and Configuration ### How do we use our own local version of the js-slang interpreter with the modules? diff --git a/docs/src/modules/2-bundle/4-documentation/4-documentation.md b/docs/src/modules/2-bundle/4-documentation/4-documentation.md index 9b2c4b4e1d..0f98d76054 100644 --- a/docs/src/modules/2-bundle/4-documentation/4-documentation.md +++ b/docs/src/modules/2-bundle/4-documentation/4-documentation.md @@ -1,4 +1,4 @@ -# Bundle Documentation +# Bundle Documentation for Users Documentation for Source bundles is generated using the [`typedoc`](https://typedoc.org) tool. There are two types: HTML and JSON documentation. By reading comments and type annotations, `typedoc` is able to generate both human readable documentation and documentation in the form of JSON. @@ -57,7 +57,7 @@ It is important that you provide an `@module` tag in this description. Otherwise documentation properly. ### Use of `@hidden` -If there are exports you want hidden from the output of the documentation, you must use the either `@hidden` tag. +If there are exports you want hidden from the output of the documentation, you must use the `@hidden` tag. The example below is taken from the `rune` bundle: ```ts @@ -113,7 +113,7 @@ Typedoc won't consider `draw_connected` to be a function. Instead it will consid This is because `drawConnected` is of type `RenderFunction` and `RenderFunction` is only _function-like_. -To remedy this, include the `@function` tag in your documentation: +To remedy this, you can either change the type to be an actual function type, or include the `@function` tag in your documentation: ```ts {6} /** * Returns a function that turns a given Curve into a Drawing, by sampling the diff --git a/docs/src/modules/2-bundle/5-type_map.md b/docs/src/modules/2-bundle/5-type_map.md index 3b6617353a..426963bb28 100644 --- a/docs/src/modules/2-bundle/5-type_map.md +++ b/docs/src/modules/2-bundle/5-type_map.md @@ -31,7 +31,22 @@ export const type_map = typeMapCreator.type_map; Note that the type map export has a `@hidden` documentation tag applied to it. -To configure the type map, you then use the functions returned by the utility as [decorators](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators): +> [!IMPORTANT] +> The `@hidden` tag needs to be applied at the point of declaration. The code below doesn't hide `type_map` as `@hidden` is being applied to where `type_map` is being exported, +> and not where it is being declared: +> ```ts +> const { type_map } = createTypeMap() +> export { +> /** @hidden */ +> type_map +> } +> ``` +> Hence in the example above, the exports aren't consolidated and written using the export shorthand as seen below: +> ```ts +> export const { functionDeclaration, variableDeclaration, classDeclaration, type_map, typeDeclaration } = createTypeMap(); +> ``` + +To configure the type map, you use the [decorators](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#decorators) returned by the utility: ```ts // rune/src/functions.ts @@ -80,8 +95,10 @@ export const translate = RuneFunctions.translate; > the documentation is properly applied to the exported function. > > Also notice that the `@function` tag has been applied. More information about why this is necessary can be found [here](./4-documentation/4-documentation#use-of-function) +> +> A quick way to check if you have written your documentation correctly is to see if IntelliSense in VSCode is able to display it to you. -Remember to export your type map from the bundle's entry point: +Remember to export the type map from the bundle's entry point: ```ts // rune/src/index.ts export { type_map } from './type_map'; @@ -91,7 +108,7 @@ export { type_map } from './type_map'; There are four decorators returned by `createTypeMap`: ### `variableDeclaration` -Use this decorator to type constant declarations that are not supposed to behave like functions. The decorator takes one parameter, which is the string +Use this decorator to type constant declarations that are **not supposed to behave like functions**. The decorator takes one parameter, which is the string representation of the type of the variable. ```ts @@ -136,7 +153,7 @@ This decorator can only be applied to members of a class, so it may be necessary have them declared as static members. ### `classDeclaration` -This declarator is used to represent actual classes. It takes parameter, the string representation of the type it is wrapping. +This declarator is used to represent actual classes. It takes one parameter, the string representation of the type it is wrapping. The decorator can be applied directly to the class: ```ts diff --git a/docs/src/modules/4-advanced/cross-use.md b/docs/src/modules/4-advanced/cross-use.md index 729418cb77..d40dcf8070 100644 --- a/docs/src/modules/4-advanced/cross-use.md +++ b/docs/src/modules/4-advanced/cross-use.md @@ -1,6 +1,6 @@ # Bundles as Libraries -Since bundles are designed to be imported from Source user code, they can also be imported from other bundles (and tabs!). The steps required for configuraing your bundle to be able to be do so can be found [here](/modules/2-bundle/6-compiling#for-other-bundles). +Since bundles are designed to be imported from Source user code, they can also be imported from other bundles (and tabs!). The steps required for configuring your bundle to be able to be do so can be found [here](/modules/2-bundle/6-compiling#for-other-bundles). Bundles can then be added as dependencies as if they were any other `npm` or Yarn package: diff --git a/docs/src/modules/4-advanced/testing.md b/docs/src/modules/4-advanced/testing.md index 7b7c6c4548..2213433860 100644 --- a/docs/src/modules/4-advanced/testing.md +++ b/docs/src/modules/4-advanced/testing.md @@ -5,7 +5,7 @@ order: 3 The testing library used by this repository is [`vitest`](https://vitest.dev). -> [!WARNING] +> [!IMPORTANT] > Other Source Academy repositories use `jest` as their testing package. Although `vitest` has been designed as a drop in replacement for `jest`, > there are subtle differences between the two. For example, `vi.spyOn` doesn't replace the implementation within the module while `jest.spyOn` does (See [here](https://vitest.dev/guide/mocking.html#mocking-pitfalls)). > diff --git a/docs/src/repotools/docserver/docserver.md b/docs/src/repotools/docserver/docserver.md index 0823a6ba27..9618f68198 100644 --- a/docs/src/repotools/docserver/docserver.md +++ b/docs/src/repotools/docserver/docserver.md @@ -3,7 +3,7 @@ Originally most of the developer documentation was contained within the Github repository's wiki. However, as the documentation became more comprehensive in scope and complex in design, it was necessary to migrate to a more advanced and configurable solution. -This documentation server is powered by [Vitepress](https://vitepress.dev), which takes Markdown files and renders them into a static site. +This documentation server is powered by [Vitepress](https://vitepress.dev), which takes Markdown files and renders them into a (mostly) static site. ## File Structure All pages for the server are contained under `src`. @@ -49,7 +49,7 @@ Instead, the links in the sidebar link to the page (if it is a markdown file), o The specific configuration for this behaviour can be found in the Vitepress config file, and the documentation for those options can be found on the Vitepress website: -<<< ../../../.vitepress/config.ts {33-43} +<<< ../../../.vitepress/config.ts {65-75 ts:line-numbers} ## Documentation for `modules-lib` The documentation for `@sourceacademy/modules-lib` is automatically generated by Typedoc. The configuration options for this generation are found in that folder. The documentation for `@sourceacademy/modules-lib` should be built before this server is built. @@ -58,3 +58,7 @@ The documentation for `@sourceacademy/modules-lib` is automatically generated by Supposedly Vitepress can embed React components, which would be nice for us to have working demonstrations of our React components. Unfortunately, I have never been able to get that to work. You can start [here](https://github.com/vuejs/vitepress/discussions/2183) if you want to help figure this out. ::: + +## Diagrams +Diagrams (such as the one seen [here](/buildtools/4-resolution.html#bundle-resolution)) are supported via the [`mermaid`](https://mermaid.js.org) rendering engine and provided with the [Vitepress plugin for mermaid](https://github.com/emersonbottero/vitepress-plugin-mermaid). +Simply use a Markdown code block with the `mermaid` language tag. diff --git a/docs/.vitepress/tsconfig.json b/docs/tsconfig.json similarity index 79% rename from docs/.vitepress/tsconfig.json rename to docs/tsconfig.json index e35ed46dc1..11f720485b 100644 --- a/docs/.vitepress/tsconfig.json +++ b/docs/tsconfig.json @@ -6,5 +6,5 @@ "resolveJsonModule": true, "verbatimModuleSyntax": true }, - "include": ["./config.ts"] + "include": ["./.vitepress/config.ts", "src"] } From 299c19c8212e8270c0e3fffc4aa75e0ba7a8e9ea Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sun, 29 Jun 2025 21:30:16 +0800 Subject: [PATCH 096/112] Enable import sorting via eslint plugin --- .github/workflows/pull-request.yml | 2 +- devserver/src/components/ControlButton.tsx | 2 +- devserver/src/components/Playground.tsx | 4 +- devserver/src/components/Workspace.tsx | 2 +- .../src/components/__tests__/Playground.tsx | 2 +- .../components/sideContent/SideContent.tsx | 2 +- eslint.config.js | 9 +- lib/buildtools/src/__tests__/utils.test.ts | 2 +- .../src/build/manifest/formatters.ts | 6 +- lib/buildtools/src/build/manifest/index.ts | 81 +- lib/buildtools/src/build/manifest/types.ts | 13 + .../build/modules/__tests__/building.test.ts | 2 +- .../src/build/modules/__tests__/tab.test.ts | 2 +- lib/buildtools/src/build/modules/index.ts | 2 +- .../src/commands/__tests__/lint.test.ts | 30 +- lib/buildtools/src/commands/build.ts | 2 +- lib/buildtools/src/commands/prebuild.ts | 36 +- lib/buildtools/src/commands/template.ts | 2 +- lib/buildtools/src/commands/testing.ts | 14 +- lib/buildtools/src/prebuild/index.ts | 2 +- lib/buildtools/src/prebuild/lint.ts | 10 +- .../src/templates/__tests__/names.test.ts | 2 +- lib/buildtools/src/templates/bundle.ts | 2 +- lib/buildtools/src/templates/print.ts | 2 +- lib/buildtools/src/templates/tab.ts | 2 +- lib/buildtools/src/types.ts | 16 +- lib/buildtools/src/utils.ts | 7 + lib/modules-lib/src/tabs/ButtonComponent.tsx | 2 +- package.json | 2 +- src/bundles/ar/src/ObjectsHelper.ts | 6 +- src/bundles/arcade_2d/src/functions.ts | 50 +- src/bundles/arcade_2d/src/phaserScene.ts | 6 +- .../communication/src/MqttController.ts | 2 +- src/bundles/csg/src/functions.ts | 8 +- src/bundles/csg/src/jscad/types.ts | 2 +- src/bundles/curve/manifest.json | 3 +- src/bundles/game/src/functions.ts | 6 +- src/bundles/mark_sweep/src/index.ts | 2 +- src/bundles/nbody/src/Force.ts | 2 +- src/bundles/nbody/src/Simulation.ts | 2 +- src/bundles/nbody/src/State.ts | 2 +- src/bundles/nbody/src/Transformation.ts | 2 +- src/bundles/nbody/src/Universe.ts | 2 +- src/bundles/painter/src/functions.ts | 2 +- src/bundles/physics_2d/src/PhysicsObject.ts | 8 +- src/bundles/physics_2d/src/PhysicsWorld.ts | 12 +- src/bundles/physics_2d/src/functions.ts | 2 +- src/bundles/pix_n_flix/src/functions.ts | 24 +- src/bundles/plotly/src/curve_functions.ts | 2 +- src/bundles/plotly/src/functions.ts | 2 +- src/bundles/repeat/src/__tests__/index.ts | 4 +- src/bundles/repl/src/programmable_repl.ts | 2 +- .../__tests__/ev3/components/Chassis.ts | 2 +- .../__tests__/ev3/components/Motor.ts | 2 +- .../__tests__/ev3/ev3/default/ev3.ts | 4 +- .../ev3/feedback_control/PidController.ts | 2 +- .../controllers/__tests__/program/Program.ts | 2 +- .../src/controllers/ev3/components/Chassis.ts | 6 +- .../src/controllers/ev3/components/Wheel.ts | 2 +- .../src/controllers/ev3/ev3/default/ev3.ts | 6 +- .../src/engine/Render/Renderer.ts | 2 +- .../src/engine/Render/helpers/GLTF.ts | 2 +- .../robot_simulation/src/engine/World.ts | 2 +- .../src/engine/__tests__/Core/Controller.ts | 2 +- .../src/engine/__tests__/Core/Events.ts | 2 +- .../src/engine/__tests__/Core/RobotConsole.ts | 2 +- .../src/engine/__tests__/Math/Convert.ts | 6 +- .../engine/__tests__/Render/helpers/Camera.ts | 2 +- .../robot_simulation/src/helper_functions.ts | 2 +- src/bundles/rune/src/functions.ts | 26 +- src/bundles/rune/src/rune.ts | 2 +- src/bundles/rune_in_words/src/functions.ts | 12 +- src/bundles/scrabble/src/__tests__/index.ts | 2 +- src/bundles/sound/src/functions.ts | 14 +- src/bundles/unittest/src/__tests__/index.ts | 2 +- src/bundles/unittest/src/asserts.ts | 2 +- src/bundles/unittest/src/functions.ts | 2 +- src/bundles/unittest/src/mocks.ts | 2 +- .../unity_academy/src/UnityAcademy.tsx | 4 +- src/bundles/unity_academy/src/functions.ts | 8 +- .../AugmentedReality/src/AugmentedContent.tsx | 4 +- src/tabs/CopyGc/src/index.tsx | 2 +- .../Curve/src/animation_canvas_3d_curve.tsx | 4 +- src/tabs/Curve/src/canvas_3d_curve.tsx | 2 +- src/tabs/Curve/src/index.tsx | 2 +- src/tabs/MarkSweep/src/index.tsx | 2 +- src/tabs/Painter/index.tsx | 2 +- src/tabs/Pixnflix/index.tsx | 2 +- src/tabs/Plotly/index.tsx | 2 +- .../src/components/Simulation/index.tsx | 2 +- src/tabs/Rune/src/index.tsx | 4 +- src/tabs/Sound/index.tsx | 2 +- src/tabs/StereoSound/index.tsx | 2 +- src/tabs/Unittest/index.tsx | 2 +- src/tabs/UnityAcademy/index.tsx | 2 +- vitest.config.js | 2 +- yarn.lock | 1376 ++++++++++++++++- 97 files changed, 1651 insertions(+), 302 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 322a252834..099bb3efb2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -87,7 +87,7 @@ jobs: - name: Build Docs Server if: needs.paths-filter.outputs.docs == 'true' - run: yarn workspaces foreach -A --include "@sourceacademy/mdoules-docserver" run build + run: yarn workspaces foreach -A --include "@sourceacademy/modules-docserver" run build - name: Run tsc for Dev Server if: needs.paths-filter.outputs.devserver == 'true' diff --git a/devserver/src/components/ControlButton.tsx b/devserver/src/components/ControlButton.tsx index 4b64f6ffe7..c23e24921f 100644 --- a/devserver/src/components/ControlButton.tsx +++ b/devserver/src/components/ControlButton.tsx @@ -1,4 +1,4 @@ -import { AnchorButton, Button, Icon, type IconName, Intent } from '@blueprintjs/core'; +import { AnchorButton, Button, Icon, Intent, type IconName } from '@blueprintjs/core'; import React from 'react'; type ButtonOptions = { diff --git a/devserver/src/components/Playground.tsx b/devserver/src/components/Playground.tsx index 0e7c6a0eea..edba1ac759 100644 --- a/devserver/src/components/Playground.tsx +++ b/devserver/src/components/Playground.tsx @@ -1,7 +1,7 @@ -import { type ToastProps, Intent, OverlayToaster, Popover, Tooltip, Button, Classes } from '@blueprintjs/core'; +import { Button, Classes, Intent, OverlayToaster, Popover, Tooltip, type ToastProps } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import classNames from 'classnames'; -import { type Context, getNames, SourceDocumentation, runInContext } from 'js-slang'; +import { SourceDocumentation, getNames, runInContext, type Context } from 'js-slang'; // Importing this straight from js-slang doesn't work for whatever reason import createContext from 'js-slang/dist/createContext'; import { Chapter, Variant } from 'js-slang/dist/types'; diff --git a/devserver/src/components/Workspace.tsx b/devserver/src/components/Workspace.tsx index 858f7a9f1c..8616ad03ea 100644 --- a/devserver/src/components/Workspace.tsx +++ b/devserver/src/components/Workspace.tsx @@ -1,5 +1,5 @@ import { FocusStyleManager } from '@blueprintjs/core'; -import { type Enable, Resizable, type ResizeCallback } from 're-resizable'; +import { Resizable, type Enable, type ResizeCallback } from 're-resizable'; import React from 'react'; import ControlBar, { type ControlBarProps } from './controlBar/ControlBar'; diff --git a/devserver/src/components/__tests__/Playground.tsx b/devserver/src/components/__tests__/Playground.tsx index 3d4f65890b..a4e19386db 100644 --- a/devserver/src/components/__tests__/Playground.tsx +++ b/devserver/src/components/__tests__/Playground.tsx @@ -1,4 +1,4 @@ -import { userEvent, commands } from '@vitest/browser/context'; +import { commands, userEvent } from '@vitest/browser/context'; import { runInContext } from 'js-slang'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { render, type RenderResult } from 'vitest-browser-react'; diff --git a/devserver/src/components/sideContent/SideContent.tsx b/devserver/src/components/sideContent/SideContent.tsx index 27125198f9..2088c0d375 100644 --- a/devserver/src/components/sideContent/SideContent.tsx +++ b/devserver/src/components/sideContent/SideContent.tsx @@ -1,4 +1,4 @@ -import { Card, Icon, Tab, type TabProps, Tabs, Tooltip } from '@blueprintjs/core'; +import { Card, Icon, Tab, Tabs, Tooltip, type TabProps } from '@blueprintjs/core'; import React from 'react'; import type { SideContentTab } from './types'; diff --git a/eslint.config.js b/eslint.config.js index 7c12a41c2d..8508cd3b9a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,7 +10,6 @@ import reactHooksPlugin from 'eslint-plugin-react-hooks'; import vitestPlugin from 'eslint-plugin-vitest'; import globals from 'globals'; import jsonParser from 'jsonc-eslint-parser'; - import tseslint from 'typescript-eslint'; const todoTreeKeywordsWarning = ['TODO', 'TODOS', 'TODO WIP', 'FIXME', 'WIP']; @@ -72,9 +71,7 @@ export default tseslint.config( plugins: { markdown }, rules: { 'markdown/no-missing-label-refs': 'off', // was error - // Frontmatter titles are used for navigation only, so its okay - // to have both that and a h1 element - 'markdown/no-multiple-h1': ['error', { frontmatterTitle: '' }], + 'markdown/no-multiple-h1': 'off', // was error 'markdown/require-alt-text': 'off', // was error } }, @@ -108,6 +105,10 @@ export default tseslint.config( 'warn', { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + named: { + import: true, + types: 'types-last' + }, alphabetize: { order: 'asc', orderImportKind: 'asc' diff --git a/lib/buildtools/src/__tests__/utils.test.ts b/lib/buildtools/src/__tests__/utils.test.ts index d5fbb22505..f22d6396c7 100644 --- a/lib/buildtools/src/__tests__/utils.test.ts +++ b/lib/buildtools/src/__tests__/utils.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect } from 'vitest'; +import { describe, expect, test } from 'vitest'; import { compareSeverity, filterAsync, findSeverity, type Severity } from '../utils.js'; describe('test findSeverity', () => { diff --git a/lib/buildtools/src/build/manifest/formatters.ts b/lib/buildtools/src/build/manifest/formatters.ts index 9eec61e64e..f330904e33 100644 --- a/lib/buildtools/src/build/manifest/formatters.ts +++ b/lib/buildtools/src/build/manifest/formatters.ts @@ -1,6 +1,8 @@ -import type { ResolveSingleBundleError, ResolveAllBundlesError } from './types.js'; +import type { ResolveAllBundlesError, ResolveSingleBundleError } from './types.js'; -export function formatResolveBundleErrors(result: ResolveAllBundlesError | ResolveSingleBundleError): string { +export function formatResolveBundleErrors( + result: ResolveAllBundlesError | ResolveSingleBundleError +): string { const strings = result.errors.map(each => `${each}`); return strings.join('\n'); } diff --git a/lib/buildtools/src/build/manifest/index.ts b/lib/buildtools/src/build/manifest/index.ts index 5071756112..50c0fff62d 100644 --- a/lib/buildtools/src/build/manifest/index.ts +++ b/lib/buildtools/src/build/manifest/index.ts @@ -5,7 +5,7 @@ import { validate } from 'jsonschema'; import uniq from 'lodash/uniq.js'; import { getTabsDir } from '../../getGitRoot.js'; import type { BundleManifest, ManifestResult, ResolvedBundle, ResolvedTab } from '../../types.js'; -import { filterAsync, isNodeError } from '../../utils.js'; +import { filterAsync, isNodeError, mapAsync } from '../../utils.js'; import { createBuilder } from '../buildUtils.js'; import manifestSchema from '../modules/manifest.schema.json' with { type: 'json' }; import { @@ -14,6 +14,7 @@ import { type GetBundleManifestResult, type ResolveAllBundlesResult, type ResolveAllTabsResult, + type ResolveEitherResult, type ResolveSingleBundleResult } from './types.js'; @@ -161,14 +162,26 @@ export async function resolveSingleBundle(bundleDir: string): Promise { - const subdirs = await fs.readdir(bundlesDir); - const manifests = await Promise.all(subdirs.map(subdir => { - const fullPath = pathlib.join(bundlesDir, subdir); + const subdirs = await fs.readdir(bundlesDir, { withFileTypes: true }); + const manifests = await mapAsync(subdirs, async each => { + if (!each.isDirectory()) return undefined; + + const fullPath = pathlib.join(bundlesDir, each.name); return resolveSingleBundle(fullPath); - })); + }); const [combinedManifests, errors] = manifests.reduce<[Record, any[]]>(([res, errors], entry) => { if (entry === undefined) return [res, errors]; @@ -234,11 +249,12 @@ export async function resolveAllBundles(bundlesDir: string): Promise { const fullyResolved = pathlib.resolve(tabDir); + + try { + const dirStats = await fs.stat(fullyResolved); + if (!dirStats.isDirectory()) { + return undefined; + } + } catch { + return undefined; + } + const tabPath = await resolvePaths( + false, `${fullyResolved}/src/index.tsx`, `${fullyResolved}/index.tsx` ); @@ -313,15 +340,37 @@ export async function resolveAllTabs(bundlesDir: string, tabsDir: string): Promi } /** - * Attempts to resolve the given directory as a bundle or a tab. Returns `undefined` - * if it was unable to do either. + * Attempts to resolve the given directory as a bundle or a tab. Returns an object with `asset: undefined` + * if it was unable to do either. Otherwise, it returns the error(s) associated with resolving the bundle or tab */ -export async function resolveEitherBundleOrTab(directory: string) { +export async function resolveEitherBundleOrTab(directory: string): Promise { const bundle = await resolveSingleBundle(directory); - if (!bundle || bundle.severity !== 'success') { + if (!bundle) { const tab = await resolveSingleTab(directory); - return tab; + if (tab === undefined) { + return { + severity: 'error', + errors: [], + asset: undefined + }; + } + + return { + severity: 'success', + asset: tab + }; } - return bundle.bundle; + if (bundle.severity === 'error') { + return { + severity: 'error', + errors: bundle.errors, + asset: undefined + }; + } + + return { + severity: 'success', + asset: bundle.bundle + }; } diff --git a/lib/buildtools/src/build/manifest/types.ts b/lib/buildtools/src/build/manifest/types.ts index 67aa7fd3ba..2c307233f2 100644 --- a/lib/buildtools/src/build/manifest/types.ts +++ b/lib/buildtools/src/build/manifest/types.ts @@ -39,6 +39,19 @@ interface ResolveAllTabsSuccess { export type ResolveAllTabsError = GetBundleManifestError; export type ResolveAllTabsResult = ResolveAllTabsError | ResolveAllTabsSuccess; +export interface ResolveEitherSuccess { + asset: ResolvedBundle | ResolvedTab + severity: 'success' +} + +export interface ResolveEitherError { + asset: undefined + errors: unknown[] + severity: 'error' +} + +export type ResolveEitherResult = ResolveEitherError | ResolveEitherSuccess; + export class MissingTabError extends Error { constructor(public readonly name: string) { super(); } diff --git a/lib/buildtools/src/build/modules/__tests__/building.test.ts b/lib/buildtools/src/build/modules/__tests__/building.test.ts index eff6792387..e7b0e82233 100644 --- a/lib/buildtools/src/build/modules/__tests__/building.test.ts +++ b/lib/buildtools/src/build/modules/__tests__/building.test.ts @@ -1,5 +1,5 @@ import fs from 'fs/promises'; -import { beforeEach, expect, vi, test } from 'vitest'; +import { beforeEach, expect, test, vi } from 'vitest'; import { testMocksDir } from '../../../__tests__/fixtures.js'; import { writeManifest } from '../../manifest/index.js'; import { buildBundle } from '../bundle.js'; diff --git a/lib/buildtools/src/build/modules/__tests__/tab.test.ts b/lib/buildtools/src/build/modules/__tests__/tab.test.ts index 0982119084..d5e2bccc86 100644 --- a/lib/buildtools/src/build/modules/__tests__/tab.test.ts +++ b/lib/buildtools/src/build/modules/__tests__/tab.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, test, vi } from 'vitest'; import { expectIsSuccess, testMocksDir } from '../../../__tests__/fixtures.js'; -import { resolveSingleTab, resolveAllTabs } from '../../manifest/index.js'; +import { resolveAllTabs, resolveSingleTab } from '../../manifest/index.js'; vi.mock(import('../../../getGitRoot.js')); diff --git a/lib/buildtools/src/build/modules/index.ts b/lib/buildtools/src/build/modules/index.ts index 2d8665f1d4..1aa2b0fd9c 100644 --- a/lib/buildtools/src/build/modules/index.ts +++ b/lib/buildtools/src/build/modules/index.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises'; import uniq from 'lodash/uniq.js'; -import { type PrebuildOptions, runBuilderWithPrebuild } from '../../prebuild/index.js'; +import { runBuilderWithPrebuild, type PrebuildOptions } from '../../prebuild/index.js'; import type { FullResult, ResolvedBundle, TabResultEntry } from '../../types.js'; import { resolveSingleTab } from '../manifest/index.js'; import { buildBundle } from './bundle.js'; diff --git a/lib/buildtools/src/commands/__tests__/lint.test.ts b/lib/buildtools/src/commands/__tests__/lint.test.ts index 778a4c30be..0d74d3161f 100644 --- a/lib/buildtools/src/commands/__tests__/lint.test.ts +++ b/lib/buildtools/src/commands/__tests__/lint.test.ts @@ -1,5 +1,5 @@ import { ESLint } from 'eslint'; -import { beforeEach, vi, expect, describe, test } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import * as manifest from '../../build/manifest/index.js'; import * as utils from '../../getGitRoot.js'; import { getLintCommand } from '../prebuild.js'; @@ -35,10 +35,13 @@ const runCommand = getCommandRunner(getLintCommand); describe('Test Lint Command', () => { beforeEach(() => { mockedResolveEither.mockResolvedValueOnce({ - type: 'bundle', - directory: '', - manifest: {}, - name: 'test0' + severity: 'success', + asset: { + type: 'bundle', + directory: '', + manifest: {}, + name: 'test0' + } }); }); @@ -127,17 +130,24 @@ describe('Test Lint Command', () => { describe('Test Lint command directory resolution', () => { test('Lint command resolving a tab', async () => { mockedResolveEither.mockResolvedValueOnce({ - type: 'tab', - directory: '', - entryPoint: '', - name: 'tab0' + severity: 'success', + asset: { + type: 'tab', + directory: '', + entryPoint: '', + name: 'tab0' + } }); lintFilesMock.mockResolvedValueOnce([{ warningCount: 0 }]); await expect(runCommand()).commandSuccess(); }); test('Lint command resolving neither', async () => { - mockedResolveEither.mockResolvedValueOnce(undefined); + mockedResolveEither.mockResolvedValueOnce({ + severity: 'error', + errors: [], + asset: undefined + }); await expect(runCommand()).commandExit(); expect(lintFilesMock).not.toHaveBeenCalled(); }); diff --git a/lib/buildtools/src/commands/build.ts b/lib/buildtools/src/commands/build.ts index 83d13452d0..b7114e748e 100644 --- a/lib/buildtools/src/commands/build.ts +++ b/lib/buildtools/src/commands/build.ts @@ -22,7 +22,7 @@ export const getBuildBundleCommand = () => new Command('bundle') .option('--ci', 'Run in CI mode', !!process.env.CI) .action(async (bundleDir, opts) => { const result = await resolveSingleBundle(bundleDir); - if (!result) logCommandErrorAndExit(`No bundle found at ${bundleDir}!`); + if (result === undefined) logCommandErrorAndExit(`No bundle found at ${bundleDir}!`); else if (result.severity === 'error') { logCommandErrorAndExit(formatResolveBundleErrors(result)); } diff --git a/lib/buildtools/src/commands/prebuild.ts b/lib/buildtools/src/commands/prebuild.ts index 0bac72dd69..996bb5a697 100644 --- a/lib/buildtools/src/commands/prebuild.ts +++ b/lib/buildtools/src/commands/prebuild.ts @@ -1,6 +1,7 @@ import pathlib from 'path'; import { Command } from '@commander-js/extra-typings'; import chalk from 'chalk'; +import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; import { resolveEitherBundleOrTab } from '../build/manifest/index.js'; import { runPrebuild } from '../prebuild/index.js'; import { formatLintResult, lintGlobal, runEslint } from '../prebuild/lint.js'; @@ -20,14 +21,17 @@ export const getLintCommand = () => new Command('lint') .option('--ci', process.env.CI) .action(async (directory, { fix, ci }) => { const fullyResolved = pathlib.resolve(directory); + const resolveResult = await resolveEitherBundleOrTab(fullyResolved); - const asset = await resolveEitherBundleOrTab(fullyResolved); + if (resolveResult.severity === 'error') { + if (resolveResult.errors.length === 0 ) { + logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + } - if (!asset) { - logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + logCommandErrorAndExit(formatResolveBundleErrors(resolveResult)); } - const result = await runEslint(asset, fix); + const result = await runEslint(resolveResult.asset, fix); console.log(formatLintResult(result)); switch (result.severity) { @@ -77,13 +81,17 @@ export const getTscCommand = () => new Command('tsc') .option('--ci', process.env.CI) .action(async (directory, { emit, ci }) => { const fullyResolved = pathlib.resolve(directory); - const asset = await resolveEitherBundleOrTab(fullyResolved); + const resolveResult = await resolveEitherBundleOrTab(fullyResolved); + + if (resolveResult.severity === 'error') { + if (resolveResult.errors.length === 0 ) { + logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + } - if (!asset) { - logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + logCommandErrorAndExit(formatResolveBundleErrors(resolveResult)); } - const result = await runTsc(asset, !emit); + const result = await runTsc(resolveResult.asset, !emit); console.log(formatTscResult(result)); switch (result.severity) { @@ -101,13 +109,17 @@ export const getPrebuildAllCommand = () => new Command('prebuild') .option('--ci', process.env.CI) .action(async (directory, { ci }) => { const fullyResolved = pathlib.resolve(directory); - const asset = await resolveEitherBundleOrTab(fullyResolved); + const resolveResult = await resolveEitherBundleOrTab(fullyResolved); + + if (resolveResult.severity === 'error') { + if (resolveResult.errors.length === 0 ) { + logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + } - if (!asset) { - logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + logCommandErrorAndExit(formatResolveBundleErrors(resolveResult)); } - const { tsc, lint } = await runPrebuild(asset); + const { tsc, lint } = await runPrebuild(resolveResult.asset); console.log(formatTscResult(tsc)); console.log(formatLintResult(lint)); diff --git a/lib/buildtools/src/commands/template.ts b/lib/buildtools/src/commands/template.ts index cb2509efae..263c6bf86f 100644 --- a/lib/buildtools/src/commands/template.ts +++ b/lib/buildtools/src/commands/template.ts @@ -3,7 +3,7 @@ import { Command } from '@commander-js/extra-typings'; import { getBundlesDir, getTabsDir } from '../getGitRoot.js'; import { addNew as addNewModule } from '../templates/bundle.js'; -import { error as _error, askQuestion, getRl, info, warn } from '../templates/print.js'; +import { askQuestion, error as _error, getRl, info, warn } from '../templates/print.js'; import { addNew as addNewTab } from '../templates/tab.js'; import { isNodeError } from '../utils.js'; diff --git a/lib/buildtools/src/commands/testing.ts b/lib/buildtools/src/commands/testing.ts index 23cf1e03ac..4a1676f48e 100644 --- a/lib/buildtools/src/commands/testing.ts +++ b/lib/buildtools/src/commands/testing.ts @@ -1,6 +1,7 @@ import { resolve } from 'path'; import { Command, Option } from '@commander-js/extra-typings'; import type { VitestRunMode } from 'vitest/node'; +import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; import { resolveEitherBundleOrTab } from '../build/manifest/index.js'; import { runIndividualVitest } from '../testing.js'; import { logCommandErrorAndExit } from './commandUtils.js'; @@ -24,10 +25,15 @@ export const getTestCommand = () => new Command('test') .argument('[directory]', 'Directory to search for tests. If no directory is specified, the current working directory is used') .action(async (directory, { mode, ...options }) => { const fullyResolved = resolve(directory ?? process.cwd()); - const asset = await resolveEitherBundleOrTab(fullyResolved); - if (asset === undefined) { - logCommandErrorAndExit(`No bundle or tab found at ${fullyResolved}`); + const resolveResult = await resolveEitherBundleOrTab(fullyResolved); + + if (resolveResult.severity === 'error') { + if (resolveResult.errors.length === 0 ) { + logCommandErrorAndExit(`No tab or bundle found at ${fullyResolved}`); + } + + logCommandErrorAndExit(formatResolveBundleErrors(resolveResult)); } - await runIndividualVitest(mode, asset, options); + await runIndividualVitest(mode, resolveResult.asset, options); }); diff --git a/lib/buildtools/src/prebuild/index.ts b/lib/buildtools/src/prebuild/index.ts index 564be53f7b..60e0436bcf 100644 --- a/lib/buildtools/src/prebuild/index.ts +++ b/lib/buildtools/src/prebuild/index.ts @@ -1,5 +1,5 @@ import fs from 'fs/promises'; -import type { ResolvedTab, ResolvedBundle, ResultEntry, ModuleResultEntry, FullResult } from '../types.js'; +import type { FullResult, ModuleResultEntry, ResolvedBundle, ResolvedTab, ResultEntry } from '../types.js'; import { compareSeverity } from '../utils.js'; import { runEslint, type LintResults } from './lint.js'; import { runTsc, type TscResult } from './tsc.js'; diff --git a/lib/buildtools/src/prebuild/lint.ts b/lib/buildtools/src/prebuild/lint.ts index afca23485a..e0db360244 100644 --- a/lib/buildtools/src/prebuild/lint.ts +++ b/lib/buildtools/src/prebuild/lint.ts @@ -1,10 +1,10 @@ -import { type Dirent, promises as fs } from 'fs'; -import path from 'path'; +import { promises as fs, type Dirent } from 'fs'; +import pathlib from 'path'; import chalk from 'chalk'; import { ESLint } from 'eslint'; import { getGitRoot } from '../getGitRoot.js'; import type { ResolvedBundle, ResolvedTab } from '../types.js'; -import { findSeverity, isNodeError, flatMapAsync, type Severity } from '../utils.js'; +import { findSeverity, flatMapAsync, isNodeError, type Severity } from '../utils.js'; import { createPrebuilder } from './prebuildUtils.js'; export interface LintResults { @@ -91,7 +91,7 @@ interface LintGlobalResults { /** * Function for linting the source directory excluding bundles and tabs. Since linting bundles and tabs - * altogether causes ESLint to run out of memory, we have this function that lints everything else + * all together causes ESLint to run out of memory, we have this function that lints everything else * so that the bundles and tabs can be linted separately. */ export async function lintGlobal(fix: boolean): Promise { @@ -120,7 +120,7 @@ export async function lintGlobal(fix: boolean): Promise { return flatMapAsync(files, async each => { if (filterSrc && each.name === 'src') return []; - const fullPath = path.join(dir, each.name); + const fullPath = pathlib.join(dir, each.name); if (each.isFile()) { const isIgnored = await linter.isPathIgnored(fullPath); return isIgnored ? [] : [fullPath]; diff --git a/lib/buildtools/src/templates/__tests__/names.test.ts b/lib/buildtools/src/templates/__tests__/names.test.ts index 38a3cb2718..dba5491890 100644 --- a/lib/buildtools/src/templates/__tests__/names.test.ts +++ b/lib/buildtools/src/templates/__tests__/names.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect } from 'vitest'; +import { describe, expect, test } from 'vitest'; import { isPascalCase, isSnakeCase } from '../utilities.js'; function testFunction( diff --git a/lib/buildtools/src/templates/bundle.ts b/lib/buildtools/src/templates/bundle.ts index c09fe4725c..1b29021502 100644 --- a/lib/buildtools/src/templates/bundle.ts +++ b/lib/buildtools/src/templates/bundle.ts @@ -3,7 +3,7 @@ import type { Interface } from 'readline/promises'; import _package from '../../../../package.json' with { type: 'json' }; import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; import { getBundleManifests } from '../build/manifest/index.js'; -import type { ModulesManifest, BundleManifest } from '../types.js'; +import type { BundleManifest, ModulesManifest } from '../types.js'; import sampleTsconfig from './bundle_tsconfig.json' with { type: 'json' }; import { askQuestion, error, success, warn } from './print.js'; import { check, isSnakeCase } from './utilities.js'; diff --git a/lib/buildtools/src/templates/print.ts b/lib/buildtools/src/templates/print.ts index ce807600fe..074b0bfba5 100644 --- a/lib/buildtools/src/templates/print.ts +++ b/lib/buildtools/src/templates/print.ts @@ -1,4 +1,4 @@ -import { type Interface, createInterface } from 'readline/promises'; +import { createInterface, type Interface } from 'readline/promises'; import chalk from 'chalk'; export const getRl = () => createInterface({ diff --git a/lib/buildtools/src/templates/tab.ts b/lib/buildtools/src/templates/tab.ts index 68e3cc5ee2..71b5b029b9 100644 --- a/lib/buildtools/src/templates/tab.ts +++ b/lib/buildtools/src/templates/tab.ts @@ -3,7 +3,7 @@ import type { Interface } from 'readline/promises'; import _package from '../../../../package.json' with { type: 'json' }; import { formatResolveBundleErrors } from '../build/manifest/formatters.js'; import { getBundleManifests } from '../build/manifest/index.js'; -import type { ModulesManifest, BundleManifest } from '../types.js'; +import type { BundleManifest, ModulesManifest } from '../types.js'; import { askQuestion, error, success, warn } from './print.js'; import { check, isPascalCase } from './utilities.js'; diff --git a/lib/buildtools/src/types.ts b/lib/buildtools/src/types.ts index f8828f4eeb..6c37868afc 100644 --- a/lib/buildtools/src/types.ts +++ b/lib/buildtools/src/types.ts @@ -11,22 +11,26 @@ export type ModulesManifest = { [name: string]: BundleManifest; }; +// #region ResolvedBundle export interface ResolvedBundle { type: 'bundle' name: string; manifest: BundleManifest; directory: string; } +// #endregion ResolvedBundle +// #region ResolvedTab export interface ResolvedTab { type: 'tab' directory: string; entryPoint: string; name: string; } +// #endregion ResolvedTab -interface BaseResult { - message: string, +interface BaseResult { + message: T, severity: Severity } @@ -38,7 +42,7 @@ export interface ManifestResult extends BaseResult { assetType: 'manifest' } -interface ModuleResult extends BaseResult { +interface ModuleResult extends BaseResult { inputName: string } @@ -54,7 +58,11 @@ export interface JsonResultEntry extends ModuleResult { assetType: 'json' } -export type ModuleResultEntry = BundleResultEntry | JsonResultEntry | TabResultEntry; +export interface ManifestResultEntry extends ModuleResult { + assetType: 'manifest' +} + +export type ModuleResultEntry = BundleResultEntry | JsonResultEntry | ManifestResultEntry | TabResultEntry; export type ResultEntry = HTMLResult | ManifestResult | ModuleResultEntry; diff --git a/lib/buildtools/src/utils.ts b/lib/buildtools/src/utils.ts index a5e540c1be..901fa214d3 100644 --- a/lib/buildtools/src/utils.ts +++ b/lib/buildtools/src/utils.ts @@ -34,6 +34,13 @@ export function isNodeError(error: unknown): error is NodeJS.ErrnoException { return error instanceof Error; } +/** + * `Array.map` but with a mapping function that returns a promise + */ +export function mapAsync(items: T[], mapper: (item: T, index: number) => Promise) { + return Promise.all(items.map(mapper)); +} + /** * `Array.flatMap` but with a mapping function that returns a promise */ diff --git a/lib/modules-lib/src/tabs/ButtonComponent.tsx b/lib/modules-lib/src/tabs/ButtonComponent.tsx index 3e21211fc5..e59c0bb206 100644 --- a/lib/modules-lib/src/tabs/ButtonComponent.tsx +++ b/lib/modules-lib/src/tabs/ButtonComponent.tsx @@ -1,4 +1,4 @@ -import { AnchorButton, type AnchorButtonProps, Button, type ButtonProps, Intent } from '@blueprintjs/core'; +import { AnchorButton, Button, Intent, type AnchorButtonProps, type ButtonProps } from '@blueprintjs/core'; const defaultOptions = { className: '', diff --git a/package.json b/package.json index 6032651c0f..cd9df53503 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "build:libs": "yarn workspaces foreach -ptW --from \"./lib/*\" run build", "build:modules": "buildtools build all", "devserver": "devserver", - "lint:all": "eslint lib devserver docs && yarn lint:modules", + "lint:all": "buildtools lintglobal && yarn lint:modules", "lint:inspect": "yarn dlx eslint --inspect-config", "lint:modules": "yarn workspaces foreach -j 5 -pW --from \"./src/{bundles,tabs}\" run lint", "preinstall": "yarn build:libs", diff --git a/src/bundles/ar/src/ObjectsHelper.ts b/src/bundles/ar/src/ObjectsHelper.ts index b600705dfa..afa2eee614 100644 --- a/src/bundles/ar/src/ObjectsHelper.ts +++ b/src/bundles/ar/src/ObjectsHelper.ts @@ -1,23 +1,23 @@ import type { Vector3 } from 'saar/libraries/misc'; import { - type ARObject, CubeObject, + GltfObject, LightObject, SphereObject, UIObject, - GltfObject, + type ARObject, } from 'saar/libraries/object_state_library/ARObject'; import { AlwaysRender, FixRotation, MovementStyle, OrbitMovement, - type PathItem, PathMovement, RenderWithinDistance, RotateAroundY, RotateToUser, SpringMovement, + type PathItem, } from 'saar/libraries/object_state_library/Behaviour'; import UIBase64ImageComponent from 'saar/libraries/object_state_library/ui_component/UIBase64ImageItem'; import UIColumnItem, { diff --git a/src/bundles/arcade_2d/src/functions.ts b/src/bundles/arcade_2d/src/functions.ts index a02d8d590a..e283405124 100644 --- a/src/bundles/arcade_2d/src/functions.ts +++ b/src/bundles/arcade_2d/src/functions.ts @@ -11,52 +11,52 @@ import { AudioClip } from './audio'; import { - DEFAULT_WIDTH, + DEFAULT_DEBUG_STATE, + DEFAULT_FPS, DEFAULT_HEIGHT, + DEFAULT_INTERACTABLE_PROPS, + DEFAULT_RENDER_PROPS, DEFAULT_SCALE, - DEFAULT_FPS, + DEFAULT_TRANSFORM_PROPS, + DEFAULT_WIDTH, + MAX_FPS, MAX_HEIGHT, - MIN_HEIGHT, - MAX_WIDTH, - MIN_WIDTH, MAX_SCALE, - MIN_SCALE, - MAX_FPS, - MIN_FPS, MAX_VOLUME, + MAX_WIDTH, + MIN_FPS, + MIN_HEIGHT, + MIN_SCALE, MIN_VOLUME, - DEFAULT_DEBUG_STATE, - DEFAULT_TRANSFORM_PROPS, - DEFAULT_RENDER_PROPS, - DEFAULT_INTERACTABLE_PROPS + MIN_WIDTH } from './constants'; import { - GameObject, + CircleGameObject, + GameObject, InteractableGameObject, + RectangleGameObject, RenderableGameObject, - type ShapeGameObject, SpriteGameObject, TextGameObject, - RectangleGameObject, - CircleGameObject, - TriangleGameObject, InteractableGameObject + TriangleGameObject, + type ShapeGameObject } from './gameobject'; import { PhaserScene, gameState } from './phaserScene'; import type { - DisplayText, BuildGame, - Sprite, - UpdateFunction, - RectangleProps, CircleProps, - TriangleProps, + ColorRGBA, + DimensionsXY, + DisplayText, FlipXY, - ScaleXY, PositionXY, - DimensionsXY, - ColorRGBA + RectangleProps, + ScaleXY, + Sprite, + TriangleProps, + UpdateFunction } from './types'; // ============================================================================= diff --git a/src/bundles/arcade_2d/src/phaserScene.ts b/src/bundles/arcade_2d/src/phaserScene.ts index 24dd46971d..85d6e6ef91 100644 --- a/src/bundles/arcade_2d/src/phaserScene.ts +++ b/src/bundles/arcade_2d/src/phaserScene.ts @@ -7,14 +7,14 @@ import { import { CircleGameObject, GameObject, - type InteractableGameObject, RectangleGameObject, ShapeGameObject, SpriteGameObject, TextGameObject, - TriangleGameObject + TriangleGameObject, + type InteractableGameObject } from './gameobject'; -import type { TransformProps, PositionXY, ExceptionError, PhaserGameObject } from './types'; +import type { ExceptionError, PhaserGameObject, PositionXY, TransformProps } from './types'; // Game state information, that changes every frame. export const gameState = { diff --git a/src/bundles/communication/src/MqttController.ts b/src/bundles/communication/src/MqttController.ts index dfe6ba00ff..1710183f9a 100644 --- a/src/bundles/communication/src/MqttController.ts +++ b/src/bundles/communication/src/MqttController.ts @@ -1,4 +1,4 @@ -import { connect, type QoS, type MqttClient } from 'mqtt/dist/mqtt'; +import { connect, type MqttClient, type QoS } from 'mqtt/dist/mqtt'; // Need to use "mqtt/dist/mqtt" as "mqtt" requires global, which SA's compiler does not define. export const STATE_CONNECTED = 'Connected'; diff --git a/src/bundles/csg/src/functions.ts b/src/bundles/csg/src/functions.ts index cdfe03032d..9389b987c9 100644 --- a/src/bundles/csg/src/functions.ts +++ b/src/bundles/csg/src/functions.ts @@ -15,10 +15,10 @@ import { serialize } from '@jscad/stl-serializer'; import { degreesToRadians, hexToColor } from '@sourceacademy/modules-lib/utilities'; import { head, + is_list, list, tail, - type List, - is_list + type List } from 'js-slang/dist/stdlib/list'; import save from 'save-file'; import { Core } from './core'; @@ -26,9 +26,9 @@ import type { Solid } from './jscad/types'; import { Group, Shape, + centerPrimitive, type Operable, - type RenderGroup, - centerPrimitive + type RenderGroup } from './utilities'; /* [Main] */ diff --git a/src/bundles/csg/src/jscad/types.ts b/src/bundles/csg/src/jscad/types.ts index 58dac8b8c7..79e4b6d351 100644 --- a/src/bundles/csg/src/jscad/types.ts +++ b/src/bundles/csg/src/jscad/types.ts @@ -1,7 +1,7 @@ /* [Import] */ import type { RGB, RGBA } from '@jscad/modeling/src/colors/types'; import type { Geom3 } from '@jscad/modeling/src/geometries/types'; -import type { cameras, drawCommands, controls } from '@jscad/regl-renderer'; +import type { cameras, controls, drawCommands } from '@jscad/regl-renderer'; import type makeDrawMultiGrid from '@jscad/regl-renderer/types/rendering/commands/drawGrid/multi'; /* [Exports] */ diff --git a/src/bundles/curve/manifest.json b/src/bundles/curve/manifest.json index c5af0880e5..bafa9468c8 100644 --- a/src/bundles/curve/manifest.json +++ b/src/bundles/curve/manifest.json @@ -1,5 +1,6 @@ { "tabs": [ "Curve" - ] + ], + "bad": "manifest" } diff --git a/src/bundles/game/src/functions.ts b/src/bundles/game/src/functions.ts index 6729768e85..908159a066 100644 --- a/src/bundles/game/src/functions.ts +++ b/src/bundles/game/src/functions.ts @@ -15,16 +15,16 @@ */ import context from 'js-slang/context'; -import { type List, head, tail, is_pair, accumulate } from 'js-slang/dist/stdlib/list'; +import { accumulate, head, is_pair, tail, type List } from 'js-slang/dist/stdlib/list'; import Phaser from 'phaser'; import { + defaultGameParams, type GameObject, type ObjectConfig, type RawContainer, type RawGameElement, type RawGameObject, - type RawInputObject, - defaultGameParams + type RawInputObject } from './types'; if (!context.moduleContexts.game.state) { diff --git a/src/bundles/mark_sweep/src/index.ts b/src/bundles/mark_sweep/src/index.ts index 2dcde4ca01..53884b281c 100644 --- a/src/bundles/mark_sweep/src/index.ts +++ b/src/bundles/mark_sweep/src/index.ts @@ -2,7 +2,7 @@ * @module mark_sweep */ -import { type MemoryHeaps, type Memory, type Tag, COMMAND, type CommandHeapObject } from './types'; +import { COMMAND, type CommandHeapObject, type Memory, type MemoryHeaps, type Tag } from './types'; // Global Variables let ROW: number = 10; diff --git a/src/bundles/nbody/src/Force.ts b/src/bundles/nbody/src/Force.ts index ab7ffa3b46..4cd5227b38 100644 --- a/src/bundles/nbody/src/Force.ts +++ b/src/bundles/nbody/src/Force.ts @@ -1,4 +1,4 @@ -import { CentripetalForce, CombinedForce, Gravity, type CelestialBody, type Force, type Vector3, LambdaForce } from 'nbody'; +import { CentripetalForce, CombinedForce, Gravity, LambdaForce, type CelestialBody, type Force, type Vector3 } from 'nbody'; /** * Create a force that applies to all bodies using the provided higher order/lambda/arrow/anonymous function. diff --git a/src/bundles/nbody/src/Simulation.ts b/src/bundles/nbody/src/Simulation.ts index ad40b2b1b1..14e959dbe4 100644 --- a/src/bundles/nbody/src/Simulation.ts +++ b/src/bundles/nbody/src/Simulation.ts @@ -1,5 +1,5 @@ import context from 'js-slang/context'; -import { RecordingVisualizer, RecordingVisualizer3D, Simulation, type Universe, type VisType } from 'nbody'; +import { RecordingVisualizer3D, RecordingVisualizer, Simulation, type Universe, type VisType } from 'nbody'; /** * Create a new simulation. diff --git a/src/bundles/nbody/src/State.ts b/src/bundles/nbody/src/State.ts index 66ac9f461d..beb7c286e2 100644 --- a/src/bundles/nbody/src/State.ts +++ b/src/bundles/nbody/src/State.ts @@ -1,4 +1,4 @@ -import { type CelestialBody, State } from 'nbody'; +import { State, type CelestialBody } from 'nbody'; /** * Create a new state snapshot of the universe. diff --git a/src/bundles/nbody/src/Transformation.ts b/src/bundles/nbody/src/Transformation.ts index f54fe438c7..20d0e7cf49 100644 --- a/src/bundles/nbody/src/Transformation.ts +++ b/src/bundles/nbody/src/Transformation.ts @@ -1,4 +1,4 @@ -import { BodyCenterTransformation, CoMTransformation, RotateTransformation, LambdaTransformation, type Vector3, type State, type Transformation, PinTransformation, TimedRotateTransformation } from 'nbody'; +import { BodyCenterTransformation, CoMTransformation, LambdaTransformation, PinTransformation, RotateTransformation, TimedRotateTransformation, type State, type Transformation, type Vector3 } from 'nbody'; /** * Create a frame of reference transformation that moves the origin to the center of ith both. diff --git a/src/bundles/nbody/src/Universe.ts b/src/bundles/nbody/src/Universe.ts index 1f228a73aa..caa35898a5 100644 --- a/src/bundles/nbody/src/Universe.ts +++ b/src/bundles/nbody/src/Universe.ts @@ -1,4 +1,4 @@ -import { type State, Universe, type SimulateFunction, type Transformation } from 'nbody'; +import { Universe, type SimulateFunction, type State, type Transformation } from 'nbody'; /** * Create a new universe. diff --git a/src/bundles/painter/src/functions.ts b/src/bundles/painter/src/functions.ts index e045328cbb..9605562487 100644 --- a/src/bundles/painter/src/functions.ts +++ b/src/bundles/painter/src/functions.ts @@ -1,6 +1,6 @@ import context from 'js-slang/context'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; -import { type Frame, LinePlot } from './painter'; +import { LinePlot, type Frame } from './painter'; const drawnPainters: LinePlot[] = []; context.moduleContexts.painter.state = { diff --git a/src/bundles/physics_2d/src/PhysicsObject.ts b/src/bundles/physics_2d/src/PhysicsObject.ts index e7ddfd888c..f3ad2e38da 100644 --- a/src/bundles/physics_2d/src/PhysicsObject.ts +++ b/src/bundles/physics_2d/src/PhysicsObject.ts @@ -1,11 +1,11 @@ import { - type b2Body, - type b2Shape, - type b2Fixture, b2BodyType, b2CircleShape, b2PolygonShape, - b2Vec2 + b2Vec2, + type b2Body, + type b2Fixture, + type b2Shape } from '@box2d/core'; import type { ReplResult } from '@sourceacademy/modules-lib/types'; diff --git a/src/bundles/physics_2d/src/PhysicsWorld.ts b/src/bundles/physics_2d/src/PhysicsWorld.ts index 235bd00422..6897125452 100644 --- a/src/bundles/physics_2d/src/PhysicsWorld.ts +++ b/src/bundles/physics_2d/src/PhysicsWorld.ts @@ -1,14 +1,14 @@ import { - type b2Body, - type b2Fixture, - type b2BodyDef, b2BodyType, + b2ContactListener, b2PolygonShape, - type b2StepConfig, b2Vec2, b2World, - b2ContactListener, - type b2Contact + type b2Body, + type b2BodyDef, + type b2Contact, + type b2Fixture, + type b2StepConfig } from '@box2d/core'; import type { PhysicsObject } from './PhysicsObject'; import { Timer } from './types'; diff --git a/src/bundles/physics_2d/src/functions.ts b/src/bundles/physics_2d/src/functions.ts index 25adf39247..9978d119c0 100644 --- a/src/bundles/physics_2d/src/functions.ts +++ b/src/bundles/physics_2d/src/functions.ts @@ -9,7 +9,7 @@ import context from 'js-slang/context'; import { PhysicsObject } from './PhysicsObject'; import { PhysicsWorld } from './PhysicsWorld'; -import { type Force, Vector2 } from './types'; +import { Vector2, type Force } from './types'; // Global Variables diff --git a/src/bundles/pix_n_flix/src/functions.ts b/src/bundles/pix_n_flix/src/functions.ts index fc16cbd1c4..51935735ec 100644 --- a/src/bundles/pix_n_flix/src/functions.ts +++ b/src/bundles/pix_n_flix/src/functions.ts @@ -1,29 +1,29 @@ import { - DEFAULT_WIDTH, - DEFAULT_HEIGHT, DEFAULT_FPS, + DEFAULT_HEIGHT, + DEFAULT_LOOP, DEFAULT_VOLUME, + DEFAULT_WIDTH, + MAX_FPS, MAX_HEIGHT, - MIN_HEIGHT, MAX_WIDTH, - MIN_WIDTH, - MAX_FPS, MIN_FPS, - DEFAULT_LOOP + MIN_HEIGHT, + MIN_WIDTH } from './constants'; import { + InputFeed, + type BundlePacket, type CanvasElement, - type VideoElement, type ErrorLogger, - type StartPacket, + type Filter, + type ImageElement, type Pixel, type Pixels, - type Filter, type Queue, + type StartPacket, type TabsPacket, - type BundlePacket, - InputFeed, - type ImageElement + type VideoElement } from './types'; // Global Variables diff --git a/src/bundles/plotly/src/curve_functions.ts b/src/bundles/plotly/src/curve_functions.ts index 18c6a80b7c..4772440c8e 100644 --- a/src/bundles/plotly/src/curve_functions.ts +++ b/src/bundles/plotly/src/curve_functions.ts @@ -1,4 +1,4 @@ -import { x_of, y_of, z_of, r_of, g_of, b_of } from '@sourceacademy/bundle-curve'; +import { b_of, g_of, r_of, x_of, y_of, z_of } from '@sourceacademy/bundle-curve'; import type { Curve } from '@sourceacademy/bundle-curve/curves_webgl'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; import { CurvePlot } from './plotly'; diff --git a/src/bundles/plotly/src/functions.ts b/src/bundles/plotly/src/functions.ts index 03cb768ea1..5f43723d47 100644 --- a/src/bundles/plotly/src/functions.ts +++ b/src/bundles/plotly/src/functions.ts @@ -11,8 +11,8 @@ import Plotly, { type Data, type Layout } from 'plotly.js-dist'; import { generatePlot } from './curve_functions'; import { CurvePlot, - type CurvePlotFunction, DrawnPlot, + type CurvePlotFunction, type ListOfPairs } from './plotly'; diff --git a/src/bundles/repeat/src/__tests__/index.ts b/src/bundles/repeat/src/__tests__/index.ts index e3cb82a214..3a3cff9e81 100644 --- a/src/bundles/repeat/src/__tests__/index.ts +++ b/src/bundles/repeat/src/__tests__/index.ts @@ -1,6 +1,6 @@ -import { test, expect } from 'vitest'; +import { expect, test } from 'vitest'; -import { repeat, twice, thrice } from '../functions'; +import { repeat, thrice, twice } from '../functions'; // Test functions test('repeat works correctly and repeats function n times', () => { diff --git a/src/bundles/repl/src/programmable_repl.ts b/src/bundles/repl/src/programmable_repl.ts index 3463651fc8..4f1cc21c69 100644 --- a/src/bundles/repl/src/programmable_repl.ts +++ b/src/bundles/repl/src/programmable_repl.ts @@ -6,7 +6,7 @@ import { runFilesInContext, type IOptions } from 'js-slang'; import context from 'js-slang/context'; -import { COLOR_RUN_CODE_RESULT, COLOR_ERROR_MESSAGE, DEFAULT_EDITOR_HEIGHT } from './config'; +import { COLOR_ERROR_MESSAGE, COLOR_RUN_CODE_RESULT, DEFAULT_EDITOR_HEIGHT } from './config'; import { default_js_slang } from './functions'; export class ProgrammableRepl { diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts index 32c85ace09..f38320ceca 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Chassis.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { Physics, Renderer, EntityFactory , MeshFactory } from '../../../../engine'; +import { EntityFactory, MeshFactory, Physics, Renderer } from '../../../../engine'; import { ChassisWrapper } from '../../../ev3/components/Chassis'; diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts index 99fba28ff8..29bd7165a7 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/components/Motor.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { Renderer, Physics } from '../../../../engine'; +import { Physics, Renderer } from '../../../../engine'; import { loadGLTF } from '../../../../engine/Render/helpers/GLTF'; import { ChassisWrapper } from '../../../ev3/components/Chassis'; import { Motor } from '../../../ev3/components/Motor'; diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts index ecc6348459..431ffc6432 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/ev3/default/ev3.ts @@ -1,6 +1,6 @@ -import { vi, describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; -import { Physics, Renderer, ControllerMap } from '../../../../../engine'; +import { ControllerMap, Physics, Renderer } from '../../../../../engine'; import { ChassisWrapper } from '../../../../ev3/components/Chassis'; import { Mesh } from '../../../../ev3/components/Mesh'; import { Motor } from '../../../../ev3/components/Motor'; diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts index 2b4de1695b..5abf9cb4e9 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/ev3/feedback_control/PidController.ts @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { beforeEach, describe, it, expect } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import { NumberPidController, VectorPidController } from '../../../ev3/feedback_control/PidController'; const resetPid = (pidController:NumberPidController) => { diff --git a/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts b/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts index 398f30f399..9f2b3e4c1f 100644 --- a/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts +++ b/src/bundles/robot_simulation/src/controllers/__tests__/program/Program.ts @@ -1,4 +1,4 @@ -import { vi, beforeEach, describe, it, expect } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; import { Program, program_controller_identifier } from '../../program/Program'; import { runECEvaluator } from '../../program/evaluate'; diff --git a/src/bundles/robot_simulation/src/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/src/controllers/ev3/components/Chassis.ts index e1b8df21ad..bec7e4339a 100644 --- a/src/bundles/robot_simulation/src/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/src/controllers/ev3/components/Chassis.ts @@ -1,11 +1,11 @@ import * as THREE from 'three'; import { - type Physics, - type Controller, EntityFactory, - type Entity, MeshFactory, + type Controller, + type Entity, + type Physics, type Renderer, } from '../../../engine'; import type { EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; diff --git a/src/bundles/robot_simulation/src/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/src/controllers/ev3/components/Wheel.ts index 7574db5ff2..b4e23e183a 100644 --- a/src/bundles/robot_simulation/src/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/src/controllers/ev3/components/Wheel.ts @@ -1,5 +1,5 @@ import type * as THREE from 'three'; -import type { Renderer, Controller, Physics } from '../../../engine'; +import type { Controller, Physics, Renderer } from '../../../engine'; import { vec3 } from '../../../engine/Math/Convert'; import type { SimpleVector } from '../../../engine/Math/Vector'; import type { PhysicsTimingInfo } from '../../../engine/Physics'; diff --git a/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/ev3.ts index 4b792c2cbf..805809f758 100644 --- a/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/ev3.ts +++ b/src/bundles/robot_simulation/src/controllers/ev3/ev3/default/ev3.ts @@ -1,4 +1,4 @@ -import { type Physics, type Renderer, ControllerMap } from '../../../../engine'; +import { ControllerMap, type Physics, type Renderer } from '../../../../engine'; import { ChassisWrapper } from '../../components/Chassis'; import { Mesh } from '../../components/Mesh'; @@ -8,12 +8,12 @@ import { ColorSensor } from '../../sensor/ColorSensor'; import { UltrasonicSensor } from '../../sensor/UltrasonicSensor'; import { - wheelNames, motorNames, + wheelNames, type DefaultEv3Controller, type Ev3Config, - type WheelControllers, type MotorControllers, + type WheelControllers, } from './types'; export type DefaultEv3 = ControllerMap; diff --git a/src/bundles/robot_simulation/src/engine/Render/Renderer.ts b/src/bundles/robot_simulation/src/engine/Render/Renderer.ts index e983a72a53..a1d5efea08 100644 --- a/src/bundles/robot_simulation/src/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/src/engine/Render/Renderer.ts @@ -1,8 +1,8 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; import { - type GLTF, GLTFLoader, + type GLTF, } from 'three/examples/jsm/loaders/GLTFLoader.js'; import type { FrameTimingInfo } from '../Core/Timer'; diff --git a/src/bundles/robot_simulation/src/engine/Render/helpers/GLTF.ts b/src/bundles/robot_simulation/src/engine/Render/helpers/GLTF.ts index 773068a3d2..4c11c11937 100644 --- a/src/bundles/robot_simulation/src/engine/Render/helpers/GLTF.ts +++ b/src/bundles/robot_simulation/src/engine/Render/helpers/GLTF.ts @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { GLTFLoader, type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import type { Dimension } from '../../Math/Vector'; type GLTFLoaderOptions = Dimension; diff --git a/src/bundles/robot_simulation/src/engine/World.ts b/src/bundles/robot_simulation/src/engine/World.ts index 321b876f02..b5dd35a2e9 100644 --- a/src/bundles/robot_simulation/src/engine/World.ts +++ b/src/bundles/robot_simulation/src/engine/World.ts @@ -1,5 +1,5 @@ import { ProgramError } from '../controllers/program/error'; -import { type Controller, ControllerGroup } from './Core/Controller'; +import { ControllerGroup, type Controller } from './Core/Controller'; import { TypedEventTarget } from './Core/Events'; import type { RobotConsole } from './Core/RobotConsole'; import type { Timer } from './Core/Timer'; diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts index 8dccaaeb09..c73bebd31f 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/Controller.ts @@ -1,8 +1,8 @@ import { describe, expect, test, vi, type Mock } from 'vitest'; import { - type Controller, ControllerGroup, ControllerMap, + type Controller, } from '../../Core/Controller'; import type { PhysicsTimingInfo } from '../../Physics'; diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts index 2818665c51..78eac82d65 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/Events.ts @@ -1,4 +1,4 @@ -import { beforeEach, expect, describe, test, vi } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { TypedEventTarget } from '../../Core/Events'; class StringEvent extends Event { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts b/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts index 9b92f5af41..9165f25ebd 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Core/RobotConsole.ts @@ -1,4 +1,4 @@ -import { describe, expect, test, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, test } from 'vitest'; import { RobotConsole } from '../../Core/RobotConsole'; // Adjust the import path as necessary describe('RobotConsole', () => { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts b/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts index 350842d571..291b9c7f38 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Math/Convert.ts @@ -1,6 +1,6 @@ -import { Quaternion, Vector3, Euler } from 'three'; -import { describe, it, expect } from 'vitest'; -import { quat, vec3, euler } from '../../Math/Convert'; // Adjust the import path as necessary +import { Euler, Quaternion, Vector3 } from 'three'; +import { describe, expect, it } from 'vitest'; +import { euler, quat, vec3 } from '../../Math/Convert'; // Adjust the import path as necessary describe('Three.js utility functions', () => { describe('quat function', () => { diff --git a/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts b/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts index 4239a4b798..368410d2a9 100644 --- a/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts +++ b/src/bundles/robot_simulation/src/engine/__tests__/Render/helpers/Camera.ts @@ -1,7 +1,7 @@ import * as THREE from 'three'; import { describe, expect, test } from 'vitest'; -import { type CameraOptions, getCamera } from '../../../Render/helpers/Camera'; +import { getCamera, type CameraOptions } from '../../../Render/helpers/Camera'; describe('getCamera', () => { test('returns a PerspectiveCamera when type is "perspective"', () => { diff --git a/src/bundles/robot_simulation/src/helper_functions.ts b/src/bundles/robot_simulation/src/helper_functions.ts index f5b6193176..13ad2d96de 100644 --- a/src/bundles/robot_simulation/src/helper_functions.ts +++ b/src/bundles/robot_simulation/src/helper_functions.ts @@ -9,7 +9,7 @@ import { type DefaultEv3, } from './controllers/ev3/ev3/default/ev3'; import { Program } from './controllers/program/Program'; -import { type Controller, Physics, Renderer, Timer, World } from './engine'; +import { Physics, Renderer, Timer, World, type Controller } from './engine'; import { RobotConsole } from './engine/Core/RobotConsole'; import { diff --git a/src/bundles/rune/src/functions.ts b/src/bundles/rune/src/functions.ts index c6f23bcb59..e161f8612d 100644 --- a/src/bundles/rune/src/functions.ts +++ b/src/bundles/rune/src/functions.ts @@ -1,32 +1,32 @@ import { mat4, vec3 } from 'gl-matrix'; import { - Rune, DrawnRune, + Rune, drawRunesToFrameBuffer, type AnimatedRune } from './rune'; import { - getSquare, + addColorFromHex, + colorPalette, getBlank, - getRcross, - getSail, - getTriangle, - getCorner, - getNova, getCircle, + getCorner, getHeart, + getNova, getPentagram, + getRcross, getRibbon, - throwIfNotRune, - addColorFromHex, - colorPalette, - hexToColor + getSail, + getSquare, + getTriangle, + hexToColor, + throwIfNotRune } from './runes_ops'; import { - type FrameBufferWithTexture, getWebGlFromCanvas, initFramebufferObject, - initShaderProgram + initShaderProgram, + type FrameBufferWithTexture } from './runes_webgl'; import { functionDeclaration, variableDeclaration } from './type_map'; diff --git a/src/bundles/rune/src/rune.ts b/src/bundles/rune/src/rune.ts index a67d47a63b..5a0726f3a8 100644 --- a/src/bundles/rune/src/rune.ts +++ b/src/bundles/rune/src/rune.ts @@ -1,4 +1,4 @@ -import { type AnimFrame, type ReplResult, glAnimation } from '@sourceacademy/modules-lib/types'; +import { glAnimation, type AnimFrame, type ReplResult } from '@sourceacademy/modules-lib/types'; import { mat4 } from 'gl-matrix'; import { getWebGlFromCanvas, initShaderProgram } from './runes_webgl'; import { classDeclaration } from './type_map'; diff --git a/src/bundles/rune_in_words/src/functions.ts b/src/bundles/rune_in_words/src/functions.ts index d40a90e840..323d19d363 100644 --- a/src/bundles/rune_in_words/src/functions.ts +++ b/src/bundles/rune_in_words/src/functions.ts @@ -1,16 +1,16 @@ import type { Rune } from './rune'; import { - getSquare, getBlank, - getRcross, - getSail, - getTriangle, - getCorner, - getNova, getCircle, + getCorner, getHeart, + getNova, getPentagram, + getRcross, getRibbon, + getSail, + getSquare, + getTriangle, throwIfNotRune } from './runes_ops'; diff --git a/src/bundles/scrabble/src/__tests__/index.ts b/src/bundles/scrabble/src/__tests__/index.ts index e70a15bfbb..6d14ba0ed7 100644 --- a/src/bundles/scrabble/src/__tests__/index.ts +++ b/src/bundles/scrabble/src/__tests__/index.ts @@ -1,4 +1,4 @@ -import { test, expect } from 'vitest'; +import { expect, test } from 'vitest'; import { scrabble_letters, scrabble_letters_tiny, diff --git a/src/bundles/sound/src/functions.ts b/src/bundles/sound/src/functions.ts index 614843790b..a017df8143 100644 --- a/src/bundles/sound/src/functions.ts +++ b/src/bundles/sound/src/functions.ts @@ -1,22 +1,22 @@ import context from 'js-slang/context'; import { - pair, + accumulate, head, - tail, - list, - length, is_null, is_pair, - accumulate, + length, + list, + pair, + tail, type List } from 'js-slang/dist/stdlib/list'; import { RIFFWAVE } from './riffwave'; import type { - Wave, + AudioPlayed, Sound, SoundProducer, SoundTransformer, - AudioPlayed + Wave } from './types'; // Global Constants and Variables diff --git a/src/bundles/unittest/src/__tests__/index.ts b/src/bundles/unittest/src/__tests__/index.ts index f344ac04ed..e723f0576a 100644 --- a/src/bundles/unittest/src/__tests__/index.ts +++ b/src/bundles/unittest/src/__tests__/index.ts @@ -1,5 +1,5 @@ import { list } from 'js-slang/dist/stdlib/list'; -import { test, expect, vi, beforeEach } from 'vitest'; +import { beforeEach, expect, test, vi } from 'vitest'; import * as asserts from '../asserts'; import * as testing from '../functions'; diff --git a/src/bundles/unittest/src/asserts.ts b/src/bundles/unittest/src/asserts.ts index 154c794195..f0855c1171 100644 --- a/src/bundles/unittest/src/asserts.ts +++ b/src/bundles/unittest/src/asserts.ts @@ -1,4 +1,4 @@ -import { is_pair, head, tail, is_list, is_null, length, type List, type Pair } from 'js-slang/dist/stdlib/list'; +import { head, is_list, is_null, is_pair, length, tail, type List, type Pair } from 'js-slang/dist/stdlib/list'; /** * Asserts that a predicate returns true. diff --git a/src/bundles/unittest/src/functions.ts b/src/bundles/unittest/src/functions.ts index c068ef2f24..bc67032147 100644 --- a/src/bundles/unittest/src/functions.ts +++ b/src/bundles/unittest/src/functions.ts @@ -1,6 +1,6 @@ import context from 'js-slang/context'; -import type { TestContext, TestSuite, Test } from './types'; +import type { Test, TestContext, TestSuite } from './types'; const handleErr = (err: any) => { if (err.error && err.error.message) { diff --git a/src/bundles/unittest/src/mocks.ts b/src/bundles/unittest/src/mocks.ts index 8df5dccbb1..ee5a65942c 100644 --- a/src/bundles/unittest/src/mocks.ts +++ b/src/bundles/unittest/src/mocks.ts @@ -1,4 +1,4 @@ -import { pair, list, vector_to_list } from 'js-slang/dist/stdlib/list'; +import { list, pair, vector_to_list } from 'js-slang/dist/stdlib/list'; /** * Mocks a function `fun`. diff --git a/src/bundles/unity_academy/src/UnityAcademy.tsx b/src/bundles/unity_academy/src/UnityAcademy.tsx index 5b865858af..7223f8ab80 100644 --- a/src/bundles/unity_academy/src/UnityAcademy.tsx +++ b/src/bundles/unity_academy/src/UnityAcademy.tsx @@ -9,8 +9,8 @@ import { Button } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Vector3, normalizeVector, zeroVector, pointDistance } from './UnityAcademyMaths'; -import { UNITY_ACADEMY_BACKEND_URL, BUILD_NAME } from './config'; +import { Vector3, normalizeVector, pointDistance, zeroVector } from './UnityAcademyMaths'; +import { BUILD_NAME, UNITY_ACADEMY_BACKEND_URL } from './config'; type Transform = { position : Vector3; diff --git a/src/bundles/unity_academy/src/functions.ts b/src/bundles/unity_academy/src/functions.ts index 89250927e0..8f008f70fd 100644 --- a/src/bundles/unity_academy/src/functions.ts +++ b/src/bundles/unity_academy/src/functions.ts @@ -4,10 +4,10 @@ * @author Wang Zihan */ -import { initializeModule, getInstance, type GameObjectIdentifier, type AudioClipIdentifier } from './UnityAcademy'; -import { - type Vector3, checkVector3Parameter, makeVector3D, scaleVector, addVectors, vectorDifference, dotProduct, - crossProduct, normalizeVector, vectorMagnitude, zeroVector, pointDistance +import { getInstance, initializeModule, type AudioClipIdentifier, type GameObjectIdentifier } from './UnityAcademy'; +import { addVectors, checkVector3Parameter, + crossProduct, dotProduct, makeVector3D, normalizeVector, pointDistance, scaleVector, vectorDifference, vectorMagnitude, zeroVector, + type Vector3 } from './UnityAcademyMaths'; /** diff --git a/src/tabs/AugmentedReality/src/AugmentedContent.tsx b/src/tabs/AugmentedReality/src/AugmentedContent.tsx index 7fb22a6635..aff3ce6503 100644 --- a/src/tabs/AugmentedReality/src/AugmentedContent.tsx +++ b/src/tabs/AugmentedReality/src/AugmentedContent.tsx @@ -1,10 +1,10 @@ import { getModuleState, - type ARState, setFrontObject, + type ARState, } from '@sourceacademy/bundle-ar/AR'; import type { OverlayHelper } from '@sourceacademy/bundle-ar/OverlayHelper'; -import { useState, type RefObject, useRef, useEffect } from 'react'; +import { useEffect, useRef, useState, type RefObject } from 'react'; import { usePlayArea } from 'saar/libraries/calibration_library/PlayAreaContext'; import { useControls } from 'saar/libraries/controls_library/ControlsContext'; import { ARObject } from 'saar/libraries/object_state_library/ARObject'; diff --git a/src/tabs/CopyGc/src/index.tsx b/src/tabs/CopyGc/src/index.tsx index 124d19e6bf..6602099d55 100644 --- a/src/tabs/CopyGc/src/index.tsx +++ b/src/tabs/CopyGc/src/index.tsx @@ -1,4 +1,4 @@ -import { Slider, Icon } from '@blueprintjs/core'; +import { Icon, Slider } from '@blueprintjs/core'; import { COMMAND } from '@sourceacademy/bundle-copy_gc/types'; import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; diff --git a/src/tabs/Curve/src/animation_canvas_3d_curve.tsx b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx index fcaadf3b5c..46eae0e1f4 100644 --- a/src/tabs/Curve/src/animation_canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/animation_canvas_3d_curve.tsx @@ -2,11 +2,11 @@ import { Icon, Slider, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import type { AnimatedCurve } from '@sourceacademy/bundle-curve/types'; import { + AutoLoopSwitch, BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, - CANVAS_MAX_WIDTH, - AutoLoopSwitch, ButtonComponent, + CANVAS_MAX_WIDTH, PlayButton, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; diff --git a/src/tabs/Curve/src/canvas_3d_curve.tsx b/src/tabs/Curve/src/canvas_3d_curve.tsx index 4132dc19dc..2a26ac9665 100644 --- a/src/tabs/Curve/src/canvas_3d_curve.tsx +++ b/src/tabs/Curve/src/canvas_3d_curve.tsx @@ -1,6 +1,6 @@ import { Slider } from '@blueprintjs/core'; import type { CurveDrawn } from '@sourceacademy/bundle-curve/curves_webgl'; -import { PlayButton, WebGLCanvas, BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '@sourceacademy/modules-lib/tabs'; +import { BP_TAB_BUTTON_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH, PlayButton, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; import { degreesToRadians } from '@sourceacademy/modules-lib/utilities'; import React from 'react'; diff --git a/src/tabs/Curve/src/index.tsx b/src/tabs/Curve/src/index.tsx index b13421d0e8..d0327a4ba3 100644 --- a/src/tabs/Curve/src/index.tsx +++ b/src/tabs/Curve/src/index.tsx @@ -1,6 +1,6 @@ import { IconNames } from '@blueprintjs/icons'; import type { CurveModuleState } from '@sourceacademy/bundle-curve/types'; -import { defineTab, getModuleState, AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; +import { AnimationCanvas, MultiItemDisplay, WebGLCanvas, defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs'; import { glAnimation, type DebuggerContext, type ModuleTab } from '@sourceacademy/modules-lib/types'; import Curve3DAnimationCanvas from './animation_canvas_3d_curve'; import CurveCanvas3D from './canvas_3d_curve'; diff --git a/src/tabs/MarkSweep/src/index.tsx b/src/tabs/MarkSweep/src/index.tsx index 80ccb356e8..3602fa3d18 100644 --- a/src/tabs/MarkSweep/src/index.tsx +++ b/src/tabs/MarkSweep/src/index.tsx @@ -1,4 +1,4 @@ -import { Slider, Icon } from '@blueprintjs/core'; +import { Icon, Slider } from '@blueprintjs/core'; import { defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; import { ThemeColor } from './style'; diff --git a/src/tabs/Painter/index.tsx b/src/tabs/Painter/index.tsx index e166d11ba1..e6a173a8f0 100644 --- a/src/tabs/Painter/index.tsx +++ b/src/tabs/Painter/index.tsx @@ -1,5 +1,5 @@ import type { LinePlot } from '@sourceacademy/bundle-painter/painter'; -import { defineTab, ModalDiv as Modal } from '@sourceacademy/modules-lib/tabs'; +import { ModalDiv as Modal, defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = { diff --git a/src/tabs/Pixnflix/index.tsx b/src/tabs/Pixnflix/index.tsx index 493332204c..b145cdc416 100644 --- a/src/tabs/Pixnflix/index.tsx +++ b/src/tabs/Pixnflix/index.tsx @@ -13,9 +13,9 @@ import { MIN_WIDTH } from '@sourceacademy/bundle-pix_n_flix/constants'; import { + InputFeed, type BundlePacket, type ErrorLogger, - InputFeed, type TabsPacket } from '@sourceacademy/bundle-pix_n_flix/types'; import { defineTab } from '@sourceacademy/modules-lib/tabs'; diff --git a/src/tabs/Plotly/index.tsx b/src/tabs/Plotly/index.tsx index bb76f22183..2f3207b562 100644 --- a/src/tabs/Plotly/index.tsx +++ b/src/tabs/Plotly/index.tsx @@ -1,5 +1,5 @@ import type { DrawnPlot } from '@sourceacademy/bundle-plotly/plotly'; -import { defineTab, ModalDiv as Modal } from '@sourceacademy/modules-lib/tabs'; +import { ModalDiv as Modal, defineTab } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; type Props = { diff --git a/src/tabs/RobotSimulation/src/components/Simulation/index.tsx b/src/tabs/RobotSimulation/src/components/Simulation/index.tsx index dc44c9e883..bb3f94c7eb 100644 --- a/src/tabs/RobotSimulation/src/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/src/components/Simulation/index.tsx @@ -4,7 +4,7 @@ import type { DefaultEv3 } from '@sourceacademy/bundle-robot_simulation/controll import type { World } from '@sourceacademy/bundle-robot_simulation/engine'; import type { WorldState } from '@sourceacademy/bundle-robot_simulation/engine/World'; import type { DebuggerContext } from '@sourceacademy/modules-lib/types'; -import { useRef, type CSSProperties, useEffect, useState } from 'react'; +import { useEffect, useRef, useState, type CSSProperties } from 'react'; import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; import { ConsolePanel } from '../TabPanels/ConsolePanel'; diff --git a/src/tabs/Rune/src/index.tsx b/src/tabs/Rune/src/index.tsx index 70e789b66f..a60f5d85a7 100644 --- a/src/tabs/Rune/src/index.tsx +++ b/src/tabs/Rune/src/index.tsx @@ -1,5 +1,5 @@ -import { type RuneModuleState, isHollusionRune } from '@sourceacademy/bundle-rune/functions'; -import { defineTab, getModuleState, AnimationCanvas, MultiItemDisplay, WebGLCanvas } from '@sourceacademy/modules-lib/tabs'; +import { isHollusionRune, type RuneModuleState } from '@sourceacademy/bundle-rune/functions'; +import { AnimationCanvas, MultiItemDisplay, WebGLCanvas, defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs'; import { glAnimation, type ModuleTab } from '@sourceacademy/modules-lib/types'; import HollusionCanvas from './hollusion_canvas'; diff --git a/src/tabs/Sound/index.tsx b/src/tabs/Sound/index.tsx index f9edc7c10b..f9648d134d 100644 --- a/src/tabs/Sound/index.tsx +++ b/src/tabs/Sound/index.tsx @@ -1,5 +1,5 @@ import type { SoundModuleState } from '@sourceacademy/bundle-sound/types'; -import { getModuleState, defineTab, MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; +import { MultiItemDisplay, defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs'; import type { DebuggerContext, ModuleTab } from '@sourceacademy/modules-lib/types'; /** diff --git a/src/tabs/StereoSound/index.tsx b/src/tabs/StereoSound/index.tsx index 42867a66c5..2b939197e5 100644 --- a/src/tabs/StereoSound/index.tsx +++ b/src/tabs/StereoSound/index.tsx @@ -1,5 +1,5 @@ import type { StereoSoundModuleState } from '@sourceacademy/bundle-stereo_sound/types'; -import { defineTab, getModuleState, MultiItemDisplay } from '@sourceacademy/modules-lib/tabs'; +import { MultiItemDisplay, defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs'; import type { ModuleTab } from '@sourceacademy/modules-lib/types'; /** diff --git a/src/tabs/Unittest/index.tsx b/src/tabs/Unittest/index.tsx index d407e7a5bf..f463ace735 100644 --- a/src/tabs/Unittest/index.tsx +++ b/src/tabs/Unittest/index.tsx @@ -1,5 +1,5 @@ import type { SuiteResult, TestContext } from '@sourceacademy/bundle-unittest/types'; -import { getModuleState, defineTab } from '@sourceacademy/modules-lib/tabs'; +import { defineTab, getModuleState } from '@sourceacademy/modules-lib/tabs'; import React from 'react'; /** diff --git a/src/tabs/UnityAcademy/index.tsx b/src/tabs/UnityAcademy/index.tsx index 44c54e25af..cc9b976dc3 100644 --- a/src/tabs/UnityAcademy/index.tsx +++ b/src/tabs/UnityAcademy/index.tsx @@ -4,7 +4,7 @@ * @author Wang Zihan */ -import { Button, NumericInput, Checkbox } from '@blueprintjs/core'; +import { Button, Checkbox, NumericInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { getInstance } from '@sourceacademy/bundle-unity_academy/UnityAcademy'; import { UNITY_ACADEMY_BACKEND_URL } from '@sourceacademy/bundle-unity_academy/config'; diff --git a/vitest.config.js b/vitest.config.js index 458c058544..fb7e273a1d 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,6 +1,6 @@ // @ts-check // Root vitest config -import { defineConfig, coverageConfigDefaults } from 'vitest/config'; +import { coverageConfigDefaults, defineConfig } from 'vitest/config'; export default defineConfig({ resolve: { diff --git a/yarn.lock b/yarn.lock index 5b9a6d312b..010d5cce35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -200,6 +200,23 @@ __metadata: languageName: node linkType: hard +"@antfu/install-pkg@npm:^1.0.0": + version: 1.1.0 + resolution: "@antfu/install-pkg@npm:1.1.0" + dependencies: + package-manager-detector: "npm:^1.3.0" + tinyexec: "npm:^1.0.1" + checksum: 10c0/140d5994c76fd3d0e824c88f1ce91b3370e8066a8bc2f5729ae133bf768caa239f7915e29c78f239b7ead253113ace51293e95127fafe2b786b88eb615b3be47 + languageName: node + linkType: hard + +"@antfu/utils@npm:^8.1.0": + version: 8.1.1 + resolution: "@antfu/utils@npm:8.1.1" + checksum: 10c0/cd55d322496f0324323a7bd312bbdc305db02f5c74c53d59213a00a7ecfd66926b6755a41f27c6e664a687cd7a967d3a8b12d3ea57f264ae45dd1c5c181f5160 + languageName: node + linkType: hard + "@asamuzakjp/css-color@npm:^3.1.2": version: 3.2.0 resolution: "@asamuzakjp/css-color@npm:3.2.0" @@ -813,6 +830,62 @@ __metadata: languageName: node linkType: hard +"@braintree/sanitize-url@npm:^6.0.0": + version: 6.0.4 + resolution: "@braintree/sanitize-url@npm:6.0.4" + checksum: 10c0/5d7bac57f3e49931db83f65aaa4fd22f96caa323bf0c7fcf6851fdbed179a8cf29eaa5dd372d340fc51ca5f44345ea5bc0196b36c8b16179888a7c9044313420 + languageName: node + linkType: hard + +"@braintree/sanitize-url@npm:^7.0.4": + version: 7.1.1 + resolution: "@braintree/sanitize-url@npm:7.1.1" + checksum: 10c0/fdfc1759c4244e287693ce1e9d42d649423e7c203fdccf27a571f8951ddfe34baa5273b7e6a8dd3007d7676859c7a0a9819be0ab42a3505f8505ad0eefecf7c1 + languageName: node + linkType: hard + +"@chevrotain/cst-dts-gen@npm:11.0.3": + version: 11.0.3 + resolution: "@chevrotain/cst-dts-gen@npm:11.0.3" + dependencies: + "@chevrotain/gast": "npm:11.0.3" + "@chevrotain/types": "npm:11.0.3" + lodash-es: "npm:4.17.21" + checksum: 10c0/9e945a0611386e4e08af34c2d0b3af36c1af08f726b58145f11310f2aeafcb2d65264c06ec65a32df6b6a65771e6a55be70580c853afe3ceb51487e506967104 + languageName: node + linkType: hard + +"@chevrotain/gast@npm:11.0.3": + version: 11.0.3 + resolution: "@chevrotain/gast@npm:11.0.3" + dependencies: + "@chevrotain/types": "npm:11.0.3" + lodash-es: "npm:4.17.21" + checksum: 10c0/54fc44d7b4a7b0323f49d957dd88ad44504922d30cb226d93b430b0e09925efe44e0726068581d777f423fabfb878a2238ed2c87b690c0c0014ebd12b6968354 + languageName: node + linkType: hard + +"@chevrotain/regexp-to-ast@npm:11.0.3": + version: 11.0.3 + resolution: "@chevrotain/regexp-to-ast@npm:11.0.3" + checksum: 10c0/6939c5c94fbfb8c559a4a37a283af5ded8e6147b184a7d7bcf5ad1404d9d663c78d81602bd8ea8458ec497358a9e1671541099c511835d0be2cad46f00c62b3f + languageName: node + linkType: hard + +"@chevrotain/types@npm:11.0.3": + version: 11.0.3 + resolution: "@chevrotain/types@npm:11.0.3" + checksum: 10c0/72fe8f0010ebef848e47faea14a88c6fdc3cdbafaef6b13df4a18c7d33249b1b675e37b05cb90a421700c7016dae7cd4187ab6b549e176a81cea434f69cd2503 + languageName: node + linkType: hard + +"@chevrotain/utils@npm:11.0.3": + version: 11.0.3 + resolution: "@chevrotain/utils@npm:11.0.3" + checksum: 10c0/b31972d1b2d444eef1499cf9b7576fc1793e8544910de33a3c18e07c270cfad88067f175d0ee63e7bc604713ebed647f8190db45cc8311852cd2d4fe2ef14068 + languageName: node + linkType: hard + "@cnakazawa/watch@npm:^1.0.3": version: 1.0.4 resolution: "@cnakazawa/watch@npm:1.0.4" @@ -1633,13 +1706,29 @@ __metadata: languageName: node linkType: hard -"@iconify/types@npm:*": +"@iconify/types@npm:*, @iconify/types@npm:^2.0.0": version: 2.0.0 resolution: "@iconify/types@npm:2.0.0" checksum: 10c0/65a3be43500c7ccacf360e136d00e1717f050b7b91da644e94370256ac66f582d59212bdb30d00788aab4fc078262e91c95b805d1808d654b72f6d2072a7e4b2 languageName: node linkType: hard +"@iconify/utils@npm:^2.1.33": + version: 2.3.0 + resolution: "@iconify/utils@npm:2.3.0" + dependencies: + "@antfu/install-pkg": "npm:^1.0.0" + "@antfu/utils": "npm:^8.1.0" + "@iconify/types": "npm:^2.0.0" + debug: "npm:^4.4.0" + globals: "npm:^15.14.0" + kolorist: "npm:^1.8.0" + local-pkg: "npm:^1.0.0" + mlly: "npm:^1.7.4" + checksum: 10c0/926013852cd9d09b8501ee0f3f7d40386dc5ed1cb904869d6502f5ee1a64aee5664e9c00da49d700528d26c4a51ea0cac4f046c4eb281d0f8d54fc5df2f3fd0d + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -1819,6 +1908,30 @@ __metadata: languageName: node linkType: hard +"@mermaid-js/mermaid-mindmap@npm:^9.3.0": + version: 9.3.0 + resolution: "@mermaid-js/mermaid-mindmap@npm:9.3.0" + dependencies: + "@braintree/sanitize-url": "npm:^6.0.0" + cytoscape: "npm:^3.23.0" + cytoscape-cose-bilkent: "npm:^4.1.0" + cytoscape-fcose: "npm:^2.1.0" + d3: "npm:^7.0.0" + khroma: "npm:^2.0.0" + non-layered-tidy-tree-layout: "npm:^2.0.2" + checksum: 10c0/f1e76e79263fb681ea2143c3ce72fabb6b33520c3a8578321f487fa0aed9c421e2ef640b9e38c1800a0f11f953d20d1815c5bd0050ad69a9731522ccdbed30ae + languageName: node + linkType: hard + +"@mermaid-js/parser@npm:^0.5.0": + version: 0.5.0 + resolution: "@mermaid-js/parser@npm:0.5.0" + dependencies: + langium: "npm:3.3.1" + checksum: 10c0/af1c1cf6cfe808bf5f7c232a881e5f9d6778c2fc3997d8ea3da93f59097411d0e13f74649e2576488f82227bab58e47a49f4e77cb11cf4196176f3c4135c724d + languageName: node + linkType: hard + "@monogrid/gainmap-js@npm:^3.0.6": version: 3.1.0 resolution: "@monogrid/gainmap-js@npm:3.1.0" @@ -3250,7 +3363,9 @@ __metadata: resolution: "@sourceacademy/modules-docserver@workspace:docs" dependencies: "@sourceacademy/modules-lib": "workspace:^" + mermaid: "npm:^11.7.0" vitepress: "npm:^1.6.3" + vitepress-plugin-mermaid: "npm:^2.0.17" vitepress-sidebar: "npm:^1.31.1" languageName: unknown linkType: soft @@ -3743,6 +3858,278 @@ __metadata: languageName: node linkType: hard +"@types/d3-array@npm:*": + version: 3.2.1 + resolution: "@types/d3-array@npm:3.2.1" + checksum: 10c0/38bf2c778451f4b79ec81a2288cb4312fe3d6449ecdf562970cc339b60f280f31c93a024c7ff512607795e79d3beb0cbda123bb07010167bce32927f71364bca + languageName: node + linkType: hard + +"@types/d3-axis@npm:*": + version: 3.0.6 + resolution: "@types/d3-axis@npm:3.0.6" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/d756d42360261f44d8eefd0950c5bb0a4f67a46dd92069da3f723ac36a1e8cb2b9ce6347d836ef19d5b8aef725dbcf8fdbbd6cfbff676ca4b0642df2f78b599a + languageName: node + linkType: hard + +"@types/d3-brush@npm:*": + version: 3.0.6 + resolution: "@types/d3-brush@npm:3.0.6" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/fd6e2ac7657a354f269f6b9c58451ffae9d01b89ccb1eb6367fd36d635d2f1990967215ab498e0c0679ff269429c57fad6a2958b68f4d45bc9f81d81672edc01 + languageName: node + linkType: hard + +"@types/d3-chord@npm:*": + version: 3.0.6 + resolution: "@types/d3-chord@npm:3.0.6" + checksum: 10c0/c5a25eb5389db01e63faec0c5c2ec7cc41c494e9b3201630b494c4e862a60f1aa83fabbc33a829e7e1403941e3c30d206c741559b14406ac2a4239cfdf4b4c17 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.3 + resolution: "@types/d3-color@npm:3.1.3" + checksum: 10c0/65eb0487de606eb5ad81735a9a5b3142d30bc5ea801ed9b14b77cb14c9b909f718c059f13af341264ee189acf171508053342142bdf99338667cea26a2d8d6ae + languageName: node + linkType: hard + +"@types/d3-contour@npm:*": + version: 3.0.6 + resolution: "@types/d3-contour@npm:3.0.6" + dependencies: + "@types/d3-array": "npm:*" + "@types/geojson": "npm:*" + checksum: 10c0/e7d83e94719af4576ceb5ac7f277c5806f83ba6c3631744ae391cffc3641f09dfa279470b83053cd0b2acd6784e8749c71141d05bdffa63ca58ffb5b31a0f27c + languageName: node + linkType: hard + +"@types/d3-delaunay@npm:*": + version: 6.0.4 + resolution: "@types/d3-delaunay@npm:6.0.4" + checksum: 10c0/d154a8864f08c4ea23ecb9bdabcef1c406a25baa8895f0cb08a0ed2799de0d360e597552532ce7086ff0cdffa8f3563f9109d18f0191459d32bb620a36939123 + languageName: node + linkType: hard + +"@types/d3-dispatch@npm:*": + version: 3.0.6 + resolution: "@types/d3-dispatch@npm:3.0.6" + checksum: 10c0/405eb7d0ec139fbf72fa6a43b0f3ca8a1f913bb2cb38f607827e63fca8d4393f021f32f3b96b33c93ddbd37789453a0b3624f14f504add5308fd9aec8a46dda0 + languageName: node + linkType: hard + +"@types/d3-drag@npm:*": + version: 3.0.7 + resolution: "@types/d3-drag@npm:3.0.7" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/65e29fa32a87c72d26c44b5e2df3bf15af21cd128386bcc05bcacca255927c0397d0cd7e6062aed5f0abd623490544a9d061c195f5ed9f018fe0b698d99c079d + languageName: node + linkType: hard + +"@types/d3-dsv@npm:*": + version: 3.0.7 + resolution: "@types/d3-dsv@npm:3.0.7" + checksum: 10c0/c0f01da862465594c8a28278b51c850af3b4239cc22b14fd1a19d7a98f93d94efa477bf59d8071beb285dca45bf614630811451e18e7c52add3a0abfee0a1871 + languageName: node + linkType: hard + +"@types/d3-ease@npm:*": + version: 3.0.2 + resolution: "@types/d3-ease@npm:3.0.2" + checksum: 10c0/aff5a1e572a937ee9bff6465225d7ba27d5e0c976bd9eacdac2e6f10700a7cb0c9ea2597aff6b43a6ed850a3210030870238894a77ec73e309b4a9d0333f099c + languageName: node + linkType: hard + +"@types/d3-fetch@npm:*": + version: 3.0.7 + resolution: "@types/d3-fetch@npm:3.0.7" + dependencies: + "@types/d3-dsv": "npm:*" + checksum: 10c0/3d147efa52a26da1a5d40d4d73e6cebaaa964463c378068062999b93ea3731b27cc429104c21ecbba98c6090e58ef13429db6399238c5e3500162fb3015697a0 + languageName: node + linkType: hard + +"@types/d3-force@npm:*": + version: 3.0.10 + resolution: "@types/d3-force@npm:3.0.10" + checksum: 10c0/c82b459079a106b50e346c9b79b141f599f2fc4f598985a5211e72c7a2e20d35bd5dc6e91f306b323c8bfa325c02c629b1645f5243f1c6a55bd51bc85cccfa92 + languageName: node + linkType: hard + +"@types/d3-format@npm:*": + version: 3.0.4 + resolution: "@types/d3-format@npm:3.0.4" + checksum: 10c0/3ac1600bf9061a59a228998f7cd3f29e85cbf522997671ba18d4d84d10a2a1aff4f95aceb143fa9960501c3ec351e113fc75884e6a504ace44dc1744083035ee + languageName: node + linkType: hard + +"@types/d3-geo@npm:*": + version: 3.1.0 + resolution: "@types/d3-geo@npm:3.1.0" + dependencies: + "@types/geojson": "npm:*" + checksum: 10c0/3745a93439038bb5b0b38facf435f7079812921d46406f5d38deaee59e90084ff742443c7ea0a8446df81a0d81eaf622fe7068cf4117a544bd4aa3b2dc182f88 + languageName: node + linkType: hard + +"@types/d3-hierarchy@npm:*": + version: 3.1.7 + resolution: "@types/d3-hierarchy@npm:3.1.7" + checksum: 10c0/873711737d6b8e7b6f1dda0bcd21294a48f75024909ae510c5d2c21fad2e72032e0958def4d9f68319d3aaac298ad09c49807f8bfc87a145a82693b5208613c7 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:*": + version: 3.0.4 + resolution: "@types/d3-interpolate@npm:3.0.4" + dependencies: + "@types/d3-color": "npm:*" + checksum: 10c0/066ebb8da570b518dd332df6b12ae3b1eaa0a7f4f0c702e3c57f812cf529cc3500ec2aac8dc094f31897790346c6b1ebd8cd7a077176727f4860c2b181a65ca4 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.1.1 + resolution: "@types/d3-path@npm:3.1.1" + checksum: 10c0/2c36eb31ebaf2ce4712e793fd88087117976f7c4ed69cc2431825f999c8c77cca5cea286f3326432b770739ac6ccd5d04d851eb65e7a4dbcc10c982b49ad2c02 + languageName: node + linkType: hard + +"@types/d3-polygon@npm:*": + version: 3.0.2 + resolution: "@types/d3-polygon@npm:3.0.2" + checksum: 10c0/f46307bb32b6c2aef8c7624500e0f9b518de8f227ccc10170b869dc43e4c542560f6c8d62e9f087fac45e198d6e4b623e579c0422e34c85baf56717456d3f439 + languageName: node + linkType: hard + +"@types/d3-quadtree@npm:*": + version: 3.0.6 + resolution: "@types/d3-quadtree@npm:3.0.6" + checksum: 10c0/7eaa0a4d404adc856971c9285e1c4ab17e9135ea669d847d6db7e0066126a28ac751864e7ce99c65d526e130f56754a2e437a1617877098b3bdcc3ef23a23616 + languageName: node + linkType: hard + +"@types/d3-random@npm:*": + version: 3.0.3 + resolution: "@types/d3-random@npm:3.0.3" + checksum: 10c0/5f4fea40080cd6d4adfee05183d00374e73a10c530276a6455348983dda341003a251def28565a27c25d9cf5296a33e870e397c9d91ff83fb7495a21c96b6882 + languageName: node + linkType: hard + +"@types/d3-scale-chromatic@npm:*": + version: 3.1.0 + resolution: "@types/d3-scale-chromatic@npm:3.1.0" + checksum: 10c0/93c564e02d2e97a048e18fe8054e4a935335da6ab75a56c3df197beaa87e69122eef0dfbeb7794d4a444a00e52e3123514ee27cec084bd21f6425b7037828cc2 + languageName: node + linkType: hard + +"@types/d3-scale@npm:*": + version: 4.0.9 + resolution: "@types/d3-scale@npm:4.0.9" + dependencies: + "@types/d3-time": "npm:*" + checksum: 10c0/4ac44233c05cd50b65b33ecb35d99fdf07566bcdbc55bc1306b2f27d1c5134d8c560d356f2c8e76b096e9125ffb8d26d95f78d56e210d1c542cb255bdf31d6c8 + languageName: node + linkType: hard + +"@types/d3-selection@npm:*": + version: 3.0.11 + resolution: "@types/d3-selection@npm:3.0.11" + checksum: 10c0/0c512956c7503ff5def4bb32e0c568cc757b9a2cc400a104fc0f4cfe5e56d83ebde2a97821b6f2cb26a7148079d3b86a2f28e11d68324ed311cf35c2ed980d1d + languageName: node + linkType: hard + +"@types/d3-shape@npm:*": + version: 3.1.7 + resolution: "@types/d3-shape@npm:3.1.7" + dependencies: + "@types/d3-path": "npm:*" + checksum: 10c0/38e59771c1c4c83b67aa1f941ce350410522a149d2175832fdc06396b2bb3b2c1a2dd549e0f8230f9f24296ee5641a515eaf10f55ee1ef6c4f83749e2dd7dcfd + languageName: node + linkType: hard + +"@types/d3-time-format@npm:*": + version: 4.0.3 + resolution: "@types/d3-time-format@npm:4.0.3" + checksum: 10c0/9ef5e8e2b96b94799b821eed5d61a3d432c7903247966d8ad951b8ce5797fe46554b425cb7888fa5bf604b4663c369d7628c0328ffe80892156671c58d1a7f90 + languageName: node + linkType: hard + +"@types/d3-time@npm:*": + version: 3.0.4 + resolution: "@types/d3-time@npm:3.0.4" + checksum: 10c0/6d9e2255d63f7a313a543113920c612e957d70da4fb0890931da6c2459010291b8b1f95e149a538500c1c99e7e6c89ffcce5554dd29a31ff134a38ea94b6d174 + languageName: node + linkType: hard + +"@types/d3-timer@npm:*": + version: 3.0.2 + resolution: "@types/d3-timer@npm:3.0.2" + checksum: 10c0/c644dd9571fcc62b1aa12c03bcad40571553020feeb5811f1d8a937ac1e65b8a04b759b4873aef610e28b8714ac71c9885a4d6c127a048d95118f7e5b506d9e1 + languageName: node + linkType: hard + +"@types/d3-transition@npm:*": + version: 3.0.9 + resolution: "@types/d3-transition@npm:3.0.9" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/4f68b9df7ac745b3491216c54203cbbfa0f117ae4c60e2609cdef2db963582152035407fdff995b10ee383bae2f05b7743493f48e1b8e46df54faa836a8fb7b5 + languageName: node + linkType: hard + +"@types/d3-zoom@npm:*": + version: 3.0.8 + resolution: "@types/d3-zoom@npm:3.0.8" + dependencies: + "@types/d3-interpolate": "npm:*" + "@types/d3-selection": "npm:*" + checksum: 10c0/1dbdbcafddcae12efb5beb6948546963f29599e18bc7f2a91fb69cc617c2299a65354f2d47e282dfb86fec0968406cd4fb7f76ba2d2fb67baa8e8d146eb4a547 + languageName: node + linkType: hard + +"@types/d3@npm:^7.4.3": + version: 7.4.3 + resolution: "@types/d3@npm:7.4.3" + dependencies: + "@types/d3-array": "npm:*" + "@types/d3-axis": "npm:*" + "@types/d3-brush": "npm:*" + "@types/d3-chord": "npm:*" + "@types/d3-color": "npm:*" + "@types/d3-contour": "npm:*" + "@types/d3-delaunay": "npm:*" + "@types/d3-dispatch": "npm:*" + "@types/d3-drag": "npm:*" + "@types/d3-dsv": "npm:*" + "@types/d3-ease": "npm:*" + "@types/d3-fetch": "npm:*" + "@types/d3-force": "npm:*" + "@types/d3-format": "npm:*" + "@types/d3-geo": "npm:*" + "@types/d3-hierarchy": "npm:*" + "@types/d3-interpolate": "npm:*" + "@types/d3-path": "npm:*" + "@types/d3-polygon": "npm:*" + "@types/d3-quadtree": "npm:*" + "@types/d3-random": "npm:*" + "@types/d3-scale": "npm:*" + "@types/d3-scale-chromatic": "npm:*" + "@types/d3-selection": "npm:*" + "@types/d3-shape": "npm:*" + "@types/d3-time": "npm:*" + "@types/d3-time-format": "npm:*" + "@types/d3-timer": "npm:*" + "@types/d3-transition": "npm:*" + "@types/d3-zoom": "npm:*" + checksum: 10c0/a9c6d65b13ef3b42c87f2a89ea63a6d5640221869f97d0657b0cb2f1dac96a0f164bf5605643c0794e0de3aa2bf05df198519aaf15d24ca135eb0e8bd8a9d879 + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -3780,6 +4167,13 @@ __metadata: languageName: node linkType: hard +"@types/geojson@npm:*": + version: 7946.0.16 + resolution: "@types/geojson@npm:7946.0.16" + checksum: 10c0/1ff24a288bd5860b766b073ead337d31d73bdc715e5b50a2cee5cb0af57a1ed02cc04ef295f5fa68dc40fe3e4f104dd31282b2b818a5ba3231bc1001ba084e3c + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.2": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" @@ -4017,6 +4411,13 @@ __metadata: languageName: node linkType: hard +"@types/trusted-types@npm:^2.0.7": + version: 2.0.7 + resolution: "@types/trusted-types@npm:2.0.7" + checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c + languageName: node + linkType: hard + "@types/uniqid@npm:^5.3.4": version: 5.3.4 resolution: "@types/uniqid@npm:5.3.4" @@ -5747,6 +6148,31 @@ __metadata: languageName: node linkType: hard +"chevrotain-allstar@npm:~0.3.0": + version: 0.3.1 + resolution: "chevrotain-allstar@npm:0.3.1" + dependencies: + lodash-es: "npm:^4.17.21" + peerDependencies: + chevrotain: ^11.0.0 + checksum: 10c0/5cadedffd3114eb06b15fd3939bb1aa6c75412dbd737fe302b52c5c24334f9cb01cee8edc1d1067d98ba80dddf971f1d0e94b387de51423fc6cf3c5d8b7ef27a + languageName: node + linkType: hard + +"chevrotain@npm:~11.0.3": + version: 11.0.3 + resolution: "chevrotain@npm:11.0.3" + dependencies: + "@chevrotain/cst-dts-gen": "npm:11.0.3" + "@chevrotain/gast": "npm:11.0.3" + "@chevrotain/regexp-to-ast": "npm:11.0.3" + "@chevrotain/types": "npm:11.0.3" + "@chevrotain/utils": "npm:11.0.3" + lodash-es: "npm:4.17.21" + checksum: 10c0/ffd425fa321e3f17e9833d7f44cd39f2743f066e92ca74b226176080ca5d455f853fe9091cdfd86354bd899d85c08b3bdc3f55b267e7d07124b048a88349765f + languageName: node + linkType: hard + "chokidar@npm:^4.0.0": version: 4.0.3 resolution: "chokidar@npm:4.0.3" @@ -5846,6 +6272,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:7": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + "commander@npm:^12.0.0": version: 12.1.0 resolution: "commander@npm:12.1.0" @@ -5860,6 +6293,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10c0/8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + "commist@npm:^1.0.0": version: 1.1.0 resolution: "commist@npm:1.1.0" @@ -5896,6 +6336,20 @@ __metadata: languageName: node linkType: hard +"confbox@npm:^0.1.8": + version: 0.1.8 + resolution: "confbox@npm:0.1.8" + checksum: 10c0/fc2c68d97cb54d885b10b63e45bd8da83a8a71459d3ecf1825143dd4c7f9f1b696b3283e07d9d12a144c1301c2ebc7842380bdf0014e55acc4ae1c9550102418 + languageName: node + linkType: hard + +"confbox@npm:^0.2.2": + version: 0.2.2 + resolution: "confbox@npm:0.2.2" + checksum: 10c0/7c246588d533d31e8cdf66cb4701dff6de60f9be77ab54c0d0338e7988750ac56863cc0aca1b3f2046f45ff223a765d3e5d4977a7674485afcd37b6edf3fd129 + languageName: node + linkType: hard + "console-browserify@npm:^1.1.0": version: 1.2.0 resolution: "console-browserify@npm:1.2.0" @@ -5965,6 +6419,24 @@ __metadata: languageName: node linkType: hard +"cose-base@npm:^1.0.0": + version: 1.0.3 + resolution: "cose-base@npm:1.0.3" + dependencies: + layout-base: "npm:^1.0.0" + checksum: 10c0/a6e400b1d101393d6af0967c1353355777c1106c40417c5acaef6ca8bdda41e2fc9398f466d6c85be30290943ad631f2590569f67b3fd5368a0d8318946bd24f + languageName: node + linkType: hard + +"cose-base@npm:^2.2.0": + version: 2.2.0 + resolution: "cose-base@npm:2.2.0" + dependencies: + layout-base: "npm:^2.0.0" + checksum: 10c0/14b9f8100ac322a00777ffb1daeb3321af368bbc9cabe3103943361273baee2003202ffe38e4ab770960b600214224e9c196195a78d589521540aa694df7cdec + languageName: node + linkType: hard + "create-ecdh@npm:^4.0.4": version: 4.0.4 resolution: "create-ecdh@npm:4.0.4" @@ -5988,97 +6460,489 @@ __metadata: languageName: node linkType: hard -"create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": - version: 1.1.7 - resolution: "create-hmac@npm:1.1.7" +"create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": + version: 1.1.7 + resolution: "create-hmac@npm:1.1.7" + dependencies: + cipher-base: "npm:^1.0.3" + create-hash: "npm:^1.1.0" + inherits: "npm:^2.0.1" + ripemd160: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + sha.js: "npm:^2.4.8" + checksum: 10c0/24332bab51011652a9a0a6d160eed1e8caa091b802335324ae056b0dcb5acbc9fcf173cf10d128eba8548c3ce98dfa4eadaa01bd02f44a34414baee26b651835 + languageName: node + linkType: hard + +"create-require@npm:^1.1.1": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + +"cross-env@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-env@npm:7.0.3" + dependencies: + cross-spawn: "npm:^7.0.1" + bin: + cross-env: src/bin/cross-env.js + cross-env-shell: src/bin/cross-env-shell.js + checksum: 10c0/f3765c25746c69fcca369655c442c6c886e54ccf3ab8c16847d5ad0e91e2f337d36eedc6599c1227904bf2a228d721e690324446876115bc8e7b32a866735ecf + languageName: node + linkType: hard + +"cross-spawn@npm:^6.0.0": + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"crypto-browserify@npm:^3.12.1": + version: 3.12.1 + resolution: "crypto-browserify@npm:3.12.1" + dependencies: + browserify-cipher: "npm:^1.0.1" + browserify-sign: "npm:^4.2.3" + create-ecdh: "npm:^4.0.4" + create-hash: "npm:^1.2.0" + create-hmac: "npm:^1.1.7" + diffie-hellman: "npm:^5.0.3" + hash-base: "npm:~3.0.4" + inherits: "npm:^2.0.4" + pbkdf2: "npm:^3.1.2" + public-encrypt: "npm:^4.0.3" + randombytes: "npm:^2.1.0" + randomfill: "npm:^1.0.4" + checksum: 10c0/184a2def7b16628e79841243232ab5497f18d8e158ac21b7ce90ab172427d0a892a561280adc08f9d4d517bce8db2a5b335dc21abb970f787f8e874bd7b9db7d + languageName: node + linkType: hard + +"cssstyle@npm:^4.2.1": + version: 4.3.1 + resolution: "cssstyle@npm:4.3.1" + dependencies: + "@asamuzakjp/css-color": "npm:^3.1.2" + rrweb-cssom: "npm:^0.8.0" + checksum: 10c0/89d73252d5f9930cf67f5c576de8030a9d960aae4c8bdd42d60464b2f67c8d809601fb7e620b43d4c84e03472016da77528df9a21a21393387ed256610ca0ab4 + languageName: node + linkType: hard + +"csstype@npm:^3.0.2, csstype@npm:^3.1.3": + version: 3.1.3 + resolution: "csstype@npm:3.1.3" + checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 + languageName: node + linkType: hard + +"cytoscape-cose-bilkent@npm:^4.1.0": + version: 4.1.0 + resolution: "cytoscape-cose-bilkent@npm:4.1.0" + dependencies: + cose-base: "npm:^1.0.0" + peerDependencies: + cytoscape: ^3.2.0 + checksum: 10c0/5e2480ddba9da1a68e700ed2c674cbfd51e9efdbd55788f1971a68de4eb30708e3b3a5e808bf5628f7a258680406bbe6586d87a9133e02a9bdc1ab1a92f512f2 + languageName: node + linkType: hard + +"cytoscape-fcose@npm:^2.1.0, cytoscape-fcose@npm:^2.2.0": + version: 2.2.0 + resolution: "cytoscape-fcose@npm:2.2.0" + dependencies: + cose-base: "npm:^2.2.0" + peerDependencies: + cytoscape: ^3.2.0 + checksum: 10c0/ce472c9f85b9057e75c5685396f8e1f2468895e71b184913e05ad56dcf3092618fe59a1054f29cb0995051ba8ebe566ad0dd49a58d62845145624bd60cd44917 + languageName: node + linkType: hard + +"cytoscape@npm:^3.23.0, cytoscape@npm:^3.29.3": + version: 3.32.0 + resolution: "cytoscape@npm:3.32.0" + checksum: 10c0/21cb0d2e79ebe137c7218e96edc2fb1c9000faae4f58c6a3c1899d9689c447c91feff94e5de649f227ced66f8c6a092b838de3fff3d8b57366156900f5df6d71 + languageName: node + linkType: hard + +"d3-array@npm:1 - 2": + version: 2.12.1 + resolution: "d3-array@npm:2.12.1" + dependencies: + internmap: "npm:^1.0.0" + checksum: 10c0/7eca10427a9f113a4ca6a0f7301127cab26043fd5e362631ef5a0edd1c4b2dd70c56ed317566700c31e4a6d88b55f3951aaba192291817f243b730cb2352882e + languageName: node + linkType: hard + +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:2.5.0 - 3, d3-array@npm:3, d3-array@npm:^3.2.0": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: "npm:1 - 2" + checksum: 10c0/08b95e91130f98c1375db0e0af718f4371ccacef7d5d257727fe74f79a24383e79aba280b9ffae655483ffbbad4fd1dec4ade0119d88c4749f388641c8bf8c50 + languageName: node + linkType: hard + +"d3-axis@npm:3": + version: 3.0.0 + resolution: "d3-axis@npm:3.0.0" + checksum: 10c0/a271e70ba1966daa5aaf6a7f959ceca3e12997b43297e757c7b945db2e1ead3c6ee226f2abcfa22abbd4e2e28bd2b71a0911794c4e5b911bbba271328a582c78 + languageName: node + linkType: hard + +"d3-brush@npm:3": + version: 3.0.0 + resolution: "d3-brush@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:3" + d3-transition: "npm:3" + checksum: 10c0/07baf00334c576da2f68a91fc0da5732c3a5fa19bd3d7aed7fd24d1d674a773f71a93e9687c154176f7246946194d77c48c2d8fed757f5dcb1a4740067ec50a8 + languageName: node + linkType: hard + +"d3-chord@npm:3": + version: 3.0.1 + resolution: "d3-chord@npm:3.0.1" + dependencies: + d3-path: "npm:1 - 3" + checksum: 10c0/baa6013914af3f4fe1521f0d16de31a38eb8a71d08ff1dec4741f6f45a828661e5cd3935e39bd14e3032bdc78206c283ca37411da21d46ec3cfc520be6e7a7ce + languageName: node + linkType: hard + +"d3-color@npm:1 - 3, d3-color@npm:3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 10c0/a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c + languageName: node + linkType: hard + +"d3-contour@npm:4": + version: 4.0.2 + resolution: "d3-contour@npm:4.0.2" + dependencies: + d3-array: "npm:^3.2.0" + checksum: 10c0/98bc5fbed6009e08707434a952076f39f1cd6ed8b9288253cc3e6a3286e4e80c63c62d84954b20e64bf6e4ededcc69add54d3db25e990784a59c04edd3449032 + languageName: node + linkType: hard + +"d3-delaunay@npm:6": + version: 6.0.4 + resolution: "d3-delaunay@npm:6.0.4" + dependencies: + delaunator: "npm:5" + checksum: 10c0/57c3aecd2525664b07c4c292aa11cf49b2752c0cf3f5257f752999399fe3c592de2d418644d79df1f255471eec8057a9cc0c3062ed7128cb3348c45f69597754 + languageName: node + linkType: hard + +"d3-dispatch@npm:1 - 3, d3-dispatch@npm:3": + version: 3.0.1 + resolution: "d3-dispatch@npm:3.0.1" + checksum: 10c0/6eca77008ce2dc33380e45d4410c67d150941df7ab45b91d116dbe6d0a3092c0f6ac184dd4602c796dc9e790222bad3ff7142025f5fd22694efe088d1d941753 + languageName: node + linkType: hard + +"d3-drag@npm:2 - 3, d3-drag@npm:3": + version: 3.0.0 + resolution: "d3-drag@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-selection: "npm:3" + checksum: 10c0/d2556e8dc720741a443b595a30af403dd60642dfd938d44d6e9bfc4c71a962142f9a028c56b61f8b4790b65a34acad177d1263d66f103c3c527767b0926ef5aa + languageName: node + linkType: hard + +"d3-dsv@npm:1 - 3, d3-dsv@npm:3": + version: 3.0.1 + resolution: "d3-dsv@npm:3.0.1" + dependencies: + commander: "npm:7" + iconv-lite: "npm:0.6" + rw: "npm:1" + bin: + csv2json: bin/dsv2json.js + csv2tsv: bin/dsv2dsv.js + dsv2dsv: bin/dsv2dsv.js + dsv2json: bin/dsv2json.js + json2csv: bin/json2dsv.js + json2dsv: bin/json2dsv.js + json2tsv: bin/json2dsv.js + tsv2csv: bin/dsv2dsv.js + tsv2json: bin/dsv2json.js + checksum: 10c0/10e6af9e331950ed258f34ab49ac1b7060128ef81dcf32afc790bd1f7e8c3cc2aac7f5f875250a83f21f39bb5925fbd0872bb209f8aca32b3b77d32bab8a65ab + languageName: node + linkType: hard + +"d3-ease@npm:1 - 3, d3-ease@npm:3": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 10c0/fec8ef826c0cc35cda3092c6841e07672868b1839fcaf556e19266a3a37e6bc7977d8298c0fcb9885e7799bfdcef7db1baaba9cd4dcf4bc5e952cf78574a88b0 + languageName: node + linkType: hard + +"d3-fetch@npm:3": + version: 3.0.1 + resolution: "d3-fetch@npm:3.0.1" + dependencies: + d3-dsv: "npm:1 - 3" + checksum: 10c0/4f467a79bf290395ac0cbb5f7562483f6a18668adc4c8eb84c9d3eff048b6f6d3b6f55079ba1ebf1908dabe000c941d46be447f8d78453b2dad5fb59fb6aa93b + languageName: node + linkType: hard + +"d3-force@npm:3": + version: 3.0.0 + resolution: "d3-force@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-quadtree: "npm:1 - 3" + d3-timer: "npm:1 - 3" + checksum: 10c0/220a16a1a1ac62ba56df61028896e4b52be89c81040d20229c876efc8852191482c233f8a52bb5a4e0875c321b8e5cb6413ef3dfa4d8fe79eeb7d52c587f52cf + languageName: node + linkType: hard + +"d3-format@npm:1 - 3, d3-format@npm:3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: 10c0/049f5c0871ebce9859fc5e2f07f336b3c5bfff52a2540e0bac7e703fce567cd9346f4ad1079dd18d6f1e0eaa0599941c1810898926f10ac21a31fd0a34b4aa75 + languageName: node + linkType: hard + +"d3-geo@npm:3": + version: 3.1.1 + resolution: "d3-geo@npm:3.1.1" + dependencies: + d3-array: "npm:2.5.0 - 3" + checksum: 10c0/d32270dd2dc8ac3ea63e8805d63239c4c8ec6c0d339d73b5e5a30a87f8f54db22a78fb434369799465eae169503b25f9a107c642c8a16c32a3285bc0e6d8e8c1 + languageName: node + linkType: hard + +"d3-hierarchy@npm:3": + version: 3.1.2 + resolution: "d3-hierarchy@npm:3.1.2" + checksum: 10c0/6dcdb480539644aa7fc0d72dfc7b03f99dfbcdf02714044e8c708577e0d5981deb9d3e99bbbb2d26422b55bcc342ac89a0fa2ea6c9d7302e2fc0951dd96f89cf + languageName: node + linkType: hard + +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:3": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + checksum: 10c0/19f4b4daa8d733906671afff7767c19488f51a43d251f8b7f484d5d3cfc36c663f0a66c38fe91eee30f40327443d799be17169f55a293a3ba949e84e57a33e6a + languageName: node + linkType: hard + +"d3-path@npm:1": + version: 1.0.9 + resolution: "d3-path@npm:1.0.9" + checksum: 10c0/e35e84df5abc18091f585725b8235e1fa97efc287571585427d3a3597301e6c506dea56b11dfb3c06ca5858b3eb7f02c1bf4f6a716aa9eade01c41b92d497eb5 + languageName: node + linkType: hard + +"d3-path@npm:1 - 3, d3-path@npm:3, d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 10c0/dc1d58ec87fa8319bd240cf7689995111a124b141428354e9637aa83059eb12e681f77187e0ada5dedfce346f7e3d1f903467ceb41b379bfd01cd8e31721f5da + languageName: node + linkType: hard + +"d3-polygon@npm:3": + version: 3.0.1 + resolution: "d3-polygon@npm:3.0.1" + checksum: 10c0/e236aa7f33efa9a4072907af7dc119f85b150a0716759d4fe5f12f62573018264a6cbde8617fbfa6944a7ae48c1c0c8d3f39ae72e11f66dd471e9b5e668385df + languageName: node + linkType: hard + +"d3-quadtree@npm:1 - 3, d3-quadtree@npm:3": + version: 3.0.1 + resolution: "d3-quadtree@npm:3.0.1" + checksum: 10c0/18302d2548bfecaef788152397edec95a76400fd97d9d7f42a089ceb68d910f685c96579d74e3712d57477ed042b056881b47cd836a521de683c66f47ce89090 + languageName: node + linkType: hard + +"d3-random@npm:3": + version: 3.0.1 + resolution: "d3-random@npm:3.0.1" + checksum: 10c0/987a1a1bcbf26e6cf01fd89d5a265b463b2cea93560fc17d9b1c45e8ed6ff2db5924601bcceb808de24c94133f000039eb7fa1c469a7a844ccbf1170cbb25b41 + languageName: node + linkType: hard + +"d3-sankey@npm:^0.12.3": + version: 0.12.3 + resolution: "d3-sankey@npm:0.12.3" + dependencies: + d3-array: "npm:1 - 2" + d3-shape: "npm:^1.2.0" + checksum: 10c0/261debb01a13269f6fc53b9ebaef174a015d5ad646242c23995bf514498829ab8b8f920a7873724a7494288b46bea3ce7ebc5a920b745bc8ae4caa5885cf5204 + languageName: node + linkType: hard + +"d3-scale-chromatic@npm:3": + version: 3.1.0 + resolution: "d3-scale-chromatic@npm:3.1.0" + dependencies: + d3-color: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + checksum: 10c0/9a3f4671ab0b971f4a411b42180d7cf92bfe8e8584e637ce7e698d705e18d6d38efbd20ec64f60cc0dfe966c20d40fc172565bc28aaa2990c0a006360eed91af + languageName: node + linkType: hard + +"d3-scale@npm:4": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" dependencies: - cipher-base: "npm:^1.0.3" - create-hash: "npm:^1.1.0" - inherits: "npm:^2.0.1" - ripemd160: "npm:^2.0.0" - safe-buffer: "npm:^5.0.1" - sha.js: "npm:^2.4.8" - checksum: 10c0/24332bab51011652a9a0a6d160eed1e8caa091b802335324ae056b0dcb5acbc9fcf173cf10d128eba8548c3ce98dfa4eadaa01bd02f44a34414baee26b651835 + d3-array: "npm:2.10.0 - 3" + d3-format: "npm:1 - 3" + d3-interpolate: "npm:1.2.0 - 3" + d3-time: "npm:2.1.1 - 3" + d3-time-format: "npm:2 - 4" + checksum: 10c0/65d9ad8c2641aec30ed5673a7410feb187a224d6ca8d1a520d68a7d6eac9d04caedbff4713d1e8545be33eb7fec5739983a7ab1d22d4e5ad35368c6729d362f1 languageName: node linkType: hard -"create-require@npm:^1.1.1": - version: 1.1.1 - resolution: "create-require@npm:1.1.1" - checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 +"d3-selection@npm:2 - 3, d3-selection@npm:3": + version: 3.0.0 + resolution: "d3-selection@npm:3.0.0" + checksum: 10c0/e59096bbe8f0cb0daa1001d9bdd6dbc93a688019abc97d1d8b37f85cd3c286a6875b22adea0931b0c88410d025563e1643019161a883c516acf50c190a11b56b languageName: node linkType: hard -"cross-env@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-env@npm:7.0.3" +"d3-shape@npm:3": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" dependencies: - cross-spawn: "npm:^7.0.1" - bin: - cross-env: src/bin/cross-env.js - cross-env-shell: src/bin/cross-env-shell.js - checksum: 10c0/f3765c25746c69fcca369655c442c6c886e54ccf3ab8c16847d5ad0e91e2f337d36eedc6599c1227904bf2a228d721e690324446876115bc8e7b32a866735ecf + d3-path: "npm:^3.1.0" + checksum: 10c0/f1c9d1f09926daaf6f6193ae3b4c4b5521e81da7d8902d24b38694517c7f527ce3c9a77a9d3a5722ad1e3ff355860b014557b450023d66a944eabf8cfde37132 languageName: node linkType: hard -"cross-spawn@npm:^6.0.0": - version: 6.0.6 - resolution: "cross-spawn@npm:6.0.6" +"d3-shape@npm:^1.2.0": + version: 1.3.7 + resolution: "d3-shape@npm:1.3.7" dependencies: - nice-try: "npm:^1.0.4" - path-key: "npm:^2.0.1" - semver: "npm:^5.5.0" - shebang-command: "npm:^1.2.0" - which: "npm:^1.2.9" - checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d + d3-path: "npm:1" + checksum: 10c0/548057ce59959815decb449f15632b08e2a1bdce208f9a37b5f98ec7629dda986c2356bc7582308405ce68aedae7d47b324df41507404df42afaf352907577ae languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.6": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" +"d3-time-format@npm:2 - 4, d3-time-format@npm:4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + d3-time: "npm:1 - 3" + checksum: 10c0/735e00fb25a7fd5d418fac350018713ae394eefddb0d745fab12bbff0517f9cdb5f807c7bbe87bb6eeb06249662f8ea84fec075f7d0cd68609735b2ceb29d206 languageName: node linkType: hard -"crypto-browserify@npm:^3.12.1": - version: 3.12.1 - resolution: "crypto-browserify@npm:3.12.1" +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:3": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" dependencies: - browserify-cipher: "npm:^1.0.1" - browserify-sign: "npm:^4.2.3" - create-ecdh: "npm:^4.0.4" - create-hash: "npm:^1.2.0" - create-hmac: "npm:^1.1.7" - diffie-hellman: "npm:^5.0.3" - hash-base: "npm:~3.0.4" - inherits: "npm:^2.0.4" - pbkdf2: "npm:^3.1.2" - public-encrypt: "npm:^4.0.3" - randombytes: "npm:^2.1.0" - randomfill: "npm:^1.0.4" - checksum: 10c0/184a2def7b16628e79841243232ab5497f18d8e158ac21b7ce90ab172427d0a892a561280adc08f9d4d517bce8db2a5b335dc21abb970f787f8e874bd7b9db7d + d3-array: "npm:2 - 3" + checksum: 10c0/a984f77e1aaeaa182679b46fbf57eceb6ebdb5f67d7578d6f68ef933f8eeb63737c0949991618a8d29472dbf43736c7d7f17c452b2770f8c1271191cba724ca1 languageName: node linkType: hard -"cssstyle@npm:^4.2.1": - version: 4.3.1 - resolution: "cssstyle@npm:4.3.1" +"d3-timer@npm:1 - 3, d3-timer@npm:3": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 10c0/d4c63cb4bb5461d7038aac561b097cd1c5673969b27cbdd0e87fa48d9300a538b9e6f39b4a7f0e3592ef4f963d858c8a9f0e92754db73116770856f2fc04561a + languageName: node + linkType: hard + +"d3-transition@npm:2 - 3, d3-transition@npm:3": + version: 3.0.1 + resolution: "d3-transition@npm:3.0.1" dependencies: - "@asamuzakjp/css-color": "npm:^3.1.2" - rrweb-cssom: "npm:^0.8.0" - checksum: 10c0/89d73252d5f9930cf67f5c576de8030a9d960aae4c8bdd42d60464b2f67c8d809601fb7e620b43d4c84e03472016da77528df9a21a21393387ed256610ca0ab4 + d3-color: "npm:1 - 3" + d3-dispatch: "npm:1 - 3" + d3-ease: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + d3-timer: "npm:1 - 3" + peerDependencies: + d3-selection: 2 - 3 + checksum: 10c0/4e74535dda7024aa43e141635b7522bb70cf9d3dfefed975eb643b36b864762eca67f88fafc2ca798174f83ca7c8a65e892624f824b3f65b8145c6a1a88dbbad languageName: node linkType: hard -"csstype@npm:^3.0.2, csstype@npm:^3.1.3": - version: 3.1.3 - resolution: "csstype@npm:3.1.3" - checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 +"d3-zoom@npm:3": + version: 3.0.0 + resolution: "d3-zoom@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:2 - 3" + d3-transition: "npm:2 - 3" + checksum: 10c0/ee2036479049e70d8c783d594c444fe00e398246048e3f11a59755cd0e21de62ece3126181b0d7a31bf37bcf32fd726f83ae7dea4495ff86ec7736ce5ad36fd3 + languageName: node + linkType: hard + +"d3@npm:^7.0.0, d3@npm:^7.9.0": + version: 7.9.0 + resolution: "d3@npm:7.9.0" + dependencies: + d3-array: "npm:3" + d3-axis: "npm:3" + d3-brush: "npm:3" + d3-chord: "npm:3" + d3-color: "npm:3" + d3-contour: "npm:4" + d3-delaunay: "npm:6" + d3-dispatch: "npm:3" + d3-drag: "npm:3" + d3-dsv: "npm:3" + d3-ease: "npm:3" + d3-fetch: "npm:3" + d3-force: "npm:3" + d3-format: "npm:3" + d3-geo: "npm:3" + d3-hierarchy: "npm:3" + d3-interpolate: "npm:3" + d3-path: "npm:3" + d3-polygon: "npm:3" + d3-quadtree: "npm:3" + d3-random: "npm:3" + d3-scale: "npm:4" + d3-scale-chromatic: "npm:3" + d3-selection: "npm:3" + d3-shape: "npm:3" + d3-time: "npm:3" + d3-time-format: "npm:4" + d3-timer: "npm:3" + d3-transition: "npm:3" + d3-zoom: "npm:3" + checksum: 10c0/3dd9c08c73cfaa69c70c49e603c85e049c3904664d9c79a1a52a0f52795828a1ff23592dc9a7b2257e711d68a615472a13103c212032f38e016d609796e087e8 + languageName: node + linkType: hard + +"dagre-d3-es@npm:7.0.11": + version: 7.0.11 + resolution: "dagre-d3-es@npm:7.0.11" + dependencies: + d3: "npm:^7.9.0" + lodash-es: "npm:^4.17.21" + checksum: 10c0/52f88bdfeca0d8554bee0c1419377585355b4ef179e5fedd3bac75f772745ecb789f6d7ea377a17566506bc8f151bc0dfe02a5175207a547975f335cd88c726c languageName: node linkType: hard @@ -6132,7 +6996,14 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.1": +"dayjs@npm:^1.11.13": + version: 1.11.13 + resolution: "dayjs@npm:1.11.13" + checksum: 10c0/a3caf6ac8363c7dade9d1ee797848ddcf25c1ace68d9fe8678ecf8ba0675825430de5d793672ec87b24a69bf04a1544b176547b2539982275d5542a7955f35b7 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -6249,6 +7120,15 @@ __metadata: languageName: node linkType: hard +"delaunator@npm:5": + version: 5.0.1 + resolution: "delaunator@npm:5.0.1" + dependencies: + robust-predicates: "npm:^3.0.2" + checksum: 10c0/3d7ea4d964731c5849af33fec0a271bc6753487b331fd7d43ccb17d77834706e1c383e6ab8fda0032da955e7576d1083b9603cdaf9cbdfd6b3ebd1fb8bb675a5 + languageName: node + linkType: hard + "dequal@npm:^2.0.0, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -6353,6 +7233,18 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^3.2.5": + version: 3.2.6 + resolution: "dompurify@npm:3.2.6" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10c0/c8f8e5b0879a0d93c84a2e5e78649a47d0c057ed0f7850ca3d573d2cca64b84fb1ff85bd4b20980ade69c4e5b80ae73011340f1c2ff375c7ef98bb8268e1d13a + languageName: node + linkType: hard + "dot-case@npm:^3.0.4": version: 3.0.4 resolution: "dot-case@npm:3.0.4" @@ -7317,6 +8209,13 @@ __metadata: languageName: node linkType: hard +"exsolve@npm:^1.0.7": + version: 1.0.7 + resolution: "exsolve@npm:1.0.7" + checksum: 10c0/4479369d0bd84bb7e0b4f5d9bc18d26a89b6dbbbccd73f9d383d14892ef78ddbe159e01781055342f83dc00ebe90044036daf17ddf55cc21e2cac6609aa15631 + languageName: node + linkType: hard + "extend-shallow@npm:^2.0.1": version: 2.0.1 resolution: "extend-shallow@npm:2.0.1" @@ -7840,6 +8739,13 @@ __metadata: languageName: node linkType: hard +"globals@npm:^15.14.0": + version: 15.15.0 + resolution: "globals@npm:15.15.0" + checksum: 10c0/f9ae80996392ca71316495a39bec88ac43ae3525a438b5626cd9d5ce9d5500d0a98a266409605f8cd7241c7acf57c354a48111ea02a767ba4f374b806d6861fe + languageName: node + linkType: hard + "globalthis@npm:^1.0.4": version: 1.0.4 resolution: "globalthis@npm:1.0.4" @@ -7904,6 +8810,13 @@ __metadata: languageName: node linkType: hard +"hachure-fill@npm:^0.5.2": + version: 0.5.2 + resolution: "hachure-fill@npm:0.5.2" + checksum: 10c0/307e3b6f9f2d3c11a82099c3f71eecbb9c440c00c1f896ac1732c23e6dbff16a92bb893d222b8b721b89cf11e58649ca60b4c24e5663f705f877cefd40153429 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.2": version: 1.1.0 resolution: "has-bigints@npm:1.1.0" @@ -8220,7 +9133,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": +"iconv-lite@npm:0.6, iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -8309,6 +9222,20 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 10c0/8cedd57f07bbc22501516fbfc70447f0c6812871d471096fad9ea603516eacc2137b633633daf432c029712df0baefd793686388ddf5737e3ea15074b877f7ed + languageName: node + linkType: hard + +"internmap@npm:^1.0.0": + version: 1.0.1 + resolution: "internmap@npm:1.0.1" + checksum: 10c0/60942be815ca19da643b6d4f23bd0bf4e8c97abbd080fb963fe67583b60bdfb3530448ad4486bae40810e92317bded9995cc31411218acc750d72cd4e8646eee + languageName: node + linkType: hard + "ip-address@npm:^9.0.5": version: 9.0.5 resolution: "ip-address@npm:9.0.5" @@ -9142,6 +10069,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.16.9": + version: 0.16.22 + resolution: "katex@npm:0.16.22" + dependencies: + commander: "npm:^8.3.0" + bin: + katex: cli.js + checksum: 10c0/07b8b1f07ae53171b5f1ea0cf6f18841d2055825c8b11cd81cfe039afcd3af2cfc84ad033531ee3875088329105195b039c267e0dd4b0c237807e3c3b2009913 + languageName: node + linkType: hard + "keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -9151,6 +10089,13 @@ __metadata: languageName: node linkType: hard +"khroma@npm:^2.0.0, khroma@npm:^2.1.0": + version: 2.1.0 + resolution: "khroma@npm:2.1.0" + checksum: 10c0/634d98753ff5d2540491cafeb708fc98de0d43f4e6795256d5c8f6e3ad77de93049ea41433928fda3697adf7bbe6fe27351858f6d23b78f8b5775ef314c59891 + languageName: node + linkType: hard + "kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0": version: 3.2.2 resolution: "kind-of@npm:3.2.2" @@ -9176,6 +10121,26 @@ __metadata: languageName: node linkType: hard +"kolorist@npm:^1.8.0": + version: 1.8.0 + resolution: "kolorist@npm:1.8.0" + checksum: 10c0/73075db44a692bf6c34a649f3b4b3aea4993b84f6b754cbf7a8577e7c7db44c0bad87752bd23b0ce533f49de2244ce2ce03b7b1b667a85ae170a94782cc50f9b + languageName: node + linkType: hard + +"langium@npm:3.3.1": + version: 3.3.1 + resolution: "langium@npm:3.3.1" + dependencies: + chevrotain: "npm:~11.0.3" + chevrotain-allstar: "npm:~0.3.0" + vscode-languageserver: "npm:~9.0.1" + vscode-languageserver-textdocument: "npm:~1.0.11" + vscode-uri: "npm:~3.0.8" + checksum: 10c0/0c54803068addb0f7c16a57fdb2db2e5d4d9a21259d477c3c7d0587c2c2f65a313f9eeef3c95ac1c2e41cd11d4f2eaf620d2c03fe839a3350ffee59d2b4c7647 + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.23 resolution: "language-subtag-registry@npm:0.3.23" @@ -9192,6 +10157,20 @@ __metadata: languageName: node linkType: hard +"layout-base@npm:^1.0.0": + version: 1.0.2 + resolution: "layout-base@npm:1.0.2" + checksum: 10c0/2a55d0460fd9f6ed53d7e301b9eb3dea19bda03815d616a40665ce6dc75c1f4d62e1ca19a897da1cfaf6de1b91de59cd6f2f79ba1258f3d7fccc7d46ca7f3337 + languageName: node + linkType: hard + +"layout-base@npm:^2.0.0": + version: 2.0.1 + resolution: "layout-base@npm:2.0.1" + checksum: 10c0/a44df9ef3cbff9916a10f616635e22b5787c89fa62b2fec6f99e8e6ee512c7cebd22668ce32dab5a83c934ba0a309c51a678aa0b40d70853de6c357893c0a88b + languageName: node + linkType: hard + "leven@npm:^2.1.0": version: 2.1.0 resolution: "leven@npm:2.1.0" @@ -9234,6 +10213,17 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^1.0.0": + version: 1.1.1 + resolution: "local-pkg@npm:1.1.1" + dependencies: + mlly: "npm:^1.7.4" + pkg-types: "npm:^2.0.1" + quansync: "npm:^0.2.8" + checksum: 10c0/fe8f9d0443fb066c3f28a4c89d587dd7cba3ab02645cd16598f8d5f30968acf60af1b0ec2d6ad768475ec9f52baad124f31a93d2fbc034f645bcc02bf3a84882 + languageName: node + linkType: hard + "locate-path@npm:^5.0.0": version: 5.0.0 resolution: "locate-path@npm:5.0.0" @@ -9252,6 +10242,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:4.17.21, lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + languageName: node + linkType: hard + "lodash.get@npm:^4.4.2": version: 4.4.2 resolution: "lodash.get@npm:4.4.2" @@ -9468,6 +10465,15 @@ __metadata: languageName: node linkType: hard +"marked@npm:^15.0.7": + version: 15.0.12 + resolution: "marked@npm:15.0.12" + bin: + marked: bin/marked.js + checksum: 10c0/e09da211544b787ecfb25fed07af206060bf7cd6d9de6cb123f15c496a57f83b7aabea93340aaa94dae9c94e097ae129377cad6310abc16009590972e85f4212 + languageName: node + linkType: hard + "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -9683,6 +10689,34 @@ __metadata: languageName: node linkType: hard +"mermaid@npm:^11.7.0": + version: 11.7.0 + resolution: "mermaid@npm:11.7.0" + dependencies: + "@braintree/sanitize-url": "npm:^7.0.4" + "@iconify/utils": "npm:^2.1.33" + "@mermaid-js/parser": "npm:^0.5.0" + "@types/d3": "npm:^7.4.3" + cytoscape: "npm:^3.29.3" + cytoscape-cose-bilkent: "npm:^4.1.0" + cytoscape-fcose: "npm:^2.2.0" + d3: "npm:^7.9.0" + d3-sankey: "npm:^0.12.3" + dagre-d3-es: "npm:7.0.11" + dayjs: "npm:^1.11.13" + dompurify: "npm:^3.2.5" + katex: "npm:^0.16.9" + khroma: "npm:^2.1.0" + lodash-es: "npm:^4.17.21" + marked: "npm:^15.0.7" + roughjs: "npm:^4.6.6" + stylis: "npm:^4.3.6" + ts-dedent: "npm:^2.2.0" + uuid: "npm:^11.1.0" + checksum: 10c0/ab37f563b54d53c513d792a91aae54c6e2ed20f4d8606cdec993d60b8c50534ac6ab740408d710a655c6190341704cf133f0a7fb47e230c0c94b38cf08e07775 + languageName: node + linkType: hard + "meshline@npm:^3.3.1": version: 3.3.1 resolution: "meshline@npm:3.3.1" @@ -10276,6 +11310,18 @@ __metadata: languageName: node linkType: hard +"mlly@npm:^1.7.4": + version: 1.7.4 + resolution: "mlly@npm:1.7.4" + dependencies: + acorn: "npm:^8.14.0" + pathe: "npm:^2.0.1" + pkg-types: "npm:^1.3.0" + ufo: "npm:^1.5.4" + checksum: 10c0/69e738218a13d6365caf930e0ab4e2b848b84eec261597df9788cefb9930f3e40667be9cb58a4718834ba5f97a6efeef31d3b5a95f4388143fd4e0d0deff72ff + languageName: node + linkType: hard + "mqtt-packet@npm:^6.8.0": version: 6.10.0 resolution: "mqtt-packet@npm:6.10.0" @@ -10489,6 +11535,13 @@ __metadata: languageName: node linkType: hard +"non-layered-tidy-tree-layout@npm:^2.0.2": + version: 2.0.2 + resolution: "non-layered-tidy-tree-layout@npm:2.0.2" + checksum: 10c0/73856e9959667193e733a7ef2b06a69421f4d9d7428a3982ce39763cd979a04eed0007f2afb3414afa3f6dc4dc6b5c850c2af9aa71a974475236a465093ec9c7 + languageName: node + linkType: hard + "nopt@npm:^8.0.0": version: 8.1.0 resolution: "nopt@npm:8.1.0" @@ -10820,6 +11873,13 @@ __metadata: languageName: node linkType: hard +"package-manager-detector@npm:^1.3.0": + version: 1.3.0 + resolution: "package-manager-detector@npm:1.3.0" + checksum: 10c0/b4b54a81a3230edd66564a59ff6a2233086961e36ba91a28a0f6d6932a8dec36618ace50e8efec9c4d8c6aa9828e98814557a39fb6b106c161434ccb44a80e1c + languageName: node + linkType: hard + "pako@npm:~1.0.5": version: 1.0.11 resolution: "pako@npm:1.0.11" @@ -10903,6 +11963,13 @@ __metadata: languageName: node linkType: hard +"path-data-parser@npm:0.1.0, path-data-parser@npm:^0.1.0": + version: 0.1.0 + resolution: "path-data-parser@npm:0.1.0" + checksum: 10c0/ba22d54669a8bc4a3df27431fe667900685585d1196085b803d0aa4066b83e709bbf2be7c1d2b56e706b49cc698231d55947c22abbfc4843ca424bbf8c985745 + languageName: node + linkType: hard + "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -10955,7 +12022,7 @@ __metadata: languageName: node linkType: hard -"pathe@npm:^2.0.3": +"pathe@npm:^2.0.1, pathe@npm:^2.0.3": version: 2.0.3 resolution: "pathe@npm:2.0.3" checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 @@ -11035,6 +12102,28 @@ __metadata: languageName: node linkType: hard +"pkg-types@npm:^1.3.0": + version: 1.3.1 + resolution: "pkg-types@npm:1.3.1" + dependencies: + confbox: "npm:^0.1.8" + mlly: "npm:^1.7.4" + pathe: "npm:^2.0.1" + checksum: 10c0/19e6cb8b66dcc66c89f2344aecfa47f2431c988cfa3366bdfdcfb1dd6695f87dcce37fbd90fe9d1605e2f4440b77f391e83c23255347c35cf84e7fd774d7fcea + languageName: node + linkType: hard + +"pkg-types@npm:^2.0.1": + version: 2.1.1 + resolution: "pkg-types@npm:2.1.1" + dependencies: + confbox: "npm:^0.2.2" + exsolve: "npm:^1.0.7" + pathe: "npm:^2.0.3" + checksum: 10c0/a1e1fd82ae2b3efa6cf048598fac08ed9451036508bd4df4c3a9049a9b366c1039102cc336c879cb790dbd0a036152304dafbcb6592a9c10186f335a493a29a6 + languageName: node + linkType: hard + "playwright-core@npm:1.52.0": version: 1.52.0 resolution: "playwright-core@npm:1.52.0" @@ -11066,6 +12155,23 @@ __metadata: languageName: node linkType: hard +"points-on-curve@npm:0.2.0, points-on-curve@npm:^0.2.0": + version: 0.2.0 + resolution: "points-on-curve@npm:0.2.0" + checksum: 10c0/f0d92343fcc2ad1f48334633e580574c1e0e28038a756133e171e537f270d6d64203feada5ee556e36f448a1b46e0306dee07b30f589f4e3ad720f6ee38ef48c + languageName: node + linkType: hard + +"points-on-path@npm:^0.2.1": + version: 0.2.1 + resolution: "points-on-path@npm:0.2.1" + dependencies: + path-data-parser: "npm:0.1.0" + points-on-curve: "npm:0.2.0" + checksum: 10c0/a7010340f9f196976f61838e767bb7b0b7f6273ab4fb9eb37c61001fe26fbfc3fcd63c96d5e85b9a4ab579213ab366f2ddaaf60e2a9253e2b91a62db33f395ba + languageName: node + linkType: hard + "portfinder@npm:^1.0.25": version: 1.0.37 resolution: "portfinder@npm:1.0.37" @@ -11264,6 +12370,13 @@ __metadata: languageName: node linkType: hard +"quansync@npm:^0.2.8": + version: 0.2.10 + resolution: "quansync@npm:0.2.10" + checksum: 10c0/f86f1d644f812a3a7c42de79eb401c47a5a67af82a9adff8a8afb159325e03e00f77cebbf42af6340a0bd47bd0c1fbe999e7caf7e1bbb30d7acb00c8729b7530 + languageName: node + linkType: hard + "querystring-es3@npm:^0.2.1": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" @@ -11717,6 +12830,13 @@ __metadata: languageName: node linkType: hard +"robust-predicates@npm:^3.0.2": + version: 3.0.2 + resolution: "robust-predicates@npm:3.0.2" + checksum: 10c0/4ecd53649f1c2d49529c85518f2fa69ffb2f7a4453f7fd19c042421c7b4d76c3efb48bc1c740c8f7049346d7cb58cf08ee0c9adaae595cc23564d360adb1fde4 + languageName: node + linkType: hard + "rollup@npm:^4.20.0": version: 4.44.0 resolution: "rollup@npm:4.44.0" @@ -11942,6 +13062,18 @@ __metadata: languageName: node linkType: hard +"roughjs@npm:^4.6.6": + version: 4.6.6 + resolution: "roughjs@npm:4.6.6" + dependencies: + hachure-fill: "npm:^0.5.2" + path-data-parser: "npm:^0.1.0" + points-on-curve: "npm:^0.2.0" + points-on-path: "npm:^0.2.1" + checksum: 10c0/68c11bf4516aa014cef2fe52426a9bab237c2f500d13e1a4f13b523cb5723667bf2d92b9619325efdc5bc2a193588ff5af8d51683df17cfb8720e96fe2b92b0c + languageName: node + linkType: hard + "rrweb-cssom@npm:^0.8.0": version: 0.8.0 resolution: "rrweb-cssom@npm:0.8.0" @@ -11965,6 +13097,13 @@ __metadata: languageName: node linkType: hard +"rw@npm:1": + version: 1.3.3 + resolution: "rw@npm:1.3.3" + checksum: 10c0/b1e1ef37d1e79d9dc7050787866e30b6ddcb2625149276045c262c6b4d53075ddc35f387a856a8e76f0d0df59f4cd58fe24707e40797ebee66e542b840ed6a53 + languageName: node + linkType: hard + "saar@npm:^1.0.4": version: 1.0.5 resolution: "saar@npm:1.0.5" @@ -12867,6 +14006,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:^4.3.6": + version: 4.3.6 + resolution: "stylis@npm:4.3.6" + checksum: 10c0/e736d484983a34f7c65d362c67dc79b7bce388054b261c2b7b23d02eaaf280617033f65d44b1ea341854f4331a5074b885668ac8741f98c13a6cfd6443ae85d0 + languageName: node + linkType: hard + "superjson@npm:^2.2.2": version: 2.2.2 resolution: "superjson@npm:2.2.2" @@ -13020,6 +14166,13 @@ __metadata: languageName: node linkType: hard +"tinyexec@npm:^1.0.1": + version: 1.0.1 + resolution: "tinyexec@npm:1.0.1" + checksum: 10c0/e1ec3c8194a0427ce001ba69fd933d0c957e2b8994808189ed8020d3e0c01299aea8ecf0083cc514ecbf90754695895f2b5c0eac07eb2d0c406f7d4fbb8feade + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13": version: 0.2.13 resolution: "tinyglobby@npm:0.2.13" @@ -13217,6 +14370,13 @@ __metadata: languageName: node linkType: hard +"ts-dedent@npm:^2.2.0": + version: 2.2.0 + resolution: "ts-dedent@npm:2.2.0" + checksum: 10c0/175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.15.0": version: 3.15.0 resolution: "tsconfig-paths@npm:3.15.0" @@ -13446,6 +14606,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.5.4": + version: 1.6.1 + resolution: "ufo@npm:1.6.1" + checksum: 10c0/5a9f041e5945fba7c189d5410508cbcbefef80b253ed29aa2e1f8a2b86f4bd51af44ee18d4485e6d3468c92be9bf4a42e3a2b72dcaf27ce39ce947ec994f1e6b + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -13677,6 +14844,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + languageName: node + linkType: hard + "vfile-message@npm:^4.0.0": version: 4.0.2 resolution: "vfile-message@npm:4.0.2" @@ -13877,6 +15053,21 @@ __metadata: languageName: node linkType: hard +"vitepress-plugin-mermaid@npm:^2.0.17": + version: 2.0.17 + resolution: "vitepress-plugin-mermaid@npm:2.0.17" + dependencies: + "@mermaid-js/mermaid-mindmap": "npm:^9.3.0" + peerDependencies: + mermaid: 10 || 11 + vitepress: ^1.0.0 || ^1.0.0-alpha + dependenciesMeta: + "@mermaid-js/mermaid-mindmap": + optional: true + checksum: 10c0/706ea493dcad091d20a7964e0287b9ae2144bcdb9a85b3d925480b2512e51a92fe3e59d51bce9afddd0b607c021bbc30eb22fafafc4319ea29f0666121d73f48 + languageName: node + linkType: hard + "vitepress-sidebar@npm:^1.31.1": version: 1.31.1 resolution: "vitepress-sidebar@npm:1.31.1" @@ -14006,6 +15197,55 @@ __metadata: languageName: node linkType: hard +"vscode-jsonrpc@npm:8.2.0": + version: 8.2.0 + resolution: "vscode-jsonrpc@npm:8.2.0" + checksum: 10c0/0789c227057a844f5ead55c84679206227a639b9fb76e881185053abc4e9848aa487245966cc2393fcb342c4541241b015a1a2559fddd20ac1e68945c95344e6 + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:3.17.5": + version: 3.17.5 + resolution: "vscode-languageserver-protocol@npm:3.17.5" + dependencies: + vscode-jsonrpc: "npm:8.2.0" + vscode-languageserver-types: "npm:3.17.5" + checksum: 10c0/5f38fd80da9868d706eaa4a025f4aff9c3faad34646bcde1426f915cbd8d7e8b6c3755ce3fef6eebd256ba3145426af1085305f8a76e34276d2e95aaf339a90b + languageName: node + linkType: hard + +"vscode-languageserver-textdocument@npm:~1.0.11": + version: 1.0.12 + resolution: "vscode-languageserver-textdocument@npm:1.0.12" + checksum: 10c0/534349894b059602c4d97615a1147b6c4c031141c2093e59657f54e38570f5989c21b376836f13b9375419869242e9efb4066643208b21ab1e1dee111a0f00fb + languageName: node + linkType: hard + +"vscode-languageserver-types@npm:3.17.5": + version: 3.17.5 + resolution: "vscode-languageserver-types@npm:3.17.5" + checksum: 10c0/1e1260de79a2cc8de3e46f2e0182cdc94a7eddab487db5a3bd4ee716f67728e685852707d72c059721ce500447be9a46764a04f0611e94e4321ffa088eef36f8 + languageName: node + linkType: hard + +"vscode-languageserver@npm:~9.0.1": + version: 9.0.1 + resolution: "vscode-languageserver@npm:9.0.1" + dependencies: + vscode-languageserver-protocol: "npm:3.17.5" + bin: + installServerIntoExtension: bin/installServerIntoExtension + checksum: 10c0/8a0838d77c98a211c76e54bd3a6249fc877e4e1a73322673fb0e921168d8e91de4f170f1d4ff7e8b6289d0698207afc6aba6662d4c1cd8e4bd7cae96afd6b0c2 + languageName: node + linkType: hard + +"vscode-uri@npm:~3.0.8": + version: 3.0.8 + resolution: "vscode-uri@npm:3.0.8" + checksum: 10c0/f7f217f526bf109589969fe6e66b71e70b937de1385a1d7bb577ca3ee7c5e820d3856a86e9ff2fa9b7a0bc56a3dd8c3a9a557d3fedd7df414bc618d5e6b567f9 + languageName: node + linkType: hard + "vue@npm:^3.5.13": version: 3.5.17 resolution: "vue@npm:3.5.17" From 06102fba18997b3740773f9d838dc8d598d52c29 Mon Sep 17 00:00:00 2001 From: Lee Yi Date: Sat, 5 Jul 2025 21:05:25 +0800 Subject: [PATCH 097/112] More linting changes --- .vscode/settings.json | 2 +- .../sideContent/importers/importers.ts | 2 +- devserver/tsconfig.json | 2 +- docs/.vitepress/config.ts | 22 ++- docs/src/buildtools/2-command.md | 28 +++ docs/src/buildtools/6-prebuild.md | 7 +- docs/src/lib/dev/index.md | 4 +- docs/src/modules/1-getting-started/3-cheat.md | 110 ++++++++--- .../modules/1-getting-started/scripts.data.ts | 32 +++ .../modules/2-bundle/1-overview/1-overview.md | 30 ++- .../modules/2-bundle/2-creating/2-creating.md | 13 +- docs/src/modules/2-bundle/3-editing.md | 3 + .../4-documentation/4-documentation.md | 14 ++ docs/src/modules/2-bundle/6-compiling.md | 6 +- docs/src/modules/3-tabs/1-overview.md | 50 +++-- docs/src/modules/3-tabs/3-editing.md | 94 ++++++++- .../src/modules/3-tabs/4-context/4-context.md | 14 +- docs/src/modules/3-tabs/5-compiling.md | 7 + .../modules/3-tabs/6-devserver/6-devserver.md | 2 + docs/src/modules/4-advanced/context.md | 15 +- docs/src/modules/4-advanced/cross-use.md | 8 +- docs/src/{ => public}/favicon.ico | Bin eslint.config.js | 153 ++++++++------ .../__test_mocks__/bundles/test0/src/index.ts | 2 +- lib/buildtools/src/__tests__/utils.test.ts | 2 +- lib/buildtools/src/build/docs/docsUtils.ts | 1 + lib/buildtools/src/commands/prebuild.ts | 5 +- lib/buildtools/src/prebuild/index.ts | 4 +- lib/buildtools/src/prebuild/lint.ts | 3 +- lib/buildtools/src/prebuild/tsc.ts | 2 +- lib/modules-lib/src/type_map.ts | 11 +- package.json | 5 +- src/bundles/copy_gc/tsconfig.json | 2 +- src/bundles/curve/manifest.json | 3 +- src/bundles/curve/package.json | 5 +- src/bundles/midi/package.json | 5 +- src/bundles/midi/src/__tests__/index.ts | 71 +++++++ src/bundles/midi/src/index.ts | 90 +++------ src/bundles/midi/src/scales.ts | 89 +++++++++ src/bundles/midi/src/utils.ts | 62 ++++++ src/bundles/repl/src/functions.ts | 22 +-- src/bundles/repl/src/index.ts | 2 +- src/bundles/repl/src/programmable_repl.ts | 14 +- src/bundles/rune/src/functions.ts | 13 +- src/bundles/rune/src/runes_ops.ts | 8 +- src/bundles/rune_in_words/src/functions.ts | 176 ++++++++--------- src/bundles/sound/package.json | 1 + src/bundles/sound/src/functions.ts | 89 +-------- src/bundles/sound/src/index.ts | 9 +- src/bundles/stereo_sound/package.json | 1 + src/bundles/stereo_sound/src/functions.ts | 89 +-------- src/bundles/stereo_sound/src/index.ts | 12 +- .../unity_academy/src/UnityAcademy.tsx | 187 +++++++++--------- .../unity_academy/src/UnityAcademyMaths.ts | 22 +-- src/bundles/unity_academy/src/functions.ts | 170 ++++++++-------- src/tabs/Repl/index.tsx | 4 +- src/tabs/UnityAcademy/index.tsx | 8 +- yarn.lock | 112 ++++++++++- 58 files changed, 1195 insertions(+), 724 deletions(-) create mode 100644 docs/src/modules/1-getting-started/scripts.data.ts rename docs/src/{ => public}/favicon.ico (100%) create mode 100644 src/bundles/midi/src/__tests__/index.ts create mode 100644 src/bundles/midi/src/scales.ts create mode 100644 src/bundles/midi/src/utils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 5943c6376e..d4b626935d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ { "fileMatch": ["src/bundles/**/manifest.json"], "url": "./lib/buildtools/src/build/modules/manifest.schema.json" - }, + }, { "fileMatch": ["tsconfig.json", "tsconfig.*.json"], "schema": { diff --git a/devserver/src/components/sideContent/importers/importers.ts b/devserver/src/components/sideContent/importers/importers.ts index 8db69ae5f4..43c0f61d09 100644 --- a/devserver/src/components/sideContent/importers/importers.ts +++ b/devserver/src/components/sideContent/importers/importers.ts @@ -49,7 +49,7 @@ export async function getCompiledTabs(context: Context) { if (manifest.tabs) { const tabsToSpawn = manifest.tabs as string[]; return Promise.all(tabsToSpawn.map(async (tabName): Promise => { - const { default: rawTab } = await import(`../../../../../build/tabs/${tabName}.js`); + const { default: rawTab } = await import(/* @vite-ignore */ `../../../../../build/tabs/${tabName}.js`); const { default: content } = await (rawTab as RawTab)(requireProvider, React); return content; })); diff --git a/devserver/tsconfig.json b/devserver/tsconfig.json index cd00a42271..ee2e19b532 100644 --- a/devserver/tsconfig.json +++ b/devserver/tsconfig.json @@ -25,7 +25,7 @@ "verbatimModuleSyntax": true, "types": [ "@vitest/browser/matchers", - "@vitest/browser/providers/playwright" + "@vitest/browser/providers/playwright" ] }, "include": ["src"], diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 0fbb70962a..1f48f46cb5 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -17,7 +17,7 @@ const vitepressOptions: UserConfig = { title: 'Modules Developer Documentation', themeConfig: { // https://vitepress.dev/reference/default-theme-config - logo: './favicon.ico', + logo: '/favicon.ico', nav: [ { text: 'Home', link: '/' }, { @@ -30,10 +30,28 @@ const vitepressOptions: UserConfig = { { text: 'Getting Started', link: '/modules/1-getting-started/2-start' + }, + { + text: 'Bundles', + link: '/modules/2-bundle/1-overview/1-overview' + }, + { + text: 'Tabs', + link: '/modules/3-tabs/1-overview' } ] }, - { text: 'Common Library', link: '/lib' }, + { + text: 'Libraries', + items: [ + { + text: 'Common Libraries', + link: '/lib' + }, { + text: 'Developer Docs', + link: '/lib/dev' + }] + }, { text: 'Dev Tools', items: [ diff --git a/docs/src/buildtools/2-command.md b/docs/src/buildtools/2-command.md index 722d7c6da7..7f0deb66b5 100644 --- a/docs/src/buildtools/2-command.md +++ b/docs/src/buildtools/2-command.md @@ -15,3 +15,31 @@ Command execution follows the following steps: ## CI Pipeline Compatibility Since the buildtools need to be run as part of the CI pipeline, it is important that they return non-zero exit codes when appropriate so that the CI pipeline can detect that errors have occurred and halt the pipeline. + +## Command Execution Flow + +### Single Bundle/Tab +For a command that processes a single bundle or tab, this is the flow of the command: +```mermaid +graph TD + A[Resolve bundle/tab] + C["Run prebuilds (if necessary)"] + B[Run Command] + D[Check if running in CI Mode] + E[Display error and exit with code 1] + F[Display success message] + + A -- Is resolved --> C + A -- Is not resolved --> E + + C -- No errors or warnings --> B + C -- Warnings present --> D + C -- Errors present --> E + + B -- Warnings present --> D + B -- Errors present --> E + + D -- Is CI Mode --> E + D -- Not CI Mode --> F + B -- No errors or warnings --> F +``` diff --git a/docs/src/buildtools/6-prebuild.md b/docs/src/buildtools/6-prebuild.md index 8ab4ac6711..fa56cb3f2f 100644 --- a/docs/src/buildtools/6-prebuild.md +++ b/docs/src/buildtools/6-prebuild.md @@ -18,6 +18,11 @@ errors always cause a non-zero exit code. ESLint provides several [formatters](https://eslint.org/docs/latest/use/formatters/) for processing the results objects it returns. To produce the human readable output that is printed to the command line, the `stylish` formatter is loaded and used. +::: details Inspecting the Linting Config +The entire repository's linting configuration is located at the root of the repository within `eslint.config.js`. If you want to view the view what rules are being applied to which files you can +use the config inspector, which can be started using `yarn lint:inspect` +::: + ## Calling Typescript from Node Most of the code for running Typescript functionality from Node was taken from [this](https://github.com/Microsoft/TypeScript/issues/6387) Github issue. @@ -39,7 +44,7 @@ The first three steps in the process involve reading the raw text from the `tsco in use, as well as the file paths to the files that are to be processed. ### Type Checking -The first time `ts.createProgram` is called, it is called with every single file as returned from `ts.parseJsonConfigFileContent`. However, it is called with `noEmit: true`. This prevents any Javascript and Typescript declaration files from being written. +At step 4, `ts.createProgram` is called for the first time. It is called with every single file as returned from `ts.parseJsonConfigFileContent`. However, it is called with `noEmit: true`. This prevents any Javascript and Typescript declaration files from being written. This is important because we want test files to be type checked, but we don't want them to be compiled into Javascript and exported with the rest of the code. If they were included and the `tsconfig` was configured to produce outputs, the test files would end up being written to the `outDir`. `typecheckProgram.emit` is called to perform the type checking. diff --git a/docs/src/lib/dev/index.md b/docs/src/lib/dev/index.md index fc6c6fb9e2..2a1d87e07b 100644 --- a/docs/src/lib/dev/index.md +++ b/docs/src/lib/dev/index.md @@ -3,11 +3,11 @@ title: Modules Developer Docs ---

    Bundles Developer Documentation

    -

    This an the index page for bundles that have documentation for developers working them

    +

    This an the index page for bundles that have documentation for developers working with them