diff --git a/.github/workflows/companion.yml b/.github/workflows/companion.yml index ee8969d06b0..7e8837cab1d 100644 --- a/.github/workflows/companion.yml +++ b/.github/workflows/companion.yml @@ -8,13 +8,17 @@ on: - v* paths: - '.github/workflows/companion.yml' + - '.github/workflows/wasm_modules.yml' - '.github/actions/python_dependencies/**' - '.github/actions/wasi_sdk/**' - '.github/actions/build_companion/**' - 'companion/**' + - 'fw.json' - 'pyproject.toml' - 'tools/build-companion.sh' - 'tools/build-wasm-modules.sh' + - 'radio/src/targets/simu/CMakeLists.txt' + - 'radio/src/targets/simu/pch_std.h' - 'cmake/wasm/CMakeLists.txt' - 'cmake/FetchWamr.cmake' - 'cmake/wamr-*.patch' @@ -22,13 +26,17 @@ on: pull_request: paths: - '.github/workflows/companion.yml' + - '.github/workflows/wasm_modules.yml' - '.github/actions/python_dependencies/**' - '.github/actions/wasi_sdk/**' - '.github/actions/build_companion/**' - 'companion/**' + - 'fw.json' - 'pyproject.toml' - 'tools/build-companion.sh' - 'tools/build-wasm-modules.sh' + - 'radio/src/targets/simu/CMakeLists.txt' + - 'radio/src/targets/simu/pch_std.h' - 'cmake/wasm/CMakeLists.txt' - 'cmake/FetchWamr.cmake' - 'cmake/wamr-*.patch' @@ -49,58 +57,8 @@ on: jobs: modules: - name: WASM Modules (${{ matrix.group.id }}) - runs-on: macos-15 - strategy: - matrix: - group: - - id: color-1 - target: [nv14, el18, pl18, pl18ev] - - id: color-2 - target: [pl18u, t15, t16, t18, t22] - - id: bw-1 - target: [zorro, pocket, commando8, tprov2, tpros, bumblebee, t14] - - id: bw-2 - target: [mt12, gx12, t20, t20v2, t12max, tx12mk2, boxer] - - id: color-3 - target: [tx15, tx16s, x12s, f16] - - id: mixed-1 - target: [v12, v14, v14lcd, v15, v16, x10, x10express] - - id: bw-3 - target: [x7access, x9dp2019, x9e] - - id: color-4 - target: [nb4p, st16, pa01, c14] - - id: color-5 - target: [t15pro, tx16smk3, gx15, h17, t15h7] - - steps: - - name: Check out the repo - uses: actions/checkout@v7 - with: - submodules: true - - - name: Setup Python Dependencies - uses: ./.github/actions/python_dependencies - - - name: Setup WASI SDK - uses: ./.github/actions/wasi_sdk - - - name: Build modules - shell: bash - env: - FLAVOR: ${{ join(matrix.group.target, ';') }} - CMAKE_BUILD_TYPE: 'Release' - run: | - mkdir output && \ - tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/" - - - name: Archive production artifacts - uses: actions/upload-artifact@v7 - with: - name: wasm-modules-${{ matrix.group.id }} - path: ${{github.workspace}}/output - if-no-files-found: error - retention-days: 15 + name: Modules + uses: ./.github/workflows/wasm_modules.yml build-linux: name: Linux Companion diff --git a/.github/workflows/validate_includes.yml b/.github/workflows/validate_includes.yml new file mode 100644 index 00000000000..2e79c285afd --- /dev/null +++ b/.github/workflows/validate_includes.yml @@ -0,0 +1,24 @@ +--- +name: Validate includes +on: + push: + branches: + - 'main' + pull_request: + branches: + - 'main' + workflow_dispatch: + +# No 'paths' filter: any source edit can add an include. Pure stdlib, runs in +# about a second. + +jobs: + validate: + name: Includes + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Check that edgetx.h does not spread + run: python3 tools/check_includes.py diff --git a/.github/workflows/wasm_modules.yml b/.github/workflows/wasm_modules.yml new file mode 100644 index 00000000000..ac91c39e0c0 --- /dev/null +++ b/.github/workflows/wasm_modules.yml @@ -0,0 +1,72 @@ +name: WASM Modules + +# Builds one simulator module per target in fw.json and uploads them as +# wasm-modules-* artifacts. Shared by the Companion and web simulator workflows. +on: + workflow_call: + +env: + # Each group takes one runner + GROUPS_COUNT: 9 + +jobs: + plan: + name: Plan WASM module groups + runs-on: ubuntu-latest + outputs: + groups: ${{ steps.groups.outputs.groups }} + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Split fw.json targets into groups + id: groups + shell: bash + run: | + groups=$(jq -c --argjson n "${GROUPS_COUNT}" ' + [.targets[][1] | rtrimstr("-")] as $t + | [range($n) as $i + | {id: "group-\($i + 1)", + target: [range($i; $t | length; $n) as $j | $t[$j]]}] + | map(select(.target | length > 0)) + ' fw.json) + echo "groups=${groups}" >> "$GITHUB_OUTPUT" + + modules: + name: WASM Modules (${{ matrix.group.id }}) + needs: plan + runs-on: ubuntu-latest + strategy: + matrix: + group: ${{ fromJSON(needs.plan.outputs.groups) }} + + steps: + - name: Check out the repo + uses: actions/checkout@v7 + with: + submodules: true + + - name: Setup Python Dependencies + uses: ./.github/actions/python_dependencies + + - name: Setup WASI SDK + uses: ./.github/actions/wasi_sdk + with: + arch: x86_64-linux + + - name: Build modules + shell: bash + env: + FLAVOR: ${{ join(matrix.group.target, ';') }} + CMAKE_BUILD_TYPE: 'Release' + run: | + mkdir output && \ + tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/" + + - name: Archive production artifacts + uses: actions/upload-artifact@v7 + with: + name: wasm-modules-${{ matrix.group.id }} + path: ${{github.workspace}}/output + if-no-files-found: error + retention-days: 15 diff --git a/.github/workflows/web_simulator.yml b/.github/workflows/web_simulator.yml new file mode 100644 index 00000000000..dc947e0b749 --- /dev/null +++ b/.github/workflows/web_simulator.yml @@ -0,0 +1,156 @@ +name: Web Simulator + +on: + schedule: + - cron: '0 3 * * *' # run at 3 AM UTC + pull_request: + paths: + - '.github/workflows/web_simulator.yml' + - 'web/**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + NODE_VERSION: '22' + +jobs: + check: + name: Check web build + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Build + working-directory: web + run: | + npm ci + npm run build + + modules: + name: Modules + # Repositories synced from this one get the schedule too + if: github.event_name != 'pull_request' && github.repository == 'EdgeTX/edgetx' + uses: ./.github/workflows/wasm_modules.yml + + build: + name: Build site + needs: modules + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v7 + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Download WASM modules + uses: actions/download-artifact@v8 + with: + pattern: wasm-modules-* + path: wasm-modules + merge-multiple: true + + # The artifacts also contain build logs, which must not be published + - name: Copy WASM modules + run: cp wasm-modules/*.wasm web/public/ + + - name: Check that every radio has a module + shell: bash + run: | + missing=0 + while read -r module; do + if [[ ! -f "web/public/${module}" ]]; then + echo "::error::Missing module ${module}" + missing=1 + fi + done < <(jq -r '.[].wasm' web/public/radios.json) + exit "${missing}" + + - name: Build + working-directory: web + run: | + npm ci + npm run build + + # Before the deploy job, which only runs if this one succeeds + - name: Start every radio + working-directory: web + run: | + npx playwright-core install --with-deps chromium + npm run smoke-test + + - name: Archive site + uses: actions/upload-artifact@v7 + with: + name: web-simulator-dist + path: web/dist + if-no-files-found: error + retention-days: 3 + + # Separate from the build, so that the Cloudflare token is only available to + # wrangler. The environment only accepts runs from the deployed branches. + deploy: + name: Deploy site + needs: build + runs-on: ubuntu-latest + environment: web-simulator + steps: + - name: Check out the repo + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install wrangler + working-directory: web + run: npm ci --ignore-scripts + + - name: Download site + uses: actions/download-artifact@v8 + with: + name: web-simulator-dist + path: web/dist + + - name: Deploy + id: deploy + working-directory: web + shell: bash + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: | + npx wrangler deploy | tee "${RUNNER_TEMP}/deploy.log" + url=$(grep -oE 'https://[^ ]+\.workers\.dev' "${RUNNER_TEMP}/deploy.log" | head -n 1) + if [[ -z "${url}" ]]; then + echo "::error::Could not find the deployed URL in the wrangler output" + exit 1 + fi + echo "url=${url}" >> "$GITHUB_OUTPUT" + + - name: Check deployed site + env: + SITE_URL: ${{ steps.deploy.outputs.url }} + run: node web/scripts/check-deployment.js "${SITE_URL}" diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 09986227741..0b21b7d7d08 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -136,10 +136,19 @@ endif() ############# Import radio hardware definitions ############### set(HWDEFS_DIR "${CMAKE_SOURCE_DIR}/radio/src/boards/hw_defs") -set(HWDEFS_TMPL "${COMPANION_SRC_DIRECTORY}/hwdefs.qrc.in") -set(HWDEFS_PHDR "HWDEF_JSON_LIST") set(HWDEFS_QRC "${CMAKE_CURRENT_BINARY_DIR}/hwdefs.qrc") -set(HWDEFS_CMD "${COMPANION_SRC_DIRECTORY}/../util/generate_hwdefs_qrc.py") + +# CONFIGURE_DEPENDS re-runs the glob on each build, so adding or removing +# a definition regenerates the qrc in an existing build folder +file(GLOB HWDEF_JSON_FILES CONFIGURE_DEPENDS "${HWDEFS_DIR}/*.json") +list(SORT HWDEF_JSON_FILES) +set(HWDEF_JSON_ENTRIES "") +foreach(json_file ${HWDEF_JSON_FILES}) + get_filename_component(json_file_name ${json_file} NAME) + list(APPEND HWDEF_JSON_ENTRIES " ${json_file}") +endforeach() +list(JOIN HWDEF_JSON_ENTRIES "\n" HWDEF_JSON_LIST) +configure_file(${COMPANION_SRC_DIRECTORY}/hwdefs.qrc.in ${HWDEFS_QRC} @ONLY) ############# Common lib ############### @@ -158,16 +167,6 @@ set(common_MOC_HDRS qt_wrap_cpp(common_SRCS ${common_MOC_HDRS}) -# qrc used by Companion and Simulator -add_custom_command(OUTPUT ${HWDEFS_QRC} - COMMAND ${PYTHON_EXECUTABLE} ${HWDEFS_CMD} - -d ${HWDEFS_DIR} - -t ${HWDEFS_TMPL} - -p ${HWDEFS_PHDR} - -o ${HWDEFS_QRC} - DEPENDS ${HWDEFS_TMPL} -) - set(common_RESOURCES companion.qrc ${TRANSLATIONS_QRC} diff --git a/companion/src/firmwares/boardjson.cpp b/companion/src/firmwares/boardjson.cpp index a0327585725..4eedfaf0af8 100644 --- a/companion/src/firmwares/boardjson.cpp +++ b/companion/src/firmwares/boardjson.cpp @@ -238,6 +238,9 @@ const int BoardJson::getCapability(const Board::Capability capability) const case Board::StatusLedColors: return m_hardware.status_led_colors; + case Board::HasTrainerJackCrossover: + return m_hardware.trainer_jack_crossover; + case Board::HasStatusLedPwm: return m_hardware.status_led_pwm; @@ -1342,6 +1345,12 @@ bool BoardJson::loadFile(Board::Type board, QString hwdefn, InputsTable * inputs hardware.cpu_type = o.value("cpu_type").toString().toStdString(); } + if (obj.value("trainer").isObject()) { + const QJsonObject &o = obj.value("trainer").toObject(); + + hardware.trainer_jack_crossover = o.value("trainer_jack_crossover").toBool(); + } + if (obj.value("identity").isObject()) { const QJsonObject &o = obj.value("identity").toObject(); diff --git a/companion/src/firmwares/boardjson.h b/companion/src/firmwares/boardjson.h index 55535c345cc..0677334a7d3 100644 --- a/companion/src/firmwares/boardjson.h +++ b/companion/src/firmwares/boardjson.h @@ -133,6 +133,7 @@ class BoardJson int has_int_module_support = 0; int sport_max_baudrate = 0; int surface = 0; + int trainer_jack_crossover = 0; std::string cpu = ""; std::string cpu_type = ""; }; diff --git a/companion/src/firmwares/boards.h b/companion/src/firmwares/boards.h index c5fb0921ae5..3a341f13a94 100644 --- a/companion/src/firmwares/boards.h +++ b/companion/src/firmwares/boards.h @@ -254,6 +254,7 @@ namespace Board { HasRTC, HasSDCard, HasSwitchableJack, + HasTrainerJackCrossover, HasTrainerModuleCPPM, HasTrainerModuleSBUS, HasVBat, diff --git a/companion/src/firmwares/edgetx/yaml_generalsettings.cpp b/companion/src/firmwares/edgetx/yaml_generalsettings.cpp index 60e1c7f1e78..23beb578dc4 100644 --- a/companion/src/firmwares/edgetx/yaml_generalsettings.cpp +++ b/companion/src/firmwares/edgetx/yaml_generalsettings.cpp @@ -263,6 +263,7 @@ Node convert::encode(const GeneralSettings& rhs) node["disableTrainerPoweroffAlarm"] = (int)rhs.disableTrainerPoweroffAlarm; node["USBMode"] = rhs.usbMode; node["usbChargeDisabled"] = (int)rhs.usbChargeDisabled; + node["trainerTipOnly"] = rhs.trainerTipOnly; node["hatsMode"] = hatsModeLut << rhs.hatsMode; node["stickDeadZone"] = rhs.stickDeadZone; node["jackMode"] = rhs.jackMode; @@ -558,6 +559,7 @@ bool convert::decode(const Node& node, GeneralSettings& rhs) node["disableTrainerPoweroffAlarm"] >> rhs.disableTrainerPoweroffAlarm; node["USBMode"] >> rhs.usbMode; node["usbChargeDisabled"] >> rhs.usbChargeDisabled; + node["trainerTipOnly"] >> rhs.trainerTipOnly; node["hatsMode"] >> hatsModeLut >> rhs.hatsMode; node["stickDeadZone"] >> rhs.stickDeadZone; node["jackMode"] >> rhs.jackMode; diff --git a/companion/src/firmwares/generalsettings.h b/companion/src/firmwares/generalsettings.h index 4c0f34ddb91..dcb2cee69af 100644 --- a/companion/src/firmwares/generalsettings.h +++ b/companion/src/firmwares/generalsettings.h @@ -319,6 +319,7 @@ class GeneralSettings { bool disableTrainerPoweroffAlarm; unsigned int usbMode; bool usbChargeDisabled; + unsigned int trainerTipOnly; unsigned int hatsMode; unsigned int stickDeadZone; unsigned int jackMode; diff --git a/companion/src/generaledit/hardware.cpp b/companion/src/generaledit/hardware.cpp index d69f9dc4fc5..ec906689d3b 100644 --- a/companion/src/generaledit/hardware.cpp +++ b/companion/src/generaledit/hardware.cpp @@ -225,6 +225,16 @@ HardwarePanel::HardwarePanel(QWidget * parent, GeneralSettings & generalSettings addParams(); } + if (Boards::getCapability(board, Board::HasTrainerJackCrossover)) { + addLabel(tr("Trainer lead")); + AutoComboBox *trainerLead = new AutoComboBox(this); + trainerLead->addItem(tr("Crossover"), 0); + trainerLead->addItem(tr("Standard"), 1); + trainerLead->setField(generalSettings.trainerTipOnly, this); + params->append(trainerLead); + addParams(); + } + if (firmware->getCapability(BluetoothEnabled)) { addLabel(tr("Bluetooth")); diff --git a/companion/src/simulation/wasmsimulatorinterface.cpp b/companion/src/simulation/wasmsimulatorinterface.cpp index d9081741e50..44746a2cfe5 100644 --- a/companion/src/simulation/wasmsimulatorinterface.cpp +++ b/companion/src/simulation/wasmsimulatorinterface.cpp @@ -444,9 +444,11 @@ void WasmSimulatorInterface::init() m_stopRequested = false; + for (auto & value : m_analogValues) + value.store(0, std::memory_order_relaxed); + // Call simuInit QMutexLocker lckr(&m_mutex); - memset(m_analogValues, 0, sizeof(m_analogValues)); wasm_runtime_call_wasm(m_execEnv, m_fnInit, 0, nullptr); // Query LCD dimensions @@ -584,13 +586,16 @@ void WasmSimulatorInterface::stop() return; m_stopRequested = true; - deinitAudio(); { QMutexLocker lckr(&m_mutex); wasm_runtime_call_wasm(m_execEnv, m_fnStop, 0, nullptr); } + // Close audio only once simuStop() has joined the firmware threads, so the + // audio task cannot be inside SDL_QueueAudio() while the device closes. + deinitAudio(); + // Fully destroy the WASM module so the next init()/start() gets a clean // instance. WASI threads created by the firmware's RTOS layer cannot be // reliably restarted within the same module instance. @@ -614,16 +619,15 @@ void WasmSimulatorInterface::setVolumeGain(const int value) void WasmSimulatorInterface::setAnalogValue(uint8_t index, int16_t value) { - QMutexLocker lckr(&m_mutex); if (index < MAX_ANALOGS) - m_analogValues[index] = value; + m_analogValues[index].store(value, std::memory_order_relaxed); } +// Called from firmware threads; must not take m_mutex (see header). int16_t WasmSimulatorInterface::getAnalogValue(uint8_t index) { - QMutexLocker lckr(&m_mutex); if (index < MAX_ANALOGS) - return m_analogValues[index]; + return m_analogValues[index].load(std::memory_order_relaxed); return 0; } diff --git a/companion/src/simulation/wasmsimulatorinterface.h b/companion/src/simulation/wasmsimulatorinterface.h index 6daebe8918f..1d8596f6606 100644 --- a/companion/src/simulation/wasmsimulatorinterface.h +++ b/companion/src/simulation/wasmsimulatorinterface.h @@ -126,7 +126,9 @@ class WasmSimulatorInterface : public SimulatorInterface // Host-side analog values (polled by WASM via simuGetAnalog) static constexpr int MAX_ANALOGS = 32; - int16_t m_analogValues[MAX_ANALOGS] = {}; + // Read by firmware threads through host_simuGetAnalog, so kept out of + // m_mutex: stop() holds that across simuStop(), which joins those threads. + std::atomic m_analogValues[MAX_ANALOGS] = {}; // Cached output values for change detection TxOutputs m_lastOutputs; diff --git a/companion/targets/linux/CPackLinuxDeploy.cmake.in b/companion/targets/linux/CPackLinuxDeploy.cmake.in index 0a1e115d32f..2df9f7e27b8 100644 --- a/companion/targets/linux/CPackLinuxDeploy.cmake.in +++ b/companion/targets/linux/CPackLinuxDeploy.cmake.in @@ -1,6 +1,12 @@ execute_process(COMMAND @CMAKE_MAKE_PROGRAM@ DESTDIR=@APPIMAGE_DIR@ install WORKING_DIRECTORY @CMAKE_BINARY_DIR@) +# appimagetool creates .DirIcon from a PNG or XPM at the top of the AppDir, and +# fails without one. linuxdeploy only puts the scalable SVG there. +file(COPY_FILE + "@APPIMAGE_DIR@@CMAKE_INSTALL_PREFIX@/share/icons/hicolor/256x256/apps/@COMPANION_NAME@.png" + "@APPIMAGE_DIR@/@COMPANION_NAME@.png") + # linuxdeploy-plugin-qt only bundles the xcb platform plugin by default, so also # bundle the Wayland platform plugins and their shell/decoration integrations. # Set via ENV as the lists contain ';' which execute_process would split. diff --git a/companion/util/generate_hwdefs_qrc.py b/companion/util/generate_hwdefs_qrc.py deleted file mode 100755 index 7d9085430e3..00000000000 --- a/companion/util/generate_hwdefs_qrc.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import glob -from os import path - -def main(jsondir, template, placeholder, output): - if not path.isdir(jsondir): - raise Exception(jsondir + " is not a directory") - - try: - tmplt = open(template, "r") - except: - raise Exception("Unable to open template file " + template) - - try: - qrc = open(output, "w") - except: - raise Exception("Unable to open " + output + " for writing") - - line = tmplt.readline() - - while placeholder not in line and line != '': - qrc.write(line) - line = tmplt.readline() - - for f in sorted(glob.iglob(path.join(jsondir, '*.json'), recursive = False)): - fname = path.basename(f) - qrc.write(' ' + f + '' + '\n') - - line = tmplt.readline() - - while line != '': - qrc.write(line) - line = tmplt.readline() - - tmplt.close() - qrc.close() - - -if __name__ == "__main__": - - parser = argparse.ArgumentParser(description='Generate hardware definitions resource file') - parser.add_argument('-d', metavar='json files directory', required=True) - parser.add_argument('-t', metavar='template file', required=True) - parser.add_argument('-p', metavar='placeholder', required=True) - parser.add_argument('-o', metavar='qrc file', required=True) - - args = parser.parse_args() - - main(args.d, args.t, args.p, args.o) diff --git a/docs/development/header-guidelines.md b/docs/development/header-guidelines.md new file mode 100644 index 00000000000..84c4ea7b650 --- /dev/null +++ b/docs/development/header-guidelines.md @@ -0,0 +1,73 @@ +# Header Guidelines + +A trivial file like `maths.cpp` used to expand to more than 54,000 lines, because `edgetx.h` includes nearly everything. This has three costs: + +- **Compile time:** about 84% of compile time is spent in the front end, mostly parsing headers. +- **Rebuilds:** a change to `edgetx.h`, or to anything it includes, rebuilds most of the tree. +- **Coupling:** code can reach anything without saying so, which blurs the separation between hardware and software. + +These rules apply to new code. Existing code moves towards them gradually, in small pull requests. + +## Rules + +### 1. Depend downwards only + +From lowest to highest: + +1. `os` +2. `hal`, `drivers` +3. `boards`, `targets` +4. Core: data model, mixer, telemetry, storage +5. `gui` + +A layer only includes headers from its own level or below. In particular, the data model must not include GUI headers. + +### 2. Do not include `edgetx.h` in headers or hardware code + +Headers and everything under `os`, `hal`, `drivers`, `boards` and `targets` must not include `edgetx.h`. `tools/check_includes.py` enforces this in CI. + +Other new files should avoid it too. If a file only needs a few declarations, move them to a small header, as `maths.h` does. + +### 3. Include what you use + +Each file includes the headers for what it uses directly, and does not rely on getting them through another header. + +### 4. Declare unconditionally + +Use `#if` around definitions and struct layouts, not function declarations. A conditional declaration makes every file that sees it differ between radios, even when the file never calls the function, which prevents reusing compiled output. + +```cpp +// Avoid +#if defined(FUNCTION_SWITCHES) +void evalFunctionSwitches(); +#endif + +// Prefer +void evalFunctionSwitches(); +``` + +### 5. Keep widely included headers light + +In a header included by many files: + +- forward-declare types used only by pointer or reference; +- avoid ``, ``, `` and similar if they can stay in the `.cpp`. + +### 6. Do not add global `-D` flags + +Every file sees a global define. For a new option, use a config header that only the files concerned include, or runtime data from the hardware definitions. + +## The `edgetx.h` baseline + +`tools/check_includes_baseline.txt` lists the headers and hardware files that still include `edgetx.h`. It can only shrink: + +- a new include in a header or in hardware code fails the check; +- once a file is fixed, its entry must be removed. `python3 tools/check_includes.py --update` does this. + +Do not use `--update` to add entries. + +## Precompiled standard library headers + +WASM builds precompile the headers in `radio/src/targets/simu/pch_std.h`, and force-include them in every C++ file. A file that forgets `#include ` can therefore still compile for WASM, but the firmware build will catch it. + +Only add standard library headers to that file. `USE_PCH=OFF` turns it off. diff --git a/mkdocs.yml b/mkdocs.yml index 99d4a8d409a..035142afbbf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -98,6 +98,7 @@ nav: - External Module Protocols: development/external-module-protocols.md - YAML Parser/Generator: development/yaml-parser-generator.md - Mixer Synchronisation: development/mixer-synchronisation.md + - Header Guidelines: development/header-guidelines.md exclude_docs: | README.md diff --git a/radio/src/boards/generic_stm32/trainer_ports.cpp b/radio/src/boards/generic_stm32/trainer_ports.cpp index c0373921f4a..07eb10c1060 100644 --- a/radio/src/boards/generic_stm32/trainer_ports.cpp +++ b/radio/src/boards/generic_stm32/trainer_ports.cpp @@ -29,12 +29,13 @@ #include "dataconstants.h" #include "hal.h" +#include "edgetx.h" __WEAK void board_trainer_init() { #if defined(TRAINER_DETECT_GPIO) gpio_init(TRAINER_DETECT_GPIO, GPIO_IN_PU, GPIO_PIN_SPEED_LOW); -#endif +#endif trainer_init(); } @@ -63,11 +64,6 @@ static const stm32_pulse_timer_t trainerOutputTimer = { .DMA_TC_CallbackPtr = nullptr, }; -void trainer_init_dsc_out() -{ - trainer_init_output(&trainerOutputTimer); -} - static const stm32_pulse_timer_t trainerInputTimer = { .GPIO = TRAINER_IN_GPIO, .GPIO_Alternate = TRAINER_GPIO_AF, @@ -82,6 +78,25 @@ static const stm32_pulse_timer_t trainerInputTimer = { .DMA_TC_CallbackPtr = nullptr, }; +#if defined(TRAINER_JACK_CROSSOVER) +// Trainer in is on the jack tip and out on the ring, for the crossover lead +// the radio ships with. A mono lead grounds the ring, so optionally send on +// the tip (the input pin) as well. +static_assert(__IS_TRAINER_TIMER_OUT_CHANNEL_SUPPORTED(TRAINER_IN_TIMER_Channel), + "Unsupported trainer timer input channel for tip-only output"); +#endif + +void trainer_init_dsc_out() +{ +#if defined(TRAINER_JACK_CROSSOVER) + if (g_eeGeneral.trainerTipOnly) { + trainer_init_output(&trainerInputTimer); + return; + } +#endif + trainer_init_output(&trainerOutputTimer); +} + void trainer_init_dsc_in() { trainer_init_capture(&trainerInputTimer); diff --git a/radio/src/boards/hw_defs/el18.json b/radio/src/boards/hw_defs/el18.json index 1001ca99c3c..b30b87fd22f 100644 --- a/radio/src/boards/hw_defs/el18.json +++ b/radio/src/boards/hw_defs/el18.json @@ -404,6 +404,7 @@ "trainer_timer_irqn": "TIM4_IRQn", "trainer_timer_irqhandler": "TIM4_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_2", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/nb4p.json b/radio/src/boards/hw_defs/nb4p.json index 09154e6d46c..37192a06563 100644 --- a/radio/src/boards/hw_defs/nb4p.json +++ b/radio/src/boards/hw_defs/nb4p.json @@ -267,6 +267,7 @@ "trainer_timer_irqn": "TIM4_IRQn", "trainer_timer_irqhandler": "TIM4_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_2", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/nv14.json b/radio/src/boards/hw_defs/nv14.json index 045f71ba681..706c95dd323 100644 --- a/radio/src/boards/hw_defs/nv14.json +++ b/radio/src/boards/hw_defs/nv14.json @@ -404,6 +404,7 @@ "trainer_timer_irqn": "TIM4_IRQn", "trainer_timer_irqhandler": "TIM4_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_2", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/pa01.json b/radio/src/boards/hw_defs/pa01.json index 1c04a5c14cd..0f763794af6 100644 --- a/radio/src/boards/hw_defs/pa01.json +++ b/radio/src/boards/hw_defs/pa01.json @@ -392,6 +392,7 @@ "trainer_timer_irqn": "TIM1_CC_IRQn", "trainer_timer_irqhandler": "TIM1_CC_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_1", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/pl18.json b/radio/src/boards/hw_defs/pl18.json index da43303620a..bab36edf8b4 100644 --- a/radio/src/boards/hw_defs/pl18.json +++ b/radio/src/boards/hw_defs/pl18.json @@ -390,6 +390,7 @@ "trainer_timer_irqn": "TIM4_IRQn", "trainer_timer_irqhandler": "TIM4_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_2", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/pl18ev.json b/radio/src/boards/hw_defs/pl18ev.json index 0be17d570e7..b4ea1e0c1e0 100644 --- a/radio/src/boards/hw_defs/pl18ev.json +++ b/radio/src/boards/hw_defs/pl18ev.json @@ -434,6 +434,7 @@ "trainer_timer_irqn": "TIM4_IRQn", "trainer_timer_irqhandler": "TIM4_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_2", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/pl18u.json b/radio/src/boards/hw_defs/pl18u.json index 171ef41e0b7..579ef426393 100644 --- a/radio/src/boards/hw_defs/pl18u.json +++ b/radio/src/boards/hw_defs/pl18u.json @@ -407,6 +407,7 @@ "trainer_timer_irqn": "TIM4_IRQn", "trainer_timer_irqhandler": "TIM4_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_2", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true } } diff --git a/radio/src/boards/hw_defs/st16.json b/radio/src/boards/hw_defs/st16.json index 7b31d781175..ad078c9adc9 100644 --- a/radio/src/boards/hw_defs/st16.json +++ b/radio/src/boards/hw_defs/st16.json @@ -511,15 +511,16 @@ "usb_gpio_af": "GPIO_AF10" }, "trainer": { - "trainer_in_gpio": "GPIO_PIN(GPIOE, 5)", - "trainer_in_timer_channel": "LL_TIM_CHANNEL_CH1", - "trainer_out_gpio": "GPIO_PIN(GPIOE, 6)", - "trainer_out_timer_channel": "LL_TIM_CHANNEL_CH2", + "trainer_in_gpio": "GPIO_PIN(GPIOE, 6)", + "trainer_in_timer_channel": "LL_TIM_CHANNEL_CH2", + "trainer_out_gpio": "GPIO_PIN(GPIOE, 5)", + "trainer_out_timer_channel": "LL_TIM_CHANNEL_CH1", "trainer_timer": "TIM15", "trainer_timer_irqn": "TIM15_IRQn", "trainer_timer_irqhandler": "TIM15_IRQHandler", "trainer_gpio_af": "LL_GPIO_AF_4", - "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)" + "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)", + "trainer_jack_crossover": true }, "key_lock_combo": [ "KEY_EXIT", diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index 18c9ca68c1e..6a15235c8f7 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -1219,13 +1219,16 @@ PACK(struct RadioData { // Radio level tabs control (global settings) NOBACKUP(uint8_t modelSelectLayout:2); NOBACKUP(uint8_t radioThemesDisabled:1); + // 0 = trainer in/out on their own jack contacts (crossover lead, default), + // 1 = both on the tip (mono / straight lead). TRAINER_JACK_CROSSOVER only + NOBACKUP(uint8_t trainerTipOnly:1); #if defined(USB_CHARGE_CONTROL) // 0 = charge while USB active (default), 1 = hold the charger off while USB // is plugged in SD/Joystick/VCP mode NOBACKUP(uint8_t usbChargeDisabled:1); - NOBACKUP(uint8_t spare:6 SKIP); + NOBACKUP(uint8_t spare:5 SKIP); #else - NOBACKUP(uint8_t spare:7 SKIP); + NOBACKUP(uint8_t spare:6 SKIP); #endif #elif LCD_W == 128 uint8_t invertLCD:1; // Invert B&W LCD display diff --git a/radio/src/edgetx.h b/radio/src/edgetx.h index 33b4c4d407c..f7038665aa8 100644 --- a/radio/src/edgetx.h +++ b/radio/src/edgetx.h @@ -353,15 +353,13 @@ uint8_t statusLedPhaseColor(uint8_t phase); bool statusLedColorAvailable(int color); #endif -uint16_t isqrt32(uint32_t n); +#include "maths.h" void setDefaultOwnerId(); void generalDefault(); void generalDefaultSwitches(); void generalDefaultUILanguage(); -uint32_t hash(const void * ptr, uint32_t size); - #define calc100to256_16Bits(x) calc100to256(x) #define calc100toRESX_16Bits(x) calc100toRESX(x) diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp index a44f9e4ac66..0654513ea47 100644 --- a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp +++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp @@ -430,7 +430,7 @@ void etx_solid_bg(lv_obj_t* obj, LcdColorIndex bg_color, void etx_font(lv_obj_t* obj, FontIndex fontIdx, lv_style_selector_t selector) { // Remove old style first - for (int i = 0; i < TOTAL_COLOR_COUNT; i += 1) + for (int i = 0; i < FONTS_COUNT; i += 1) lv_obj_remove_style(obj, &styles->font[i], selector); etx_obj_add_style(obj, styles->font[fontIdx], selector); } diff --git a/radio/src/gui/colorlcd/radio/radio_hardware.cpp b/radio/src/gui/colorlcd/radio/radio_hardware.cpp index fc57e4c3af6..e941fb7f173 100644 --- a/radio/src/gui/colorlcd/radio/radio_hardware.cpp +++ b/radio/src/gui/colorlcd/radio/radio_hardware.cpp @@ -22,6 +22,7 @@ #include "radio_hardware.h" #include "edgetx.h" +#include "choice.h" #include "getset_helpers.h" #include "hal/adc_driver.h" #include "hw_extmodule.h" @@ -165,6 +166,21 @@ const static SetupLineDef setupLines[] = { new ToggleSwitch(parent, {x, y, 0, 0}, GET_SET_DEFAULT(g_eeGeneral.audioMuteEnable)); } }, +#endif +#if defined(TRAINER_JACK_CROSSOVER) + { + // Trainer jack wiring: crossover lead vs mono / straight lead + STR_DEF(STR_TRAINER_JACK_MODE), + [](Window* parent, coord_t x, coord_t y) { + new Choice(parent, {x, y, 0, 0}, STR_TRAINER_JACK_MODES, 0, 1, + GET_DEFAULT(g_eeGeneral.trainerTipOnly), [=](int32_t newValue) { + g_eeGeneral.trainerTipOnly = newValue; + SET_DIRTY(); + // restarted on the new contacts by checkTrainerSettings() + stopTrainer(); + }); + } + }, #endif {nullptr, nullptr}, }; diff --git a/radio/src/maths.cpp b/radio/src/maths.cpp index 592dd1c1873..b171fbd5ff9 100644 --- a/radio/src/maths.cpp +++ b/radio/src/maths.cpp @@ -19,7 +19,7 @@ * GNU General Public License for more details. */ -#include "edgetx.h" +#include "maths.h" uint16_t isqrt32(uint32_t n) { diff --git a/radio/src/maths.h b/radio/src/maths.h new file mode 100644 index 00000000000..779433f7da2 --- /dev/null +++ b/radio/src/maths.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include + +uint16_t isqrt32(uint32_t n); +uint32_t hash(const void * ptr, uint32_t size); diff --git a/radio/src/storage/yaml/yaml_datastructs_c14.cpp b/radio/src/storage/yaml/yaml_datastructs_c14.cpp index 81079dac127..7a12f97092c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_c14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_c14.cpp @@ -474,7 +474,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_f16.cpp b/radio/src/storage/yaml/yaml_datastructs_f16.cpp index deca61c1a1b..1dbd5d91bca 100644 --- a/radio/src/storage/yaml/yaml_datastructs_f16.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_f16.cpp @@ -475,7 +475,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_gx15.cpp b/radio/src/storage/yaml/yaml_datastructs_gx15.cpp index 5373309f211..2983abac9ea 100644 --- a/radio/src/storage/yaml/yaml_datastructs_gx15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_gx15.cpp @@ -498,8 +498,9 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), YAML_UNSIGNED( "usbChargeDisabled", 1 ), - YAML_PADDING( 6 ), + YAML_PADDING( 5 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_h17.cpp b/radio/src/storage/yaml/yaml_datastructs_h17.cpp index 7cad23b569c..de5cb26d87d 100644 --- a/radio/src/storage/yaml/yaml_datastructs_h17.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_h17.cpp @@ -469,7 +469,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp b/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp index 41e83bb5be6..03e807226be 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp @@ -465,7 +465,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp index 06b9f866951..36672f520b7 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp @@ -465,7 +465,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ARRAY("keyShortcuts", 8, 6, struct_KeyShortcut, NULL), YAML_ARRAY("qmFavorites", 8, 12, struct_QMFavorite, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_pa01.cpp b/radio/src/storage/yaml/yaml_datastructs_pa01.cpp index 96643126a95..4f0ac605118 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pa01.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pa01.cpp @@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index e41b4810019..dcbe900758b 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -472,7 +472,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp index cfddee0632a..2def7ffbe9a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp @@ -465,7 +465,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_st16.cpp b/radio/src/storage/yaml/yaml_datastructs_st16.cpp index eca99be6f0d..d1536f51f6d 100644 --- a/radio/src/storage/yaml/yaml_datastructs_st16.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_st16.cpp @@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t15.cpp b/radio/src/storage/yaml/yaml_datastructs_t15.cpp index 8e25a6ef249..1579cbf38ce 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t15.cpp @@ -483,7 +483,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp b/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp index efecdd65629..7513972850a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp @@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp b/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp index efecdd65629..7513972850a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp @@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp b/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp index 38114b7a477..ab639771abc 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp @@ -493,7 +493,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_t22.cpp b/radio/src/storage/yaml/yaml_datastructs_t22.cpp index efecdd65629..7513972850a 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t22.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t22.cpp @@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_tx15.cpp b/radio/src/storage/yaml/yaml_datastructs_tx15.cpp index 5373309f211..2983abac9ea 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tx15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tx15.cpp @@ -498,8 +498,9 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), YAML_UNSIGNED( "usbChargeDisabled", 1 ), - YAML_PADDING( 6 ), + YAML_PADDING( 5 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp b/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp index 88ee740d5b7..e62517f3eaa 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp @@ -499,8 +499,9 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), YAML_UNSIGNED( "usbChargeDisabled", 1 ), - YAML_PADDING( 6 ), + YAML_PADDING( 5 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_v12.cpp b/radio/src/storage/yaml/yaml_datastructs_v12.cpp index 3c886a9d22a..64e63fe2f59 100644 --- a/radio/src/storage/yaml/yaml_datastructs_v12.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_v12.cpp @@ -493,7 +493,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_v15.cpp b/radio/src/storage/yaml/yaml_datastructs_v15.cpp index 192f60d3926..4dd823e075c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_v15.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_v15.cpp @@ -500,7 +500,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp index f3c773b8e0b..920a8cf6a40 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp @@ -474,7 +474,8 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "favMultiMode", 1 ), YAML_UNSIGNED( "modelSelectLayout", 2 ), YAML_UNSIGNED( "radioThemesDisabled", 1 ), - YAML_PADDING( 7 ), + YAML_UNSIGNED( "trainerTipOnly", 1 ), + YAML_PADDING( 6 ), YAML_UNSIGNED( "pwrOffIfInactive", 8 ), YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL), YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL), diff --git a/radio/src/targets/common/arm/CMakeLists.txt b/radio/src/targets/common/arm/CMakeLists.txt index 564ace9c2c0..4e5bebf2bae 100644 --- a/radio/src/targets/common/arm/CMakeLists.txt +++ b/radio/src/targets/common/arm/CMakeLists.txt @@ -275,7 +275,10 @@ if(GHOST) ) endif() -add_definitions(-g) +# The WASM modules are stripped at link time, so this would be wasted work +if(NOT WASI) + add_definitions(-g) +endif() add_definitions(-DCPUARM) add_definitions(-DGPS) diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt index 4a7d2394e34..e53dba1bfa4 100644 --- a/radio/src/targets/simu/CMakeLists.txt +++ b/radio/src/targets/simu/CMakeLists.txt @@ -95,6 +95,14 @@ if(WASI) target_compile_options(radiolib_native PUBLIC ${WASI_COMPILE_OPTIONS}) target_compile_options(simu_drivers PUBLIC ${WASI_COMPILE_OPTIONS}) + option(USE_PCH "Precompile the standard library headers" ON) + if(USE_PCH) + target_precompile_headers(radiolib_native PRIVATE + "$<$:${CMAKE_CURRENT_SOURCE_DIR}/pch_std.h>" + ) + target_precompile_headers(simu_drivers REUSE_FROM radiolib_native) + endif() + add_executable(wasi-module EXCLUDE_FROM_ALL ${SIMU_SRC} ) diff --git a/radio/src/targets/simu/pch_std.h b/radio/src/targets/simu/pch_std.h new file mode 100644 index 00000000000..f20bd4f313d --- /dev/null +++ b/radio/src/targets/simu/pch_std.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +// Precompiled for the WASM builds, where parsing these headers takes about half +// of the compile time. Standard library headers only: including EdgeTX headers +// here would make every file depend on them. +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/radio/src/telemetry/ghost.cpp b/radio/src/telemetry/ghost.cpp index 7d167bb4139..36588703f6f 100644 --- a/radio/src/telemetry/ghost.cpp +++ b/radio/src/telemetry/ghost.cpp @@ -88,7 +88,7 @@ const GhostSensor ghostSensors[] = { GS(GHOST_ID_GPS_LAT, STR_GPS, UNIT_GPS_LATITUDE, 0), GS(GHOST_ID_GPS_LONG, STR_GPS, UNIT_GPS_LONGITUDE, 0), GS(GHOST_ID_GPS_GSPD, STR_SENSOR_GSPD, UNIT_KMH, 1), - GS(GHOST_ID_GPS_HDG, STR_SENSOR_HDG, UNIT_DEGREE, 3), + GS(GHOST_ID_GPS_HDG, STR_SENSOR_HDG, UNIT_DEGREE, 1), GS(GHOST_ID_GPS_ALT, STR_SENSOR_GPSALT, UNIT_METERS, 0), GS(GHOST_ID_GPS_SATS, STR_SENSOR_SATELLITES, UNIT_RAW, 0), @@ -302,7 +302,8 @@ void processGhostTelemetryFrame(uint8_t module, uint8_t* buffer, uint32_t length bluetooth.write(buffer, length); } #endif - processGhostTelemetryValue(GHOST_ID_GPS_HDG, _get_u16le(frame, 3) / 10); + // ground course is passed via GHST in 0.1 degree + processGhostTelemetryValue(GHOST_ID_GPS_HDG, _get_u16le(frame, 3)); // ground speed is passed via GHST as cm/s, converted to km/h for OpenTx processGhostTelemetryValue(GHOST_ID_GPS_GSPD, (_get_u16le(frame, 1) * 36 + 50) / 100); diff --git a/radio/src/tests/ghost.cpp b/radio/src/tests/ghost.cpp new file mode 100644 index 00000000000..4d5127353bf --- /dev/null +++ b/radio/src/tests/ghost.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "gtest/gtest.h" +#include "gtests.h" +#include "telemetry/telemetry.h" +#include "telemetry/ghost.h" +#include "telemetry/sensor_names.h" +#include "crc.h" + +#if defined(GHOST) + +// GHST_DL_GPS_SECONDARY as sent by Betaflight (ghstFrameGpsSecondaryTelemetry): +// ground speed in cm/s, ground course in 0.1 degree, number of satellites. +static void sendGhostGpsSecondary(uint16_t speed, uint16_t course, uint8_t sats) +{ + uint8_t buffer[14] = {0}; + buffer[0] = GHST_ADDR_RADIO; + buffer[1] = 12; // type + 10 bytes payload + crc + uint8_t* frame = buffer + 2; + frame[0] = GHST_DL_GPS_SECONDARY; + frame[1] = speed & 0xFF; + frame[2] = speed >> 8; + frame[3] = course & 0xFF; + frame[4] = course >> 8; + frame[5] = sats; + frame[11] = crc8(frame, 11); + processGhostTelemetryFrame(EXTERNAL_MODULE, buffer, sizeof(buffer)); +} + +static int findSensor(const char* label) +{ + for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) { + if (g_model.telemetrySensors[i].isAvailable() && + !strncmp(g_model.telemetrySensors[i].label, label, TELEM_LABEL_LEN)) + return i; + } + return -1; +} + +// sensor value in whole units, as shown on the radio +static int32_t sensorValue(int index) +{ + int32_t value = telemetryItems[index].value; + for (uint8_t prec = g_model.telemetrySensors[index].prec; prec > 0; prec--) + value /= 10; + return value; +} + +TEST(Ghost, gpsSecondaryHeading) +{ + MODEL_RESET(); + TELEMETRY_RESET(); + telemetryStreaming = TELEMETRY_TIMEOUT10ms; + allowNewSensors = true; + + // 10 m/s, 185.0 deg, 12 sats + sendGhostGpsSecondary(1000, 1850, 12); + + int hdg = findSensor(STR_SENSOR_HDG); + ASSERT_GE(hdg, 0); + EXPECT_EQ(sensorValue(hdg), 185); + EXPECT_EQ(telemetryItems[hdg].value, 1850); + EXPECT_EQ(g_model.telemetrySensors[hdg].prec, 1); + + sendGhostGpsSecondary(1000, 3599, 12); + EXPECT_EQ(telemetryItems[hdg].value, 3599); + + // sensor discovered by an older firmware (stored with precision 2) + g_model.telemetrySensors[hdg].prec = 2; + sendGhostGpsSecondary(1000, 1850, 12); + EXPECT_EQ(telemetryItems[hdg].value, 18500); + EXPECT_EQ(sensorValue(hdg), 185); + + // ground speed and satellites were already right + int gspd = findSensor(STR_SENSOR_GSPD); + ASSERT_GE(gspd, 0); + EXPECT_EQ(sensorValue(gspd), 36); + int sats = findSensor(STR_SENSOR_SATELLITES); + ASSERT_GE(sats, 0); + EXPECT_EQ(sensorValue(sats), 12); + + allowNewSensors = false; +} + +#endif // GHOST diff --git a/radio/src/translations/i18n/cn.h b/radio/src/translations/i18n/cn.h index 626f29dc8b1..4a681fbfb32 100644 --- a/radio/src/translations/i18n/cn.h +++ b/radio/src/translations/i18n/cn.h @@ -567,6 +567,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("关机检查教练", "关机时检查教练信号") #define TR_MODEL_STILL_POWERED "模型未断电" #define TR_TRAINER_STILL_CONNECTED "教练信号未断开" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB未断开" #define TR_MODEL_SHUTDOWN "关机 ?" #define TR_PRESS_ENTER_TO_CONFIRM "确认" diff --git a/radio/src/translations/i18n/cz.h b/radio/src/translations/i18n/cz.h index d714b037bce..8cafb1a350e 100644 --- a/radio/src/translations/i18n/cz.h +++ b/radio/src/translations/i18n/cz.h @@ -569,6 +569,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trenér vypnutí", "Kontrola trenéra při vypnutí") #define TR_MODEL_STILL_POWERED "Model stále spuštěn" #define TR_TRAINER_STILL_CONNECTED "Trenér stále připojen" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB stále připojeno" #define TR_MODEL_SHUTDOWN "Vypnout?" #define TR_PRESS_ENTER_TO_CONFIRM "Zmáčkni ENTER pro potvrzení" diff --git a/radio/src/translations/i18n/da.h b/radio/src/translations/i18n/da.h index 9f6a9338127..4f72962bb1c 100644 --- a/radio/src/translations/i18n/da.h +++ b/radio/src/translations/i18n/da.h @@ -572,6 +572,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Træner alarm", "Kontroller træner forbindelse") #define TR_MODEL_STILL_POWERED "Model stadig med strøm" #define TR_TRAINER_STILL_CONNECTED "Træner fortsat forbundet" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB stadig forbundet" #define TR_MODEL_SHUTDOWN "Luk ned?" #define TR_PRESS_ENTER_TO_CONFIRM "Tryk enter for bekræft" diff --git a/radio/src/translations/i18n/de.h b/radio/src/translations/i18n/de.h index e16600f6459..3680ab697b5 100644 --- a/radio/src/translations/i18n/de.h +++ b/radio/src/translations/i18n/de.h @@ -569,6 +569,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer b. Aussch.", "Prüfe Trainer bei Ausschalten") #define TR_MODEL_STILL_POWERED "Modell noch aktiv!" #define TR_TRAINER_STILL_CONNECTED "Schüler noch verbunden" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB noch verbunden" #define TR_MODEL_SHUTDOWN "Herunterfahren?" #define TR_PRESS_ENTER_TO_CONFIRM "Drücke [ENTER] zum Bestätigen" diff --git a/radio/src/translations/i18n/en.h b/radio/src/translations/i18n/en.h index c89ac711e31..e38c2fcfc63 100644 --- a/radio/src/translations/i18n/en.h +++ b/radio/src/translations/i18n/en.h @@ -568,6 +568,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer shutdown", "Check trainer on shutdown") #define TR_MODEL_STILL_POWERED "Model still powered" #define TR_TRAINER_STILL_CONNECTED TR("Trainer still on","Trainer still connected") +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB still connected" #define TR_MODEL_SHUTDOWN "Shutdown?" #define TR_PRESS_ENTER_TO_CONFIRM "Press enter to confirm" diff --git a/radio/src/translations/i18n/es.h b/radio/src/translations/i18n/es.h index 05370adcf39..7c18a393533 100644 --- a/radio/src/translations/i18n/es.h +++ b/radio/src/translations/i18n/es.h @@ -566,6 +566,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer alarm", "check trainer connection") #define TR_MODEL_STILL_POWERED "Modelo aun encendido" #define TR_TRAINER_STILL_CONNECTED "Trainer still connected" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB still connected" #define TR_MODEL_SHUTDOWN "Apagar ?" #define TR_PRESS_ENTER_TO_CONFIRM "Enter para confirmar" diff --git a/radio/src/translations/i18n/fi.h b/radio/src/translations/i18n/fi.h index 4a8da731af1..062b74458be 100644 --- a/radio/src/translations/i18n/fi.h +++ b/radio/src/translations/i18n/fi.h @@ -567,6 +567,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer alarm", "check trainer connection") #define TR_MODEL_STILL_POWERED "Model still powered" #define TR_TRAINER_STILL_CONNECTED "Trainer still connected" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB still connected" #define TR_MODEL_SHUTDOWN "Shutdown ?" #define TR_PRESS_ENTER_TO_CONFIRM "Press enter to confirm" diff --git a/radio/src/translations/i18n/fr.h b/radio/src/translations/i18n/fr.h index 2cb7a25cb54..e470f52ac01 100644 --- a/radio/src/translations/i18n/fr.h +++ b/radio/src/translations/i18n/fr.h @@ -570,6 +570,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer shutdown", "Vérifier écolage avant extinction") #define TR_MODEL_STILL_POWERED TR("Modèle alimenté", "Modèle encore alimenté") #define TR_TRAINER_STILL_CONNECTED "Élève encore connecté" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB encore connecté" #define TR_MODEL_SHUTDOWN "Éteindre ?" #define TR_PRESS_ENTER_TO_CONFIRM "Appui [Enter] pour confirmer" diff --git a/radio/src/translations/i18n/he.h b/radio/src/translations/i18n/he.h index 76042693328..8c8d91d752d 100644 --- a/radio/src/translations/i18n/he.h +++ b/radio/src/translations/i18n/he.h @@ -571,6 +571,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("התראת טריינר", "בדוק חיבור טריינר") #define TR_MODEL_STILL_POWERED "מודל עדיין פעיל" #define TR_TRAINER_STILL_CONNECTED "מצב חניך עדיין מחובר" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "חיבור עדיין מחובר" #define TR_MODEL_SHUTDOWN "?לכבות" #define TR_PRESS_ENTER_TO_CONFIRM "לחץ אנטר לאישור" diff --git a/radio/src/translations/i18n/it.h b/radio/src/translations/i18n/it.h index 3b827b341db..adc564a00fa 100644 --- a/radio/src/translations/i18n/it.h +++ b/radio/src/translations/i18n/it.h @@ -568,6 +568,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Spegnimento Trainer", "Controlla trainer spegnendo") #define TR_MODEL_STILL_POWERED "Radio ancora\nconnessa" #define TR_TRAINER_STILL_CONNECTED "Trainer ancora\nconnesso" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB ancora\nconnesso" #define TR_MODEL_SHUTDOWN "Spegnimento?" #define TR_PRESS_ENTER_TO_CONFIRM "Premi [ENT] per confermare" diff --git a/radio/src/translations/i18n/jp.h b/radio/src/translations/i18n/jp.h index 7c0fe11b7fc..f205aeb31c4 100644 --- a/radio/src/translations/i18n/jp.h +++ b/radio/src/translations/i18n/jp.h @@ -567,6 +567,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer shutdown", "シャットダウン時にトレーナーをチェック") #define TR_MODEL_STILL_POWERED "電源が入ったままです" #define TR_TRAINER_STILL_CONNECTED "トレーナー通信はまだ継続しています" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USBが接続されたままです" #define TR_MODEL_SHUTDOWN "シャットダウンしますか?" #define TR_PRESS_ENTER_TO_CONFIRM "Enterを押して確認してください" diff --git a/radio/src/translations/i18n/ko.h b/radio/src/translations/i18n/ko.h index 7800a01be36..1c46ddd3861 100644 --- a/radio/src/translations/i18n/ko.h +++ b/radio/src/translations/i18n/ko.h @@ -574,6 +574,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("트레이너 상태 확인", "종료 시 트레이너 확인") #define TR_MODEL_STILL_POWERED "모델 전원이 켜져 있음" #define TR_TRAINER_STILL_CONNECTED TR("트레이너 연결됨", "트레이너 아직 연결됨") +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB 연결 중" #define TR_MODEL_SHUTDOWN "모델을 종료할까요?" diff --git a/radio/src/translations/i18n/nl.h b/radio/src/translations/i18n/nl.h index 42bf51a86fb..67d48859664 100644 --- a/radio/src/translations/i18n/nl.h +++ b/radio/src/translations/i18n/nl.h @@ -569,6 +569,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer alarm", "check trainer connection") #define TR_MODEL_STILL_POWERED "Model still powered" #define TR_TRAINER_STILL_CONNECTED "Trainer still connected" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB still connected" #define TR_MODEL_SHUTDOWN "Shutdown ?" #define TR_PRESS_ENTER_TO_CONFIRM "Press enter to confirm" diff --git a/radio/src/translations/i18n/pl.h b/radio/src/translations/i18n/pl.h index c1303a1f863..35fe5003346 100644 --- a/radio/src/translations/i18n/pl.h +++ b/radio/src/translations/i18n/pl.h @@ -567,6 +567,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trener wyłączanie", "Sprawdzaj trener przy wyłączaniu") #define TR_MODEL_STILL_POWERED "Model jest włączony" #define TR_TRAINER_STILL_CONNECTED "Uczeń ciągle podłączony" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB jest podłączone" #define TR_MODEL_SHUTDOWN "Wyłączyć?" #define TR_PRESS_ENTER_TO_CONFIRM "Press enter to confirm" diff --git a/radio/src/translations/i18n/pt.h b/radio/src/translations/i18n/pt.h index 6fb1c3ec3a4..23d4cbf19c5 100644 --- a/radio/src/translations/i18n/pt.h +++ b/radio/src/translations/i18n/pt.h @@ -567,6 +567,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Trainer ao desl", "Checar trainer ao desligar") #define TR_MODEL_STILL_POWERED "Modelo ainda ligado" #define TR_TRAINER_STILL_CONNECTED "Trainer ainda conectado" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB ainda ligado" #define TR_MODEL_SHUTDOWN "Desligar?" #define TR_PRESS_ENTER_TO_CONFIRM "Press enter p/ confirmar" diff --git a/radio/src/translations/i18n/ru.h b/radio/src/translations/i18n/ru.h index 1d7d93d4b37..cd78446e7c4 100644 --- a/radio/src/translations/i18n/ru.h +++ b/radio/src/translations/i18n/ru.h @@ -570,6 +570,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("ВЫК Тренер", "Проверьте работу тренера при ВЫКЛ") #define TR_MODEL_STILL_POWERED "Модель работает" #define TR_TRAINER_STILL_CONNECTED "Тренер подкл" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB подкл" #define TR_MODEL_SHUTDOWN "Отключение?" #define TR_PRESS_ENTER_TO_CONFIRM "Нажмите [Enter] для подтв" diff --git a/radio/src/translations/i18n/se.h b/radio/src/translations/i18n/se.h index ecc354673e2..3a73b275876 100644 --- a/radio/src/translations/i18n/se.h +++ b/radio/src/translations/i18n/se.h @@ -571,6 +571,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Lärare alarm", "Kolla läraranslutningen") #define TR_MODEL_STILL_POWERED TR("Modell på","Modellen är på") #define TR_TRAINER_STILL_CONNECTED "Lärare fortfarande ansluten" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB är ansluten" #define TR_MODEL_SHUTDOWN "Stänga av?" #define TR_PRESS_ENTER_TO_CONFIRM "Tryck [ENTER] för att bekräfta" diff --git a/radio/src/translations/i18n/sk.h b/radio/src/translations/i18n/sk.h index 714a1b212ce..99f8fb70aae 100644 --- a/radio/src/translations/i18n/sk.h +++ b/radio/src/translations/i18n/sk.h @@ -574,6 +574,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Tréner vypnutý", "Kontrola trénera pri vypnutí") #define TR_MODEL_STILL_POWERED "Model stále spustený" #define TR_TRAINER_STILL_CONNECTED "Tréner stále pripojený" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB stále pripojené" #define TR_MODEL_SHUTDOWN "Vypnúť?" #define TR_PRESS_ENTER_TO_CONFIRM "Stlač ENTER pre potvrdenie" diff --git a/radio/src/translations/i18n/tw.h b/radio/src/translations/i18n/tw.h index a3c0c7eeee8..5484e753c19 100644 --- a/radio/src/translations/i18n/tw.h +++ b/radio/src/translations/i18n/tw.h @@ -565,6 +565,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("關機檢查教練", "關機時檢查教練信號") #define TR_MODEL_STILL_POWERED "模型未斷電" #define TR_TRAINER_STILL_CONNECTED "教練信號未斷開" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB未斷開" #define TR_MODEL_SHUTDOWN "關機 ?" #define TR_PRESS_ENTER_TO_CONFIRM "確認" diff --git a/radio/src/translations/i18n/ua.h b/radio/src/translations/i18n/ua.h index cf7ac995065..bb0b7770d8a 100644 --- a/radio/src/translations/i18n/ua.h +++ b/radio/src/translations/i18n/ua.h @@ -569,6 +569,9 @@ #define TR_TRAINER_SHUTDOWN_ALARM TR("Тренер при вимк.", "Перевіряти тренер при вимиканні") #define TR_MODEL_STILL_POWERED "Модель досі під живленням" #define TR_TRAINER_STILL_CONNECTED "Трен. досі підключено" +#define TR_TRAINER_JACK_MODE "Trainer lead" +#define TR_TRAINER_JACK_MODES_1 "Crossover" +#define TR_TRAINER_JACK_MODES_2 "Standard" #define TR_USB_STILL_CONNECTED "USB досі підключено" #define TR_MODEL_SHUTDOWN "Вимкнути?" #define TR_PRESS_ENTER_TO_CONFIRM "Тисни enter для підтвердження" diff --git a/radio/src/translations/sim_string_list.h b/radio/src/translations/sim_string_list.h index 0ece2c080f9..11ae8e55914 100644 --- a/radio/src/translations/sim_string_list.h +++ b/radio/src/translations/sim_string_list.h @@ -1010,6 +1010,8 @@ #define STR_TOUCH_PANEL currentLangStrings->STR_TOUCH_PANEL #define STR_TRAINER_SHUTDOWN_ALARM currentLangStrings->STR_TRAINER_SHUTDOWN_ALARM #define STR_TRAINER_STILL_CONNECTED currentLangStrings->STR_TRAINER_STILL_CONNECTED +#define STR_TRAINER_JACK_MODE currentLangStrings->STR_TRAINER_JACK_MODE +#define STR_TRAINER_JACK_MODES currentLangStrings->STR_TRAINER_JACK_MODES #define STR_TRAINER currentLangStrings->STR_TRAINER #define STR_TRIM currentLangStrings->STR_TRIM #define STR_TRIMINC currentLangStrings->STR_TRIMINC diff --git a/radio/src/translations/string_list.h b/radio/src/translations/string_list.h index f3dee893726..f51525022cd 100644 --- a/radio/src/translations/string_list.h +++ b/radio/src/translations/string_list.h @@ -1006,6 +1006,8 @@ STR(TOUCH_ENABLED) STR(TOUCH_PANEL) STR(TRAINER_SHUTDOWN_ALARM) STR(TRAINER_STILL_CONNECTED) +STR(TRAINER_JACK_MODE) +STRARRAY(TRAINER_JACK_MODES) STR(TRAINER) STR(TRIM) STR(TRIMINC) diff --git a/radio/src/translations/translation_def.h b/radio/src/translations/translation_def.h index c6c6ebba316..8bfe6dee206 100644 --- a/radio/src/translations/translation_def.h +++ b/radio/src/translations/translation_def.h @@ -136,6 +136,7 @@ #define TR_FS_COLOR_LIST SA9(TR_FS_COLOR_LIST) #define TR_STATUS_LED_COLORS SA3(TR_STATUS_LED_COLORS) +#define TR_TRAINER_JACK_MODES SA2(TR_TRAINER_JACK_MODES) #define TR_VUSBJOYSTICK_CH_AXIS SA9(TR_VUSBJOYSTICK_CH_AXIS) #define TR_POTTYPES SA8(TR_POTTYPES) @@ -189,7 +190,7 @@ #define TR_TRNMODE SA3(TR_TRNMODE) #define TR_VPERSISTENT SA3(TR_VPERSISTENT) #define TR_COUNTRY_CODES SA3(TR_COUNTRY_CODES) -#define TR_JACK_MODES SA3(TR_COUNTRY_CODES) +#define TR_JACK_MODES SA3(TR_JACK_MODES) #define TR_VDISPLAYTRIMS SA3(TR_VDISPLAYTRIMS) #define TR_ADCFILTERVALUES SA3(TR_ADCFILTERVALUES) #define TR_VMLTPX SA3(TR_VMLTPX) diff --git a/radio/util/hw_defs/models.py b/radio/util/hw_defs/models.py index 04c9f6c742a..284b46fb202 100644 --- a/radio/util/hw_defs/models.py +++ b/radio/util/hw_defs/models.py @@ -363,6 +363,7 @@ class Trainer(BaseModel): trainer_gpio_af: Optional[str] = None trainer_detect_gpio: Optional[str] = None trainer_detect_inverted: Optional[bool] = None + trainer_jack_crossover: Optional[bool] = None Input = Union[StickInput, FlexInput, SwitchInput, RawInput, VBatInput, RTCBatInput, LuxInput] diff --git a/tools/build-companion.sh b/tools/build-companion.sh index 1bd5485d3ce..68d3e07ee8b 100755 --- a/tools/build-companion.sh +++ b/tools/build-companion.sh @@ -60,7 +60,8 @@ if [[ -z ${EDGETX_VERSION_SUFFIX} ]]; then fi fi -rm -rf build && mkdir build && cd build || exit +BUILD_ROOT="${BUILD_ROOT:-build}" +rm -rf "${BUILD_ROOT}" && mkdir -p "${BUILD_ROOT}" && cd "${BUILD_ROOT}" || exit get_platform_config() { local platform @@ -105,15 +106,16 @@ clean_build() { clean_build && mkdir -p native/plugins -# Copy WASM simulator modules if available -if [[ -d "${SRCDIR}/wasm-modules" ]]; then - cp "${SRCDIR}"/wasm-modules/*.wasm native/plugins/ 2>/dev/null && \ +# Copy WASM simulator modules if available (WASM_DIR overrides the default location) +WASM_DIR="${WASM_DIR:-${SRCDIR}/wasm-modules}" +if [[ -d "${WASM_DIR}" ]]; then + cp "${WASM_DIR}"/*.wasm native/plugins/ 2>/dev/null && \ echo " 🔌 Copied WASM modules to plugins/" || true fi error_status=0 -if ! cmake -B native -S "${SRCDIR}" --toolchain cmake/toolchain/native.cmake ${BUILD_OPTIONS} >> "$LOG_FILE" 2>&1; then +if ! cmake -B native -S "${SRCDIR}" --toolchain "${SRCDIR}/cmake/toolchain/native.cmake" ${BUILD_OPTIONS} >> "$LOG_FILE" 2>&1; then echo " ❌ CMake configuration failed" cat "$LOG_FILE" error_status=1 diff --git a/tools/check_includes.py b/tools/check_includes.py new file mode 100644 index 00000000000..d325e11352e --- /dev/null +++ b/tools/check_includes.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Stop edgetx.h from spreading further into headers and hardware code. + +Existing includes are listed in check_includes_baseline.txt, which can only +shrink: a new include fails, and so does a baseline entry that has been fixed. +See docs/development/header-guidelines.md. + +Usage: check_includes.py [--update] +""" +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / "radio" / "src" +BASELINE = Path(__file__).with_name("check_includes_baseline.txt") + +INCLUDE = re.compile(r'^\s*#\s*include\s*"(?:\.\./)*edgetx\.h"', re.M) +SRC_EXT = {".c", ".cpp", ".h", ".hpp"} +SKIP_DIRS = {"thirdparty", "tests"} +HARDWARE_DIRS = {"boards", "drivers", "hal", "os", "targets"} + + +def includers(): + for path in sorted(SRC.rglob("*")): + rel = path.relative_to(SRC) + if path.suffix not in SRC_EXT or path.is_dir(): + continue + if SKIP_DIRS.intersection(rel.parts) or any(p.startswith(".") for p in rel.parts): + continue + if INCLUDE.search(path.read_text(errors="replace")): + yield rel + + +def restricted(rel): + return rel.suffix in {".h", ".hpp"} or rel.parts[0] in HARDWARE_DIRS + + +def main(): + found = list(includers()) + current = {r.as_posix() for r in found if restricted(r)} + + if "--update" in sys.argv[1:]: + BASELINE.write_text("".join(f"{r}\n" for r in sorted(current))) + print(f"{BASELINE.name}: {len(current)} entries") + return 0 + + baseline = set(BASELINE.read_text().split()) + new = sorted(current - baseline) + fixed = sorted(baseline - current) + + for rel in new: + print(f"{rel}: must not include edgetx.h") + for rel in fixed: + print(f"{rel}: no longer includes edgetx.h, remove it from {BASELINE.name}") + + print(f"edgetx.h includes: {len(found)} in total, {len(current)} in headers and hardware code") + return 1 if new or fixed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/check_includes_baseline.txt b/tools/check_includes_baseline.txt new file mode 100644 index 00000000000..0d0e7292a6e --- /dev/null +++ b/tools/check_includes_baseline.txt @@ -0,0 +1,76 @@ +boards/generic_stm32/battery_voltage.cpp +boards/generic_stm32/intmodule_heartbeat.cpp +boards/generic_stm32/rgb_leds.cpp +boards/generic_stm32/sport_update.cpp +boards/rm-h750/luminosity_sensor.cpp +drivers/CI1302.cpp +gps.h +gui/colorlcd/controls/channel_bar.h +gui/colorlcd/controls/switch_warn_dialog.h +gui/colorlcd/model/function_switches.h +gui/colorlcd/model/model_curves.h +gui/colorlcd/model/model_flightmodes.h +gui/colorlcd/model/model_gvars.h +gui/colorlcd/model/model_logical_switches.h +gui/colorlcd/model/model_mixer_scripts.h +gui/colorlcd/model/model_telemetry.h +gui/colorlcd/model/model_templates.h +gui/colorlcd/model/special_functions.h +gui/colorlcd/radio/radio_theme.h +gui/colorlcd/radio/radio_trainer.h +gui/colorlcd/themes/theme_manager.h +hal/adc_driver.cpp +hal/rgbleds.cpp +hal/switch_driver.cpp +io/uf2_flash.h +pdm_wav_recorder.h +targets/c14/battery_driver.cpp +targets/common/arm/stm32/audio_dac_driver.cpp +targets/common/arm/stm32/rotary_encoder_driver.cpp +targets/common/arm/stm32/trainer_driver.cpp +targets/common/arm/stm32/vs1053b.cpp +targets/horus/board.cpp +targets/horus/charge_ui.cpp +targets/hrs/tools/ai_terminal_ui.cpp +targets/hrs/tools/auto_measure_ui.cpp +targets/hrs/tools/batsenser_ui.cpp +targets/hrs/tools/charge_ui.cpp +targets/hrs/tools/chat_ui.cpp +targets/hrs/tools/crsf_measure_ui.cpp +targets/hrs/tools/custom_actions.cpp +targets/hrs/tools/dji_rs_measure_ui.cpp +targets/hrs/tools/dshot_tester_ui.cpp +targets/hrs/tools/hrs_tool.cpp +targets/hrs/tools/hrs_tools_hub.cpp +targets/hrs/tools/logic_measure_ui.cpp +targets/hrs/tools/mavlink_measure_ui.cpp +targets/hrs/tools/ppm_measure_ui.cpp +targets/hrs/tools/pwm_measure_ui.cpp +targets/hrs/tools/sbus_measure_ui.cpp +targets/hrs/tools/servo_tester_ui.cpp +targets/hrs/tools/sumd_measure_ui.cpp +targets/hrs/v15/auto_measure.cpp +targets/hrs/v15/batsenser.cpp +targets/hrs/v15/crsf_measure.cpp +targets/hrs/v15/dji_rs_measure.cpp +targets/hrs/v15/dshot_tester.cpp +targets/hrs/v15/ext_senser_driver.cpp +targets/hrs/v15/logic_measure.cpp +targets/hrs/v15/mavlink_measure.cpp +targets/hrs/v15/measure_trainer_feed.cpp +targets/hrs/v15/ppm_measure.cpp +targets/hrs/v15/pwm_measure.cpp +targets/hrs/v15/sbus_measure.cpp +targets/hrs/v15/servo_tester.cpp +targets/hrs/v15/sumd_measure.cpp +targets/pa01/battery_driver.cpp +targets/pl18/battery_driver.cpp +targets/simu/led_driver.cpp +targets/simu/sdl_simu.cpp +targets/simu/simulib.cpp +targets/st16/battery_driver.cpp +targets/taranis/bind_button_driver.cpp +targets/taranis/board.cpp +targets/taranis/lcd_driver_spi.cpp +targets/taranis/shared_dsc_headphone.cpp +targets/taranis/top_lcd_driver.cpp diff --git a/tools/hwdef_schema.json b/tools/hwdef_schema.json index e9133e8ff75..c7b7cf4dd7f 100644 --- a/tools/hwdef_schema.json +++ b/tools/hwdef_schema.json @@ -480,7 +480,8 @@ "trainer_timer_freq": { "type": "string" }, "trainer_gpio_af": { "type": "string" }, "trainer_detect_gpio": { "type": "string" }, - "trainer_detect_inverted": { "type": "boolean" } + "trainer_detect_inverted": { "type": "boolean" }, + "trainer_jack_crossover": { "type": "boolean" } } } } diff --git a/web/.gitignore b/web/.gitignore index 2cb9fdea2f6..ea62202a879 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1,4 +1,5 @@ node_modules dist .vite +.wrangler public/*.wasm diff --git a/web/README.md b/web/README.md index 64bb3eab79e..5bd2aac71d2 100644 --- a/web/README.md +++ b/web/README.md @@ -4,7 +4,7 @@ Browser-based EdgeTX radio simulator using WebAssembly + WASI threads. ## Prerequisites -- Node.js 20+ +- Node.js 22+ - Pre-built `.wasm` modules in `public/` (see [Building WASM Modules](#building-wasm-modules)) ## Quick Start @@ -113,6 +113,8 @@ Passing flavour names as arguments limits generation to those radios only — th | `public/radios.json` | Radio definitions (generated — do not edit manually) | | `public/_headers` | COOP/COEP headers for production deployment | | `scripts/gen-radios-json.js` | Generates `radios.json` from `radio/src/boards/hw_defs/` | +| `scripts/check-deployment.js` | Checks a deployed site: modules, content types and COOP/COEP headers | +| `wrangler.jsonc` | Cloudflare Workers configuration | ## Browser Requirements @@ -127,6 +129,7 @@ Passing flavour names as arguments limits generation to those radios only — th npm run dev # Start dev server with HMR npm run build # Production build to dist/ npm run preview # Preview production build +npm run smoke-test # Start every radio of the production build in a browser ``` ### Debugging @@ -136,16 +139,28 @@ npm run preview # Preview production build ## Production Deployment -The dev server sets the required COOP/COEP headers automatically. For production, `public/_headers` provides them for platforms like Cloudflare Pages. For other hosts, configure your web server to set: +The dev server sets the required COOP/COEP headers automatically. For production, `public/_headers` provides them on Cloudflare Workers. For other hosts, configure your web server to set: ``` Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin ``` -### Cloudflare Pages +### Cloudflare Workers + +The site is deployed as a Worker with static assets, configured in `wrangler.jsonc`. The [Web Simulator workflow](../.github/workflows/web_simulator.yml) builds all modules and deploys `main` every night. It can also be started manually from the Actions tab. + +To deploy by hand, with every module in `public/`: ```bash npm run build -npx wrangler pages deploy dist --project-name=edgetx-simulator +npx wrangler deploy +node scripts/check-deployment.js ``` + +`wrangler deploy` goes live immediately. + +CI uses two secrets from the `web-simulator` GitHub environment, which only accepts runs from `main`: + +- `CLOUDFLARE_API_TOKEN`: an account API token with the Editor role on this Worker only. It cannot create a Worker, so the first deployment has to be done by hand. +- `CLOUDFLARE_ACCOUNT_ID` diff --git a/web/package-lock.json b/web/package-lock.json index 5a87f38d2d9..514f9042daa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -14,9 +14,157 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "@types/node": "^20.11.0", + "playwright-core": "1.64.1", "svelte": "^5.0.0", "typescript": "^5.3.3", - "vite": "^6.0.0" + "vite": "^6.0.0", + "wrangler": "4.156.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.2.tgz", + "integrity": "sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": ">1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261023.1.tgz", + "integrity": "sha512-lsEx0i7nR0PNG/erUFFHmQmiidEhLfDE5uQmAL9421/y4/ycuxDDOSaxu3VGGYFFPK5sduyUeMmjIlX4CwOBFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261023.1.tgz", + "integrity": "sha512-5e79fHgabdDD8EgmjOhJ1S+xMsf6DbC7fL7IgTBizMnzOp0P2isDn7pYmUxI9AJZE9FVQ4RUilH/I36YNg+GQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261023.1.tgz", + "integrity": "sha512-VHQ6jKFSihjR2+x1buby8XTdBLLJztVnYiB7OLOTYO8aDXB+c1Rd2410PdgFuFYc4AgFEk6PCJLyCLL5L1KBnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261023.1.tgz", + "integrity": "sha512-ilpRHzfDN8YFRM5CLRKuFw40x/LJBS89yVdhdQmxehlVDuBqjKpJB++zPFbTgKRRFaNupgnXc8RHktXAIhgCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261023.1.tgz", + "integrity": "sha512-279WLVjNsMycAwr0EZNiNEf0l2SY6NUtsN80NwZzbTHzakmiN+WSx4w38muxPm+PAU0oSPPAPXKVD+H6Hya8xw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { @@ -470,991 +618,2385 @@ "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ - "arm" + "x64" ], "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" + "darwin" ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "android" - ] + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ - "arm64" + "arm" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "freebsd" - ] + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ - "x64" + "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "freebsd" - ] + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ - "arm" + "ppc64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ - "arm" + "riscv64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ - "arm64" + "s390x" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ - "arm64" + "x64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ - "loong64" + "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ - "loong64" + "x64" ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" - ] + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ - "ppc64" + "arm" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ - "riscv64" + "ppc64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ - "x64" + "arm64" ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "Apache-2.0", "optional": true, "os": [ - "openbsd" - ] + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ - "arm64" + "wasm32" ], "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sveltejs/acorn-typescript": { + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", + "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", "dev": true, "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", + "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/miniflare": { + "version": "5.20261023.3-beta", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261023.3-beta.tgz", + "integrity": "sha512-thdXzlktu5GE1o/BlmMAPeJ0Cu6Tu5LpMbRlyfRrpVvMRrhd6p52TMqcITCZZLVzt1UO92VGBkLDhLOpryUAyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.5", + "undici": "7.30.1", + "workerd": "1.20261023.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/svelte": { + "version": "5.53.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz", + "integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.6.3", + "esm-env": "^1.2.1", + "esrap": "^2.2.2", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" } }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", - "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", - "debug": "^4.4.1", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "vitefu": "^1.0.6" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.0.0" + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "node_modules/vitefu": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", + "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.7" + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/workerd": { + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261023.1.tgz", + "integrity": "sha512-NeofJFyaYNpqKpFRJW3ApuonaerPd6/JWlfTtLDYf2A6ygqrDEFnYgEXmc0hhOxqnJsSM+kXJN6VebPojzRrsg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": ">=16" }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "vite": "^6.0.0" + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20261023.1", + "@cloudflare/workerd-darwin-arm64": "1.20261023.1", + "@cloudflare/workerd-linux-64": "1.20261023.1", + "@cloudflare/workerd-linux-arm64": "1.20261023.1", + "@cloudflare/workerd-windows-64": "1.20261023.1" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "license": "MIT", + "node_modules/wrangler": { + "version": "4.156.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.156.0.tgz", + "integrity": "sha512-kVLAShuLiNgE3jcrmXcymDf/iPKDPrSxACe5ueugW/33HeNB9E9CkKYchudCeBwgmsnl4k+1zt4NO7vqb/fRuQ==", + "dev": true, + "license": "MIT OR Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "@cloudflare/kv-asset-handler": "0.5.0", + "@cloudflare/unenv-preset": "2.16.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.28.2", + "miniflare": "5.20261023.3-beta", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20261023.1" + }, + "bin": { + "cf-wrangler": "bin/cf-wrangler.js", + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^5.20261023.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/wrangler/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "node_modules/wrangler/node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "node_modules/wrangler/node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "node_modules/wrangler/node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.4.0" + "node": ">=18" } }, - "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "node_modules/wrangler/node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/wrangler/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/wrangler/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/wrangler/node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/devalue": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", - "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/wrangler/node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" } }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz", - "integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==", + "node_modules/wrangler/node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/wrangler/node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "node": ">=18" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/wrangler/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=18" } }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "node_modules/wrangler/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "node_modules/wrangler/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/wrangler/node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/wrangler/node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" ], + "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=18" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/wrangler/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=18" } }, - "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "node_modules/wrangler/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/wrangler/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" } }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "node_modules/wrangler/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" + "node": ">=18" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/wrangler/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/svelte": { - "version": "5.53.7", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.7.tgz", - "integrity": "sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==", + "node_modules/wrangler/node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "@types/trusted-types": "^2.0.7", - "acorn": "^8.12.1", - "aria-query": "5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.6.3", - "esm-env": "^1.2.1", - "esrap": "^2.2.2", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { "node": ">=18" } }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "node_modules/wrangler/node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=18" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/wrangler/node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14.17" + "node": ">=18" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "node_modules/wrangler/node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "node_modules/wrangler/node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, "bin": { - "vite": "bin/vite.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" + "node": ">=18" }, "optionalDependencies": { - "fsevents": "~2.3.3" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { + "bufferutil": { "optional": true }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { + "utf-8-validate": { "optional": true } } }, - "node_modules/vitefu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", "dev": true, "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" } }, "node_modules/zimmerframe": { diff --git a/web/package.json b/web/package.json index 10ea7ed0a7f..39a5d912a77 100644 --- a/web/package.json +++ b/web/package.json @@ -6,14 +6,17 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "smoke-test": "node scripts/smoke-test.js" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "@types/node": "^20.11.0", + "playwright-core": "1.64.1", "svelte": "^5.0.0", "typescript": "^5.3.3", - "vite": "^6.0.0" + "vite": "^6.0.0", + "wrangler": "4.156.0" }, "dependencies": { "@emnapi/wasi-threads": "^1.0.0", diff --git a/web/scripts/check-deployment.js b/web/scripts/check-deployment.js new file mode 100644 index 00000000000..eaeff13d028 --- /dev/null +++ b/web/scripts/check-deployment.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node +// +// Check that a deployed web simulator serves what the app needs at runtime. +// +// Usage: node web/scripts/check-deployment.js +// +// A radio only shows up in the app if a HEAD request for its module succeeds +// with a WASM content type, and threads need the COOP/COEP headers. + +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const ATTEMPTS = 3; +const RETRY_DELAY_MS = 15000; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const radios = JSON.parse( + readFileSync(join(__dirname, '..', 'public', 'radios.json'), 'utf-8'), +); + +const REQUIRED_HEADERS = { + 'cross-origin-embedder-policy': 'require-corp', + 'cross-origin-opener-policy': 'same-origin', +}; + +async function checkSite(site) { + const errors = []; + + const index = await fetch(site); + if (!index.ok) errors.push(`${site}: status ${index.status}`); + for (const [name, expected] of Object.entries(REQUIRED_HEADERS)) { + const actual = index.headers.get(name); + if (actual !== expected) { + errors.push(`${site}: ${name} is '${actual}', expected '${expected}'`); + } + } + + await Promise.all(radios.map(async ({ wasm }) => { + const resp = await fetch(new URL(wasm, site), { method: 'HEAD' }); + const type = resp.headers.get('content-type') || ''; + if (!resp.ok || !type.includes('wasm')) { + errors.push(`${wasm}: status ${resp.status}, content type '${type}'`); + } + })); + + // Must not fall back to index.html + const missing = await fetch(new URL('does-not-exist.wasm', site), { method: 'HEAD' }); + if (missing.status !== 404) { + errors.push(`Missing file: status ${missing.status}, expected 404`); + } + + return errors; +} + +const url = process.argv[2]; +if (!url) { + console.error('Usage: check-deployment.js '); + process.exit(2); +} +const site = url.endsWith('/') ? url : `${url}/`; + +// A new deployment can take a moment to be served everywhere +for (let attempt = 1; ; attempt++) { + const errors = await checkSite(site); + if (errors.length === 0) { + console.log(`${site}: OK, ${radios.length} radios`); + break; + } + errors.forEach((e) => console.error(e)); + if (attempt === ATTEMPTS) process.exit(1); + console.error(`Attempt ${attempt}/${ATTEMPTS} failed, retrying`); + await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); +} diff --git a/web/scripts/smoke-test.js b/web/scripts/smoke-test.js new file mode 100644 index 00000000000..f9d04977bec --- /dev/null +++ b/web/scripts/smoke-test.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node +// +// Start every radio of a built site in a browser, and check that it gets past its +// splash screen. Catches what cannot be seen by looking at the files, such as a module +// importing a function the page does not provide. +// +// Usage: node web/scripts/smoke-test.js [module.wasm ...] +// +// Needs `npm run build`, and `npx playwright-core install chromium`. + +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { chromium } from 'playwright-core'; +import { preview } from 'vite'; + +const JOBS = 3; +const TIMEOUT_MS = 120000; + +const webDir = join(dirname(fileURLToPath(import.meta.url)), '..'); +const radios = JSON.parse(readFileSync(join(webDir, 'dist', 'radios.json'), 'utf-8')); +const selected = process.argv.slice(2); +const queue = radios.filter(({ wasm }) => !selected.length || selected.includes(wasm)); + +if (!queue.length || (selected.length && queue.length !== selected.length)) { + console.error(`Selected ${queue.length} of the radios in radios.json`); + process.exit(2); +} + +// Reads back a screenshot, as the WebGL canvas cannot be read directly +async function lcdColours(page) { + const png = await page.locator('canvas.lcd').screenshot(); + return page.evaluate(async (base64) => { + const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const image = await createImageBitmap(new Blob([bytes], { type: 'image/png' })); + const canvas = new OffscreenCanvas(image.width, image.height); + const ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0); + // Leave out the edges, as the rounded corners show the page behind the LCD + const [x, y] = [image.width >> 3, image.height >> 3]; + const { data } = ctx.getImageData(x, y, image.width - 2 * x, image.height - 2 * y); + const colours = new Set(); + for (let i = 0; i < data.length; i += 4) { + colours.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]); + } + return colours.size; + }, png.toString('base64')); +} + +async function checkRadio(browser, url, { wasm }) { + const context = await browser.newContext(); + const page = await context.newPage(); + const errors = []; + page.on('pageerror', (e) => errors.push(e.message)); + page.on('console', (m) => { + if (m.type() === 'error') errors.push(m.text()); + }); + + try { + const deadline = Date.now() + TIMEOUT_MS; + const remaining = () => Math.max(deadline - Date.now(), 1); + + await page.goto(url); + await page.locator(`select option[value="${wasm}"]`).waitFor({ state: 'attached', timeout: remaining() }); + await page.selectOption('select', wasm); + await page.getByRole('button', { name: 'Run', exact: true }).click({ timeout: remaining() }); + + const status = page.getByText(/^(Running|Error: .*|WASM module not found.*)$/); + await status.first().waitFor({ timeout: remaining() }); + const text = await status.first().innerText(); + if (text !== 'Running') throw new Error(text); + + const fail = (message) => { + throw new Error(errors[0] ?? message); + }; + while ((await lcdColours(page)) < 2) { + if (errors.length || Date.now() > deadline) fail('nothing drawn on the LCD'); + await page.waitForTimeout(250); + } + + const splash = await page.locator('canvas.lcd').screenshot(); + while (splash.equals(await page.locator('canvas.lcd').screenshot())) { + if (errors.length || Date.now() > deadline) fail('stuck on the first screen'); + await page.waitForTimeout(250); + } + if (errors.length) fail(); + } finally { + await context.close(); + } +} + +const server = await preview({ root: webDir, preview: { port: 0, open: false } }); +const url = server.resolvedUrls.local[0]; +const browser = await chromium.launch(); +const failed = []; +const started = Date.now(); + +await Promise.all(Array.from({ length: JOBS }, async () => { + for (let radio = queue.shift(); radio; radio = queue.shift()) { + const t = Date.now(); + try { + await checkRadio(browser, url, radio); + console.log(`${radio.name}: OK (${((Date.now() - t) / 1000).toFixed(1)}s)`); + } catch (e) { + failed.push(radio.name); + console.error(`${radio.name}: ${e.message.split('\n')[0]}`); + } + } +})); + +await browser.close(); +await server.close(); + +console.log(`${failed.length} failed, ${((Date.now() - started) / 1000).toFixed(0)}s`); +process.exit(failed.length ? 1 : 0); diff --git a/web/src/App.svelte b/web/src/App.svelte index 4c424d018fb..4ca137805ac 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -37,6 +37,8 @@ KEY_SHIFT: 14, KEY_BIND: 15, }; + const DEFAULT_RADIO = 'edgetx-tx16s-simulator.wasm'; + interface DisplayDef { w: number; h: number; @@ -155,7 +157,10 @@ let lcdSize = 0; let isPortrait = $derived(lcdHeight > lcdWidth); - let cssWidth = $derived(Math.max(lcdWidth, 150 * lcdWidth / lcdHeight, 320)); + // Same as LAYOUT_SCALE in etx_lv_theme.h: this LCD has a higher density, and showing + // it at this scale makes the UI the same size on the page as on other radios. + let lcdScale = $derived(lcdWidth === 800 ? 1.375 : 1); + let cssWidth = $derived(Math.max(lcdWidth / lcdScale, 150 * lcdWidth / lcdHeight, 320)); let cssHeight = $derived(cssWidth * lcdHeight / lcdWidth); // Stick mode: 0=Mode1(thr=RV), 1=Mode2(thr=LV), 2=Mode3(thr=RV), 3=Mode4(thr=LV) @@ -342,7 +347,7 @@ }); selectedRadio = match ? match.wasm : (saved && avail.some((r) => r.wasm === saved)) ? saved - : avail[0].wasm; + : (avail.find((r) => r.wasm === DEFAULT_RADIO) ?? avail[0]).wasm; status = `${avail.length} radio(s) available`; } else { status = 'No .wasm files found in public/'; @@ -1148,7 +1153,8 @@ bind:this={canvas} class="lcd" style:width="{cssWidth}px" - style:height="{cssHeight}px" + style:aspect-ratio="{lcdWidth} / {lcdHeight}" + style:image-rendering={lcdDepth >= 16 && lcdWidth > cssWidth ? 'auto' : null} style:background="{lcdDepth > 0 && lcdDepth < 16 ? 'rgb(47, 123, 227)' : '#000'}" onmousedown={handleCanvasMouseDown} ontouchstart={handleCanvasTouchStart} @@ -1594,6 +1600,18 @@ Trace output{#if running}{fps} fps{/if}
{traceLog}
+ + {#if __BUILD_COMMIT__} + + {/if}