master 967e91838b91 cached
988 files
8.5 MB
2.3M tokens
720 symbols
1 requests
Download .txt
Showing preview only (9,130K chars total). Download the full file or copy to clipboard to get everything.
Repository: OpenLightingProject/open-fixture-library
Branch: master
Commit: 967e91838b91
Files: 988
Total size: 8.5 MB

Directory structure:
gitextract_hf70e5ny/

├── .devcontainer/
│   └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── new-plugin.md
│   ├── aw/
│   │   └── actions-lock.json
│   ├── dependabot.yaml
│   ├── lighthouserc.json
│   └── workflows/
│       ├── external-links-checker.yaml
│       ├── fixture-metadata-triage.lock.yml
│       ├── fixture-metadata-triage.md
│       ├── lighthouse-production.yaml
│       ├── lighthouse-review.yaml
│       └── test.yaml
├── .gitignore
├── .stylelintrc.yaml
├── LICENSE
├── README.md
├── cli/
│   ├── build-plugin-data.js
│   ├── build-register.js
│   ├── build-test-fixtures.js
│   ├── debug-env-variables.js
│   ├── diff-plugin-outputs.js
│   ├── export-fixture.js
│   ├── import-fixture.js
│   └── run-export-test.js
├── docs/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── README.md
│   ├── capability-types.md
│   ├── environment-variables.md
│   ├── fixture-features.md
│   ├── fixture-format.md
│   ├── fixture-model.md
│   ├── model-api.md
│   ├── plugins.md
│   ├── rest-api.md
│   ├── testing.md
│   └── ui.md
├── eslint.config.js
├── fixtures/
│   ├── 5star-systems/
│   │   └── spica-250m.json
│   ├── abstract/
│   │   └── twister-4.json
│   ├── acoustic-control/
│   │   └── par-180-cob-3in1.json
│   ├── adb/
│   │   ├── alc4.json
│   │   ├── europe-105.json
│   │   └── warp-m.json
│   ├── afx/
│   │   └── lmh460z.json
│   ├── alien-pro/
│   │   └── alien-s.json
│   ├── american-dj/
│   │   ├── 12p-hex-ip.json
│   │   ├── 18p-hex-ip.json
│   │   ├── 7p-hex-ip.json
│   │   ├── auto-spot-150.json
│   │   ├── boom-box-fx2.json
│   │   ├── cob-cannon-wash.json
│   │   ├── crazy-pocket-8.json
│   │   ├── dekker-led.json
│   │   ├── dotz-par.json
│   │   ├── encore-lp12z-ip.json
│   │   ├── encore-profile-1000-ww.json
│   │   ├── flat-par-qa12.json
│   │   ├── flat-par-qa12xs.json
│   │   ├── fog-fury-jett-pro.json
│   │   ├── galaxian-3d.json
│   │   ├── illusion-dotz-4-4.json
│   │   ├── inno-pocket-beam-q4.json
│   │   ├── inno-pocket-fusion.json
│   │   ├── inno-pocket-spot.json
│   │   ├── inno-spot-pro.json
│   │   ├── mega-bar-50rgb-rc.json
│   │   ├── mega-bar-50rgb.json
│   │   ├── mega-bar-rgba.json
│   │   ├── mega-hex-par.json
│   │   ├── mega-par-profile-plus.json
│   │   ├── mega-tripar-profile-plus.json
│   │   ├── mega-tripar-profile.json
│   │   ├── mod-hex100.json
│   │   ├── pocket-pro.json
│   │   ├── quad-phase-hp.json
│   │   ├── revo-4-ir.json
│   │   ├── revo-burst.json
│   │   ├── revo-sweep.json
│   │   ├── saber-spot-rgbw.json
│   │   ├── starburst.json
│   │   ├── stinger-ii.json
│   │   ├── stinger-spot.json
│   │   ├── ultra-hex-bar-12.json
│   │   ├── uv-eco-bar.json
│   │   ├── vbar-pak.json
│   │   ├── vizi-q-wash7.json
│   │   ├── vizi-spot-led-pro.json
│   │   └── xs-400.json
│   ├── ape-labs/
│   │   └── lightcan.json
│   ├── aputure/
│   │   ├── ls-1200d-pro.json
│   │   ├── ls-300x.json
│   │   ├── ls-600d-pro.json
│   │   ├── ls-600d.json
│   │   ├── ls-600x-pro.json
│   │   └── nova-p300c.json
│   ├── arri/
│   │   ├── broadcaster-2-plus.json
│   │   ├── l10-c.json
│   │   ├── l5-c.json
│   │   ├── l7-c.json
│   │   ├── skypanel-s120c.json
│   │   ├── skypanel-s30c.json
│   │   ├── skypanel-s360c.json
│   │   └── skypanel-s60c.json
│   ├── astera/
│   │   ├── ax3-lightdrop.json
│   │   ├── fp1-titan-tube.json
│   │   ├── fp2-helios-tube.json
│   │   ├── fp3-hyperion-tube.json
│   │   └── fp5-nyx-bulb.json
│   ├── audibax/
│   │   └── boston-60.json
│   ├── ayra/
│   │   ├── compar-20.json
│   │   └── tdc-triple-burst.json
│   ├── ayrton/
│   │   ├── diablo-s.json
│   │   ├── diablo-tc.json
│   │   └── magicblade-fx.json
│   ├── beamz/
│   │   ├── h2000-faze-machine.json
│   │   ├── panther-7r.json
│   │   ├── pls25-par.json
│   │   └── triple-flex-centre-pro-led.json
│   ├── big-dipper/
│   │   ├── lp001.json
│   │   └── ls90.json
│   ├── bitfocus/
│   │   └── companion-v2.json
│   ├── blizzard/
│   │   ├── hotbox-exa.json
│   │   ├── hotbox-rgbw.json
│   │   ├── puck-rgbaw.json
│   │   └── rokbox-rgbw.json
│   ├── boomtonedj/
│   │   ├── crazy-spot-30.json
│   │   ├── silentpar-12x10w-5in1.json
│   │   ├── silentpar-12x10w-6in1.json
│   │   ├── silentpar-12x3w-3in1.json
│   │   ├── silentpar-5x10w-5in1.json
│   │   ├── silentpar-5x10w-6in1.json
│   │   ├── silentpar-5x3w-3in1.json
│   │   ├── silentpar-7x10w-5in1.json
│   │   ├── silentpar-7x10w-6in1.json
│   │   ├── silentpar-7x3w-3in1.json
│   │   └── xtrem-led.json
│   ├── briteq/
│   │   ├── beam-fury-1.json
│   │   ├── beamspot1-dmx-fc.json
│   │   ├── bt-coloray-120r.json
│   │   ├── bt-coloray-18fcr.json
│   │   ├── bt-coloray-60r.json
│   │   ├── bt-ledrotor.json
│   │   ├── bt-stagepar-6in1.json
│   │   ├── btx-cirrus-ii.json
│   │   ├── btx-titan.json
│   │   ├── cob-slim-100-rgb.json
│   │   ├── pro-beamer-zoom-indoor.json
│   │   └── pro-beamer-zoom-outdoor.json
│   ├── cameo/
│   │   ├── auro-beam-150.json
│   │   ├── auro-spot-100.json
│   │   ├── auro-spot-200.json
│   │   ├── auro-spot-300.json
│   │   ├── auro-spot-400.json
│   │   ├── auro-spot-z300.json
│   │   ├── flash-matrix-250.json
│   │   ├── flat-par-can-rgb-10-ir.json
│   │   ├── flat-par-can-tri-5x-3w-ir.json
│   │   ├── flat-par-can-tri-7x-3w-ir.json
│   │   ├── flat-pro-18.json
│   │   ├── flat-pro-flood-600-ip65.json
│   │   ├── flat-pro-flood-ip65-tri.json
│   │   ├── gobo-scanner-80.json
│   │   ├── hydrabeam-100.json
│   │   ├── hydrabeam-300-rgbw.json
│   │   ├── hydrabeam-400-rgbw.json
│   │   ├── instant-air-1000-pro.json
│   │   ├── instant-air-2000-pro.json
│   │   ├── instant-hazer-1500-t-pro.json
│   │   ├── ioda-1000-rgb.json
│   │   ├── ioda-400-rgy.json
│   │   ├── ioda-600-rgb.json
│   │   ├── multi-fx-bar.json
│   │   ├── multi-par-cob-1.json
│   │   ├── nanospot-120.json
│   │   ├── outdoor-par-tri-12.json
│   │   ├── q-spot-40-cw.json
│   │   ├── q-spot-40-rgbw.json
│   │   ├── root-par-6.json
│   │   ├── steam-wizard-1000.json
│   │   ├── steam-wizard-2000.json
│   │   ├── storm.json
│   │   ├── thunder-wash-100-rgb.json
│   │   ├── thunder-wash-100-w.json
│   │   ├── thunder-wash-600-rgb.json
│   │   ├── thunder-wash-600-rgbw.json
│   │   ├── thunder-wash-600-w.json
│   │   ├── ts-100-ww.json
│   │   ├── ts-200-fc.json
│   │   ├── ts-60-rgbw.json
│   │   ├── ts60.json
│   │   ├── zenit-w600.json
│   │   └── zenit-z120.json
│   ├── chauvet-dj/
│   │   ├── colorband-pix-ip.json
│   │   ├── colorband-pix.json
│   │   ├── corepar-uv-usb.json
│   │   ├── dmx-4.json
│   │   ├── eve-p-100-ww.json
│   │   ├── eve-p-130-rgb.json
│   │   ├── freedom-h1.json
│   │   ├── geyser-rgb.json
│   │   ├── gigbar-2.json
│   │   ├── hurricane-1600.json
│   │   ├── hurricane-haze-1dx.json
│   │   ├── hurricane-haze-2d.json
│   │   ├── intimidator-spot-110.json
│   │   ├── intimidator-spot-160.json
│   │   ├── intimidator-spot-260.json
│   │   ├── kinta-x.json
│   │   ├── led-par-64-tri-b.json
│   │   ├── megastrobe-fx12.json
│   │   ├── motiondrape-led.json
│   │   ├── slimpar-pro-h-usb.json
│   │   ├── slimpar-pro-qz12.json
│   │   ├── slimpar-pro-rgba.json
│   │   ├── slimpar-pro-w.json
│   │   ├── slimpar-q12-bt.json
│   │   ├── slimpar-t12-bt.json
│   │   ├── slimpar-t12-usb.json
│   │   └── washfx.json
│   ├── chauvet-professional/
│   │   ├── colorado-1-solo.json
│   │   ├── colordash-batten-quad-6.json
│   │   ├── colordash-s-par-1.json
│   │   ├── ovation-f-915vw.json
│   │   ├── rogue-r1-wash.json
│   │   ├── rogue-r2-wash.json
│   │   └── vesuvio-rgba.json
│   ├── chroma-q/
│   │   ├── color-force-ii-12.json
│   │   ├── color-force-ii-48.json
│   │   └── color-force-ii-72.json
│   ├── cinetec/
│   │   └── par-18x15w-rgbwa.json
│   ├── clay-paky/
│   │   ├── a-leda-b-eye-k10.json
│   │   ├── a-leda-b-eye-k20.json
│   │   ├── alpha-spot-qwo-800.json
│   │   ├── sharpy.json
│   │   ├── show-batten-100.json
│   │   └── spheriscan.json
│   ├── clf/
│   │   └── hera.json
│   ├── coemar/
│   │   └── prospot-250-lx.json
│   ├── contest/
│   │   ├── irled64-18x12six.json
│   │   └── irledflat-5x12SIXb.json
│   ├── dedolight/
│   │   ├── dled4-bi.json
│   │   └── dled7-bi.json
│   ├── desisti/
│   │   ├── softled-4-vw.json
│   │   └── softled-8-vw.json
│   ├── dmg-lumiere/
│   │   ├── maxi-mix.json
│   │   ├── mini-mix.json
│   │   └── sl1-mix.json
│   ├── dts/
│   │   ├── scena-led-150.json
│   │   ├── xr1200-wash.json
│   │   └── xr4-spot.json
│   ├── elation/
│   │   ├── acl-360-roller.json
│   │   ├── cuepix-blinder-ww2.json
│   │   ├── cuepix-blinder-ww4.json
│   │   ├── design-led-par-zoom.json
│   │   ├── fuze-par-z60ip.json
│   │   ├── platinum-hfx.json
│   │   ├── platinum-seven.json
│   │   ├── platinum-spot-15r-pro.json
│   │   ├── proteus-hybrid.json
│   │   ├── sixpar-100-ip.json
│   │   ├── sixpar-100.json
│   │   ├── sixpar-200-ip.json
│   │   ├── sixpar-200-wmg.json
│   │   ├── sixpar-200.json
│   │   ├── sixpar-300-ip.json
│   │   ├── sixpar-300-wmg.json
│   │   ├── sixpar-300.json
│   │   ├── uni-bar.json
│   │   └── zw19.json
│   ├── eliminator/
│   │   ├── stealth-beam.json
│   │   └── stealth-wash-zoom.json
│   ├── empire-lighting/
│   │   └── 8x-3w-led-spider-effect.json
│   ├── epsilon/
│   │   └── duo-q-beam-bar.json
│   ├── equinox/
│   │   ├── gigabar.json
│   │   └── rgb-power-batten.json
│   ├── etc/
│   │   ├── colorsource-par-deep-blue.json
│   │   ├── colorsource-par.json
│   │   ├── colorsource-spot-deep-blue.json
│   │   ├── colorsource-spot.json
│   │   ├── fos4PD16.json
│   │   ├── fos4PD24.json
│   │   ├── fos4PD8.json
│   │   ├── fos4PL16.json
│   │   ├── fos4PL24.json
│   │   ├── fos4PL8.json
│   │   ├── source-4wrd-color-ii.json
│   │   ├── source-four-led-series-2-daylight-hd.json
│   │   ├── source-four-led-series-2-lustr.json
│   │   ├── source-four-led-series-2-tungsten-hd.json
│   │   ├── source-four-led-series-3-daylight-hdr.json
│   │   └── source-four-led-series-3-lustr-x8.json
│   ├── eurolite/
│   │   ├── edx-4.json
│   │   ├── led-7c-7-silent-slim.json
│   │   ├── led-b-40.json
│   │   ├── led-bar-12-qcl-rgba-bar.json
│   │   ├── led-bar-3-hcl-bar.json
│   │   ├── led-bar-6-qcl-rgbw.json
│   │   ├── led-big-party-spot.json
│   │   ├── led-big-party-tcl-spot.json
│   │   ├── led-dmx-pixel-tube-16-rgb-ip20.json
│   │   ├── led-fe-1500.json
│   │   ├── led-h2o.json
│   │   ├── led-kls-801.json
│   │   ├── led-ml-56-rgbw.json
│   │   ├── led-par-56-tcl.json
│   │   ├── led-party-spot.json
│   │   ├── led-party-tcl-spot.json
│   │   ├── led-pix-12-hcl.json
│   │   ├── led-pix-144.json
│   │   ├── led-ps-4-hcl.json
│   │   ├── led-sls-12-bcl.json
│   │   ├── led-sls-5-bcl.json
│   │   ├── led-sls-6-uv-floor.json
│   │   ├── led-svf-1.json
│   │   ├── led-tha-100f-mk2.json
│   │   ├── led-tha-100f.json
│   │   ├── led-theatre-cob-200-rgb-ww.json
│   │   ├── led-tl-3-rgb-uv.json
│   │   ├── led-tl-4-qcl.json
│   │   ├── led-tmh-14.json
│   │   ├── led-tmh-17.json
│   │   ├── led-tmh-18.json
│   │   ├── led-tmh-7.json
│   │   ├── led-tmh-8.json
│   │   ├── led-tmh-9.json
│   │   ├── led-tmh-x12.json
│   │   ├── led-tmh-x25.json
│   │   ├── led-z-200-tcl.json
│   │   ├── md-2030.json
│   │   ├── multiflood-pro-ip-smd-rgbw.json
│   │   ├── n-150.json
│   │   ├── tmh-xb-130.json
│   │   └── ts-2.json
│   ├── event-lighting/
│   │   ├── par12x12.json
│   │   └── par5x12.json
│   ├── evolight/
│   │   └── colours-archspot-54-rgb.json
│   ├── explo/
│   │   ├── gasprojector-gx2.json
│   │   └── x2-wave-flamer.json
│   ├── eyourlife/
│   │   └── led-rgbw-54x3-par64.json
│   ├── fiilex/
│   │   └── p3-color.json
│   ├── flash-professional/
│   │   ├── led-moving-head-150w.json
│   │   ├── led-par-64-cob-300w-rgbwauv.json
│   │   └── led-par-64-slim-7x10w-rgbw-mk2.json
│   ├── fovitec/
│   │   └── 600xb.json
│   ├── fractal-lights/
│   │   ├── par-led-7x10w.json
│   │   ├── par-led-7x12w.json
│   │   └── par-led-7x9w.json
│   ├── fun-generation/
│   │   ├── led-pot-12-1w-rgbw.json
│   │   ├── led-pot-12x1w-qcl-rgb-ww-15.json
│   │   ├── led-pot-12x1w-qcl-rgb-ww-40.json
│   │   ├── picobeam-30-quad-led.json
│   │   ├── picobeam-60-cob-rgbw.json
│   │   ├── picoblade-fx-4x10w-rgbw.json
│   │   ├── picospot-20-led.json
│   │   ├── picospot-45-led.json
│   │   ├── picowash-40-pixel-quad-led.json
│   │   ├── separ-hex-led-rgbaw-uv.json
│   │   ├── separ-quad-led-rgb-uv.json
│   │   └── separ-quad-led-rgbw.json
│   ├── futurelight/
│   │   ├── dj-scan-250.json
│   │   ├── dmh-75-i-led-moving-head.json
│   │   ├── pro-slim-par-7-hcl.json
│   │   ├── sc-250-scanner.json
│   │   └── stb-648-led-strobe-smd-5050.json
│   ├── galaxis/
│   │   └── g-flame.json
│   ├── gantom/
│   │   └── precision-dmx.json
│   ├── generic/
│   │   ├── 4-channel-dimmer-pack.json
│   │   ├── cmy-fader.json
│   │   ├── color-temperature-fader.json
│   │   ├── cw-ww-fader.json
│   │   ├── desk-channel.json
│   │   ├── drgb-fader.json
│   │   ├── drgbw-fader.json
│   │   ├── grbw-fader.json
│   │   ├── pan-tilt.json
│   │   ├── rgb-fader.json
│   │   ├── rgba-fader.json
│   │   ├── rgbd-fader.json
│   │   ├── rgbw-fader.json
│   │   ├── rgbwauv-fader.json
│   │   ├── rgbww-fader.json
│   │   └── strobe.json
│   ├── ghost/
│   │   ├── ip-spot-bat.json
│   │   └── ip-spot-pro.json
│   ├── glp/
│   │   ├── force-120.json
│   │   ├── impression-fr1.json
│   │   ├── impression-laser.json
│   │   ├── impression-spot-one.json
│   │   ├── impression-x4-bar-10.json
│   │   ├── jdc1.json
│   │   ├── knv-arc.json
│   │   └── knv-cube.json
│   ├── glx/
│   │   └── gls-4-led-stage-4.json
│   ├── griven/
│   │   └── kolorado-4000.json
│   ├── gruft/
│   │   ├── pixel-tube.json
│   │   └── ventilator.json
│   ├── hazebase/
│   │   └── base-hazer-pro.json
│   ├── hive/
│   │   ├── bee-50-c.json
│   │   ├── bumble-bee-25-cx.json
│   │   ├── hornet-200-c.json
│   │   ├── hornet-200-cx.json
│   │   ├── super-hornet-575-c.json
│   │   ├── wasp-100-c.json
│   │   └── wasp-100-cx.json
│   ├── hong-yi/
│   │   └── hy-g60.json
│   ├── hsl/
│   │   └── 40w-beam-spot-light-rgbw.json
│   ├── ibiza-light/
│   │   ├── lp64-led-promo.json
│   │   ├── ls-005led.json
│   │   └── par-mini-rgb3.json
│   ├── ignition/
│   │   ├── 2bright-par-18-ip.json
│   │   ├── led-accu-par.json
│   │   ├── strip-blinder-x.json
│   │   ├── teatro-led-spot-100-fr.json
│   │   └── teatro-led-spot-100-pc.json
│   ├── ikan/
│   │   └── stryder-sfb150.json
│   ├── infinity/
│   │   ├── iw-340-rdm.json
│   │   └── iw-720-rdm.json
│   ├── jb-lighting/
│   │   ├── jbled-a7.json
│   │   └── varyscan-p7.json
│   ├── jb-systems/
│   │   ├── imove-5s.json
│   │   ├── irock-5c.json
│   │   └── twin-effect-laser.json
│   ├── kam/
│   │   └── gobotracer.json
│   ├── kino-flo/
│   │   ├── celeb-250-led-dmx.json
│   │   ├── celeb-450-led-dmx.json
│   │   ├── celeb-led-201-dmx.json
│   │   ├── celeb-led-250-dmx.json
│   │   └── celeb-led-450-dmx.json
│   ├── lalucenatz/
│   │   ├── 18leds-par-light.json
│   │   └── dj-lights.json
│   ├── laserworld/
│   │   ├── cs-1000rgb.json
│   │   ├── ds-1000rgb.json
│   │   ├── el-400rgb-mk2.json
│   │   └── shownet.json
│   ├── ledj/
│   │   ├── slimline-12q5-rgba.json
│   │   └── slimline-12q5-rgbw.json
│   ├── lep-laser/
│   │   └── diamond-pro-2-8.json
│   ├── light-sky/
│   │   └── aurora.json
│   ├── light4me/
│   │   └── led-par-18x3w-uv.json
│   ├── lightmaxx/
│   │   ├── cls-nano-cob.json
│   │   ├── dj-scan-led.json
│   │   ├── easy-wash-quad-led.json
│   │   ├── led-blinder-4.json
│   │   ├── led-nano-par.json
│   │   ├── led-par-56.json
│   │   ├── led-par-64-cob-100w-rgb.json
│   │   ├── platinum-mini-tri-par.json
│   │   ├── vector-haze-1-0.json
│   │   ├── vector-pixel-bar-18x-15w-rgbwa.json
│   │   ├── vega-bat-1.json
│   │   └── vega-zoom-wash.json
│   ├── lite-tek/
│   │   └── beam-230.json
│   ├── litecraft/
│   │   ├── washx-21.json
│   │   └── washx-432-sw.json
│   ├── litegear/
│   │   ├── litemat-plus-1.json
│   │   ├── litemat-plus-2.json
│   │   ├── litemat-plus-2l.json
│   │   ├── litemat-plus-3.json
│   │   ├── litemat-plus-4.json
│   │   ├── litemat-plus-8.json
│   │   ├── litetile-plus-4.json
│   │   ├── litetile-plus-8.json
│   │   ├── s2-litemat-1.json
│   │   ├── s2-litemat-2.json
│   │   ├── s2-litemat-2l.json
│   │   ├── s2-litemat-3.json
│   │   └── s2-litemat-4.json
│   ├── lixada/
│   │   ├── mini-beam-rgbw.json
│   │   ├── mini-gobo-moving-head-light.json
│   │   └── mini-moving-head-rgbw.json
│   ├── look/
│   │   ├── cryofog.json
│   │   └── viper-nt.json
│   ├── lupo/
│   │   ├── actionpanel-dual-color.json
│   │   ├── actionpanel-full-color.json
│   │   ├── superpanel-dual-color-60.json
│   │   ├── superpanel-full-color-60.json
│   │   ├── superpanelpro-dual-color-30.json
│   │   ├── superpanelpro-full-color-30.json
│   │   ├── ultrapanel-dual-color-60.json
│   │   ├── ultrapanel-full-color-60.json
│   │   ├── ultrapanelpro-dual-color-30.json
│   │   └── ultrapanelpro-full-color-30.json
│   ├── magicfx/
│   │   ├── psyco2jet.json
│   │   ├── smokejet.json
│   │   └── stage-flame.json
│   ├── manufacturers.json
│   ├── mark/
│   │   ├── mbar-381-ip.json
│   │   └── superbat-led-72.json
│   ├── martin/
│   │   ├── atomic-3000.json
│   │   ├── mac-250-beam.json
│   │   ├── mac-250-krypton.json
│   │   ├── mac-250-wash.json
│   │   ├── mac-600.json
│   │   ├── mac-700-wash.json
│   │   ├── mac-aura.json
│   │   ├── mac-axiom-hybrid.json
│   │   ├── mac-encore-performance.json
│   │   ├── mac-viper-airfx.json
│   │   ├── mac-viper-performance.json
│   │   ├── mac-viper-wash.json
│   │   ├── magnum-2500-hz.json
│   │   ├── mania-scx500.json
│   │   ├── mx-10-extreme.json
│   │   ├── roboscan-812.json
│   │   ├── rush-mh-2-wash.json
│   │   ├── rush-mh-3-beam.json
│   │   ├── rush-mh-5-profile.json
│   │   ├── rush-mh-7-hybrid.json
│   │   ├── rush-par-2-rgbw-zoom.json
│   │   ├── rush-scanner-1-led.json
│   │   ├── stagebar-54l.json
│   │   └── stagebar-54s.json
│   ├── mdg/
│   │   ├── atme.json
│   │   ├── hazer-atmosphere-aps.json
│   │   └── theone-atmospheric-generator.json
│   ├── mega-led-lighting/
│   │   ├── led-par-light-372.json
│   │   └── zoom-360.json
│   ├── mega-lite/
│   │   ├── framebot-600.json
│   │   ├── mw1.json
│   │   ├── spotbot-led-cmy-300.json
│   │   └── washbot-led-cymk-300.json
│   ├── minuit-une/
│   │   ├── ivl-carre.json
│   │   └── m-carre.json
│   ├── nicols/
│   │   ├── led-bar-123-fc-ip.json
│   │   └── pat-252.json
│   ├── orion/
│   │   └── orcan2.json
│   ├── panasonic/
│   │   ├── pt-rz120.json
│   │   └── pt-rz120l.json
│   ├── phocea-light/
│   │   └── box-leds-batterie-6x15w.json
│   ├── powerlighting/
│   │   └── wash-84w.json
│   ├── pr-lighting/
│   │   └── xs-250-spot.json
│   ├── prolights/
│   │   ├── diamond19.json
│   │   ├── pixpan16.json
│   │   ├── polar3000.json
│   │   ├── smartbat.json
│   │   └── v700spot.json
│   ├── qtx/
│   │   ├── lux-ld01.json
│   │   └── lux-ld30w.json
│   ├── renkforce/
│   │   └── gm107.json
│   ├── robe/
│   │   ├── colorspot-2500e-at.json
│   │   ├── dj-scan-250-xt.json
│   │   ├── robin-300e-wash.json
│   │   ├── robin-600e-spot.json
│   │   ├── robin-ledbeam-100.json
│   │   ├── robin-ledbeam-150.json
│   │   ├── robin-ledwash-600.json
│   │   ├── robin-parfect-150.json
│   │   ├── robin-t1-profile.json
│   │   ├── robin-viva-cmy.json
│   │   └── spot-160-xt.json
│   ├── robert-juliat/
│   │   └── 613sx.json
│   ├── rockville/
│   │   └── rockpar50.json
│   ├── sgm/
│   │   └── p-5.json
│   ├── shehds/
│   │   ├── led-flat-par-12x3w-rgbw.json
│   │   ├── led-flat-par-18x18w.json
│   │   ├── led-flat-par-54x3w.json
│   │   ├── led-flat-par-7x18w-rgbwa-uv-light.json
│   │   ├── led-par-18x18w.json
│   │   └── led-spot-60w.json
│   ├── showline/
│   │   └── sl-nitro-510c.json
│   ├── showlite/
│   │   └── lb-4390.json
│   ├── showpro/
│   │   └── litebar-h9.json
│   ├── showtec/
│   │   ├── accent-spot-q4-rgbw.json
│   │   ├── archi-painter-24-8-q4.json
│   │   ├── atmos-2000.json
│   │   ├── club-par-12-4-rgbw.json
│   │   ├── club-par-12-6-rgbwauv.json
│   │   ├── compact-par-18.json
│   │   ├── compact-par-7-tri.json
│   │   ├── dim-4lc.json
│   │   ├── dominator.json
│   │   ├── explorer-250-wash-pro.json
│   │   ├── horizon-8.json
│   │   ├── kanjo-spot-60.json
│   │   ├── kanjo-wash-rgb.json
│   │   ├── led-blinder-2-cob.json
│   │   ├── led-light-bar-rgb-v3.json
│   │   ├── performer-2500.json
│   │   ├── phantom-140-led-spot.json
│   │   ├── phantom-25-led-wash.json
│   │   ├── phantom-3r-beam.json
│   │   ├── phantom-50-led-spot.json
│   │   ├── phantom-matrix-fx.json
│   │   ├── pixel-bar-12-mkii.json
│   │   ├── shark-the-meg-hybrid-one.json
│   │   ├── sunraise-led.json
│   │   ├── sunstrip-active-mkii.json
│   │   └── xs-1-rgbw.json
│   ├── showven/
│   │   ├── sparkular-fall.json
│   │   └── sparkular.json
│   ├── silver-star/
│   │   └── mx-indigo-6000xe.json
│   ├── skypix/
│   │   └── ribalta-beam.json
│   ├── smoke-factory/
│   │   ├── data-ii.json
│   │   └── tour-hazer-ii.json
│   ├── solaris/
│   │   └── smart-36.json
│   ├── solena/
│   │   ├── max-par-20.json
│   │   └── mini-par-12.json
│   ├── soundlight/
│   │   └── 3204r-h.json
│   ├── stage-right/
│   │   ├── mini-beam-rgbw.json
│   │   └── stage-wash-7x10w-led-moving-head.json
│   ├── stairville/
│   │   ├── af-180-led-fogger.json
│   │   ├── af-250.json
│   │   ├── afh-600.json
│   │   ├── bel6-ip-bar-hex.json
│   │   ├── clb5-2p-rgb-ww-compact-led-par.json
│   │   ├── cx60-hex.json
│   │   ├── hz-200-compact-hazer.json
│   │   ├── led-bar-240-8.json
│   │   ├── led-flood-panel-150.json
│   │   ├── led-par-56.json
│   │   ├── led-par-64.json
│   │   ├── matrixx-sc-100.json
│   │   ├── mh-100.json
│   │   ├── mh-x20.json
│   │   ├── mh-x25.json
│   │   ├── mh-x30-led-spot.json
│   │   ├── mh-x30.json
│   │   ├── mh-x50.json
│   │   ├── mh-x60.json
│   │   ├── octagon-theater-20x6w-cw-ww-a.json
│   │   ├── par-56.json
│   │   ├── remus-hexspot-515.json
│   │   ├── revueled-120-cob-rgbww.json
│   │   ├── revueled-120-cob-true-white.json
│   │   ├── sonicpulse-led-bar-05.json
│   │   ├── sonicpulse-led-bar-10.json
│   │   ├── stage-tri-led.json
│   │   ├── vf-1200-dmx-vertifog-co2-fx.json
│   │   ├── wild-wash-132-led-rgb-dmx.json
│   │   ├── wild-wash-648-led-white-dmx.json
│   │   ├── xbrick-full-colour.json
│   │   ├── xbrick-quad-16x8w-rgbw.json
│   │   └── z120m-par-64-led-rgbw-120w.json
│   ├── starway/
│   │   ├── servo-color-4k.json
│   │   └── stickolor-1210uhd.json
│   ├── studio-due/
│   │   └── light-deflector.json
│   ├── sun-star/
│   │   └── g-2011-nova.json
│   ├── tecshow/
│   │   ├── nebula-18.json
│   │   └── nebula-6.json
│   ├── tiptop-stage-light/
│   │   └── 3-10w-battery-led-wedge-par.json
│   ├── tmb/
│   │   └── solaris-flare.json
│   ├── tomshine/
│   │   ├── 3-led-par-light-rgbuv.json
│   │   └── 80w-mini-gobo-moving-head.json
│   ├── uking/
│   │   ├── b117-par-can-4in1-rgbw-18-leds.json
│   │   ├── mini-led-spot-25w.json
│   │   ├── par-light-b262.json
│   │   └── zq-b20-mini-spider-light.json
│   ├── ultratec/
│   │   └── radiance-hazer.json
│   ├── varytec/
│   │   ├── bat-par-6-rgbuv.json
│   │   ├── bat-par-6-rgbwa.json
│   │   ├── easy-move-xs-hp-wash-7x8w-rgbw.json
│   │   ├── giga-bar-frost-pix-8-rgb.json
│   │   ├── giga-bar-hex-3.json
│   │   ├── hero-spot-230.json
│   │   ├── hero-wash-340fx-rgbw-zoom.json
│   │   ├── hero-wash-640fx.json
│   │   ├── led-hellball-3-rgb.json
│   │   └── led-theater-spot-100.json
│   ├── velleman/
│   │   └── aeron-250-ii.json
│   ├── venue/
│   │   ├── thintri64.json
│   │   └── tristrip3z.json
│   └── vrsl/
│       ├── disco-ball.json
│       └── flasher.json
├── jsconfig.json
├── lib/
│   ├── ajv-validator.js
│   ├── cache-result.js
│   ├── create-github-issue.js
│   ├── create-github-pr.js
│   ├── diff-plugin-outputs.js
│   ├── esm-shim.cjs
│   ├── fixture-features/
│   │   ├── 16bit-dmx-value-resolution.js
│   │   ├── capability-types.js
│   │   ├── duplicate-channel-names.js
│   │   ├── fine-channels.js
│   │   ├── fine-positions.js
│   │   ├── floating-point-physicals.js
│   │   ├── many-modes.js
│   │   ├── matrices.js
│   │   ├── multiple-categories.js
│   │   ├── multiple-focuses.js
│   │   ├── no-physical-data.js
│   │   ├── null-channels.js
│   │   ├── physical-override.js
│   │   ├── rdm.js
│   │   ├── redirect-reasons.js
│   │   ├── reused-channels.js
│   │   ├── switching-channels.js
│   │   └── wheels.js
│   ├── fixture-json-stringify.js
│   ├── get-ajv-error-messages.js
│   ├── import-json.js
│   ├── load-env-file.js
│   ├── model/
│   │   ├── AbstractChannel.js
│   │   ├── Capability.js
│   │   ├── CoarseChannel.js
│   │   ├── Entity.js
│   │   ├── FineChannel.js
│   │   ├── Fixture.js
│   │   ├── Manufacturer.js
│   │   ├── Matrix.js
│   │   ├── Meta.js
│   │   ├── Mode.js
│   │   ├── NullChannel.js
│   │   ├── Physical.js
│   │   ├── Range.js
│   │   ├── Resource.js
│   │   ├── SwitchingChannel.js
│   │   ├── TemplateChannel.js
│   │   ├── Wheel.js
│   │   └── WheelSlot.js
│   ├── model.js
│   ├── register.js
│   ├── scale-dmx-values.js
│   ├── schema-properties.js
│   ├── server-response-helpers.js
│   ├── site-crawler.js
│   └── types.js
├── nuxt.config.js
├── package.json
├── plugins/
│   ├── aglight/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── color-chief/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── colorsource/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── d-light/
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── attributes-correctness.js
│   │   └── plugin.json
│   ├── dmxcontrol3/
│   │   ├── ddf3-function-groups.js
│   │   ├── ddf3-functions.js
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── channel-numbers.js
│   │   └── plugin.json
│   ├── dragonframe/
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── json-schema-conformity.js
│   │   └── plugin.json
│   ├── ecue/
│   │   ├── export.js
│   │   ├── import.js
│   │   └── plugin.json
│   ├── gdtf/
│   │   ├── deprecated-gdtf-attributes.js
│   │   ├── gdtf-attributes.js
│   │   ├── gdtf-helpers.js
│   │   ├── import.js
│   │   └── plugin.json
│   ├── millumin/
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── json-schema-conformity.js
│   │   └── plugin.json
│   ├── ofl/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── op-z/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── plugins.json
│   └── qlcplus_4.12.2/
│       ├── export.js
│       ├── exportTests/
│       │   ├── fixture-tool-validation.js
│       │   └── xsd-schema-conformity.js
│       ├── import.js
│       ├── plugin.json
│       └── presets.js
├── resources/
│   └── gobos/
│       ├── 1-vertical-bar.json
│       ├── 10-circles.json
│       ├── 3-fold-spiral.json
│       ├── 3-fold-swirl.json
│       ├── 32-circles.json
│       ├── 4-line-arranged-dots.json
│       ├── 4-vertical-bars.json
│       ├── 5-fold-swirl-inverted.json
│       ├── 5-fold-swirl.json
│       ├── 5-pointed-compass-rose-star.json
│       ├── 5-pointed-star.json
│       ├── 6-mouse-heads.json
│       ├── aliases/
│       │   └── qlcplus.json
│       ├── biohazard.json
│       ├── bubbles.json
│       ├── caro-flower.json
│       ├── circle-outline.json
│       ├── circled-clock-face.json
│       ├── circular-beams.json
│       ├── circular-droplets.json
│       ├── circular-drops.json
│       ├── clockwork.json
│       ├── comets.json
│       ├── crescent.json
│       ├── daisy-flower.json
│       ├── dot-spiral.json
│       ├── double-lines-star.json
│       ├── edge-touching-circles.json
│       ├── fragmented-star.json
│       ├── glass-raindrops-on-window.json
│       ├── glass-red-10-circles.json
│       ├── hypnotic-swirl.json
│       ├── pointed-grid.json
│       ├── quarter-mark.json
│       ├── rays.json
│       ├── rose-petal-abstract.json
│       ├── rose-petal.json
│       ├── shards.json
│       ├── square-arrows.json
│       ├── square-outline.json
│       ├── stars.json
│       ├── stones.json
│       ├── striped-iris.json
│       ├── tiny-stars.json
│       ├── triangle-hexagon-pattern.json
│       ├── triangle-outline.json
│       └── vibrating-triangle.json
├── schemas/
│   ├── capability.json
│   ├── channel.json
│   ├── definitions.json
│   ├── fixture-redirect.json
│   ├── fixture.json
│   ├── gobo.json
│   ├── manufacturers.json
│   ├── matrix.json
│   ├── plugin.json
│   └── wheel-slot.json
├── server/
│   ├── ecosystem.config.js
│   ├── nginx-site-available
│   ├── redeploy.sh
│   └── webhook.js
├── tests/
│   ├── built-files-committed.js
│   ├── dmx-value-scaling.test.js
│   ├── external-links.js
│   ├── fixture-valid.js
│   ├── fixtures-valid.js
│   ├── github/
│   │   ├── export-diff.js
│   │   ├── exports-valid.js
│   │   ├── pull-request.js
│   │   └── schema-version-reminder.js
│   ├── http-status.test.js
│   ├── test-fixtures.json
│   └── test-fixtures.md
└── ui/
    ├── api/
    │   ├── download.js
    │   ├── index.js
    │   ├── openapi.json
    │   ├── routes/
    │   │   ├── fixtures/
    │   │   │   ├── from-editor.js
    │   │   │   ├── from-editor.json
    │   │   │   ├── import.js
    │   │   │   ├── import.json
    │   │   │   ├── submit.js
    │   │   │   └── submit.json
    │   │   ├── get-search-results.js
    │   │   ├── get-search-results.json
    │   │   ├── manufacturers/
    │   │   │   ├── _manufacturerKey.js
    │   │   │   ├── _manufacturerKey.json
    │   │   │   ├── index.js
    │   │   │   └── index.json
    │   │   ├── plugins/
    │   │   │   ├── _pluginKey.js
    │   │   │   ├── _pluginKey.json
    │   │   │   ├── index.js
    │   │   │   └── index.json
    │   │   ├── submit-feedback.js
    │   │   └── submit-feedback.json
    │   └── routes.js
    ├── assets/
    │   ├── icons/
    │   │   └── icons.js
    │   ├── scripts/
    │   │   ├── editor-utilities.js
    │   │   └── fixture-link-types.js
    │   └── styles/
    │       ├── fonts.scss
    │       ├── global.scss
    │       ├── mixins.scss
    │       ├── style.scss
    │       ├── theming.scss
    │       └── vars.scss
    ├── components/
    │   ├── A11yDialog.vue
    │   ├── CapabilityTypeIcon.vue
    │   ├── CategoryBadge.vue
    │   ├── ChannelTypeIcon.vue
    │   ├── ClimateStrikeBanner.vue
    │   ├── ConditionalDetails.vue
    │   ├── DownloadButton.vue
    │   ├── FixtureHeader.vue
    │   ├── HeaderBar.vue
    │   ├── HelpWantedDialog.vue
    │   ├── HelpWantedMessage.vue
    │   ├── LabeledInput.vue
    │   ├── LabeledValue.vue
    │   ├── PropertyInputBoolean.vue
    │   ├── PropertyInputDimensions.vue
    │   ├── PropertyInputEntity.vue
    │   ├── PropertyInputNumber.vue
    │   ├── PropertyInputRange.vue
    │   ├── PropertyInputSelect.vue
    │   ├── PropertyInputText.vue
    │   ├── PropertyInputTextarea.vue
    │   ├── ThemeSwitcher.vue
    │   ├── editor/
    │   │   ├── EditorCapability.vue
    │   │   ├── EditorCapabilityTypeData.vue
    │   │   ├── EditorCapabilityWizard.vue
    │   │   ├── EditorCategoryChooser.vue
    │   │   ├── EditorChannelDialog.vue
    │   │   ├── EditorChooseChannelEditModeDialog.vue
    │   │   ├── EditorFileUpload.vue
    │   │   ├── EditorFixtureInformation.vue
    │   │   ├── EditorLink.vue
    │   │   ├── EditorLinks.vue
    │   │   ├── EditorManufacturer.vue
    │   │   ├── EditorMode.vue
    │   │   ├── EditorPhysical.vue
    │   │   ├── EditorProportionalPropertySwitcher.vue
    │   │   ├── EditorRestoreDialog.vue
    │   │   ├── EditorSubmitDialog.vue
    │   │   ├── EditorWheelSlot.vue
    │   │   ├── EditorWheelSlots.vue
    │   │   ├── capabilities/
    │   │   │   ├── CapabilityBeamAngle.vue
    │   │   │   ├── CapabilityBeamPosition.vue
    │   │   │   ├── CapabilityBladeInsertion.vue
    │   │   │   ├── CapabilityBladeRotation.vue
    │   │   │   ├── CapabilityBladeSystemRotation.vue
    │   │   │   ├── CapabilityColorIntensity.vue
    │   │   │   ├── CapabilityColorPreset.vue
    │   │   │   ├── CapabilityColorTemperature.vue
    │   │   │   ├── CapabilityEffect.vue
    │   │   │   ├── CapabilityEffectDuration.vue
    │   │   │   ├── CapabilityEffectParameter.vue
    │   │   │   ├── CapabilityEffectSpeed.vue
    │   │   │   ├── CapabilityFocus.vue
    │   │   │   ├── CapabilityFog.vue
    │   │   │   ├── CapabilityFogOutput.vue
    │   │   │   ├── CapabilityFogType.vue
    │   │   │   ├── CapabilityFrost.vue
    │   │   │   ├── CapabilityFrostEffect.vue
    │   │   │   ├── CapabilityGeneric.vue
    │   │   │   ├── CapabilityIntensity.vue
    │   │   │   ├── CapabilityIris.vue
    │   │   │   ├── CapabilityIrisEffect.vue
    │   │   │   ├── CapabilityMaintenance.vue
    │   │   │   ├── CapabilityNoFunction.vue
    │   │   │   ├── CapabilityPan.vue
    │   │   │   ├── CapabilityPanContinuous.vue
    │   │   │   ├── CapabilityPanTiltSpeed.vue
    │   │   │   ├── CapabilityPrism.vue
    │   │   │   ├── CapabilityPrismRotation.vue
    │   │   │   ├── CapabilityRotation.vue
    │   │   │   ├── CapabilityShutterStrobe.vue
    │   │   │   ├── CapabilitySoundSensitivity.vue
    │   │   │   ├── CapabilitySpeed.vue
    │   │   │   ├── CapabilityStrobeDuration.vue
    │   │   │   ├── CapabilityStrobeSpeed.vue
    │   │   │   ├── CapabilityTilt.vue
    │   │   │   ├── CapabilityTiltContinuous.vue
    │   │   │   ├── CapabilityTime.vue
    │   │   │   ├── CapabilityWheelRotation.vue
    │   │   │   ├── CapabilityWheelShake.vue
    │   │   │   ├── CapabilityWheelSlot.vue
    │   │   │   ├── CapabilityWheelSlotRotation.vue
    │   │   │   └── CapabilityZoom.vue
    │   │   └── wheel-slots/
    │   │       ├── WheelSlotAnimationGoboEnd.vue
    │   │       ├── WheelSlotAnimationGoboStart.vue
    │   │       ├── WheelSlotClosed.vue
    │   │       ├── WheelSlotColor.vue
    │   │       ├── WheelSlotFrost.vue
    │   │       ├── WheelSlotGobo.vue
    │   │       ├── WheelSlotIris.vue
    │   │       ├── WheelSlotOpen.vue
    │   │       └── WheelSlotPrism.vue
    │   ├── fixture-page/
    │   │   ├── FixturePage.vue
    │   │   ├── FixturePageCapabilityTable.vue
    │   │   ├── FixturePageChannel.vue
    │   │   ├── FixturePageMatrix.vue
    │   │   ├── FixturePageMode.vue
    │   │   ├── FixturePagePhysical.vue
    │   │   └── FixturePageWheel.vue
    │   └── global/
    │       ├── OflSvg.vue
    │       └── OflTime.vue
    ├── layouts/
    │   ├── default.vue
    │   └── error.vue
    ├── pages/
    │   ├── _manufacturerKey/
    │   │   ├── _fixtureKey.vue
    │   │   └── index.vue
    │   ├── about/
    │   │   ├── index.vue
    │   │   └── plugins/
    │   │       ├── _plugin.vue
    │   │       └── index.vue
    │   ├── categories/
    │   │   ├── _category.vue
    │   │   └── index.vue
    │   ├── fixture-editor.vue
    │   ├── import-fixture-file.vue
    │   ├── index.vue
    │   ├── manufacturers.vue
    │   ├── rdm.vue
    │   └── search.vue
    ├── plugins/
    │   ├── global-components.js
    │   └── vue-form.js
    └── static/
        ├── BingSiteAuth.xml
        ├── browserconfig.xml
        ├── fonts/
        │   ├── Inconsolata/
        │   │   └── OFL.txt
        │   └── LatoLatin/
        │       └── OFL.txt
        ├── google02fa8e96cb305d78.html
        └── manifest.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
  "name": "Node.js",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:24",
  "containerEnv": {
    "HOST": "0.0.0.0" // Let Nuxt listen on all interfaces, otherwise container port forwarding doesn't work.
  },
  "postCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && npm install"
}


================================================
FILE: .editorconfig
================================================
# see https://editorconfig.org/

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[server/nginx-site-available]
indent_size = 4


================================================
FILE: .gitattributes
================================================
# make JSON and Markdown files appear in GitHub language bar
# see https://github.com/github/linguist#detectable
fixtures/*/*.json linguist-detectable=true
plugins/*/plugin.json linguist-detectable=true
schemas/*.json linguist-detectable=true
*.md linguist-detectable=true

# enable automatic line ending handling
# see https://rehansaeed.com/gitattributes-best-practices/
* text=auto
*.sh text eol=lf

# explicitly set unix line endings for files that are checked by ESLint
# see https://eslint.org/docs/rules/linebreak-style#using-this-rule-with-version-control-systems
*.cjs text eol=lf
*.js text eol=lf
*.json text eol=lf
*.md text eol=lf
*.ts text eol=lf
*.vue text eol=lf

# mark agent lock files as generated and avoid manual conflict resolution
# see https://github.github.com/gh-aw/reference/glossary/#workflow-lock-file-lockyml
.github/workflows/*.lock.yml linguist-generated=true merge=ours


================================================
FILE: .github/ISSUE_TEMPLATE/new-plugin.md
================================================
---
name: New plugin
about: Open Fixture Library should have a plugin for this fixture format.
title: Add [software / console name] Plugin
labels: new-plugin
assignees: ''

---

XXXXX Please edit all lines that start with "XXXXX".  
XXXXX Short description of the software / desk console using the fixture format.

- **Link to the software / desk console website:**  
  XXXXX
- **Where can fixtures of this format be downloaded?**  
  XXXXX
- **What file type is the fixture format?**  
  XXXXX Binary / XML / JSON / Unknown
- **Does a schema / description / documentation of the format exist?**  
  XXXXX Yes, here: XXXXX / No / Unknown
- **Are there many fixtures of good quality in this format?**  
  XXXXX Yes / No / Unknown
- **Is there any existing work done to parse this format?**  
  XXXXX Yes, here: XXXXX / No / Unknown


================================================
FILE: .github/aw/actions-lock.json
================================================
{
  "entries": {
    "github/gh-aw-actions/setup@v0.68.3": {
      "repo": "github/gh-aw-actions/setup",
      "version": "v0.68.3",
      "sha": "ba90f2186d7ad780ec640f364005fa24e797b360"
    }
  }
}


================================================
FILE: .github/dependabot.yaml
================================================
# See https://help.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    labels:
      - "dependencies"
    groups:
      eslint:
        patterns:
          - "eslint"
          - "@eslint/js"
    ignore:
      - dependency-name: "eslint-plugin-jsdoc"
        update-types:
          - "version-update:semver-patch"
          - "version-update:semver-minor"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
    labels:
      - "dependencies"


================================================
FILE: .github/lighthouserc.json
================================================
{
  "ci": {
    "collect": {
      "startServerCommand": "npm run start",
      "startServerReadyPattern": "Nuxt.js is ready.",
      "startServerReadyTimeout": 20000
    }
  }
}


================================================
FILE: .github/workflows/external-links-checker.yaml
================================================
name: External Links Checker
on:
  schedule:
    - cron: '30 18 * * *' # everyday 18:30 UTC
  workflow_dispatch:
jobs:
  external-links:
    name: Check external links
    if: github.repository == 'OpenLightingProject/open-fixture-library'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
      - name: Setup Node.js v24
        uses: actions/setup-node@v6.4.0
        with:
          node-version: 24
          cache: 'npm'
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Check external links
        run: node --tls-min-v1.0 tests/external-links.js
        env:
          GITHUB_USER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_BROKEN_LINKS_ISSUE_NUMBER: 999
          NODE_ENV: production
      - name: Check Markdown Links in all files
        uses: gaurav-nelson/github-action-markdown-link-check@v1
        with:
          use-verbose-mode: 'yes'


================================================
FILE: .github/workflows/fixture-metadata-triage.lock.yml
================================================
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e3344764c91da961e68d011784f9ac52e21c60848ac0eef547ff7c18e0471ead","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]}
#    ___                   _   _      
#   / _ \                 | | (_)     
#  | |_| | __ _  ___ _ __ | |_ _  ___ 
#  |  _  |/ _` |/ _ \ '_ \| __| |/ __|
#  | | | | (_| |  __/ | | | |_| | (__ 
#  \_| |_/\__, |\___|_| |_|\__|_|\___|
#          __/ |
#  _    _ |___/ 
# | |  | |                / _| |
# | |  | | ___ _ __ _  __| |_| | _____      ____
# | |/\| |/ _ \ '__| |/ /|  _| |/ _ \ \ /\ / / ___|
# \  /\  / (_) | | | | ( | | | | (_) \ V  V /\__ \
#  \/  \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT.
#
# To update this file, edit the corresponding .md file and run:
#   gh aw compile
# Not all edits will cause changes to this file.
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# Daily triage of PRs with new-fixture/via-editor labels to validate fixture JSON metadata
#
# Secrets used:
#   - COPILOT_GITHUB_TOKEN
#   - GH_AW_GITHUB_MCP_SERVER_TOKEN
#   - GH_AW_GITHUB_TOKEN
#   - GITHUB_TOKEN
#
# Custom actions used:
#   - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
#   - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
#   - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
#   - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
#   - github/gh-aw-actions/setup@07c7335cd76c4d4d9f00dd7874f85ff55ed71f24 # v0.71.3
#
# Container images used:
#   - ghcr.io/github/gh-aw-firewall/agent:0.25.20
#   - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20
#   - ghcr.io/github/gh-aw-firewall/squid:0.25.20
#   - ghcr.io/github/gh-aw-mcpg:v0.2.19
#   - ghcr.io/github/github-mcp-server:v0.32.0
#   - node:lts-alpine

name: "Fixture Metadata Triage"
"on":
  workflow_dispatch:
    inputs:
      aw_context:
        default: ""
        description: Agent caller context (used internally by Agentic Workflows).
        required: false
        type: string
      dry-run:
        default: false
        description: If true, report what would be done without making changes
        type: boolean

permissions: {}

concurrency:
  group: "gh-aw-${{ github.workflow }}"

run-name: "Fixture Metadata Triage"

jobs:
  activation:
    runs-on: ubuntu-slim
    permissions:
      actions: read
      contents: read
    outputs:
      comment_id: ""
      comment_repo: ""
      lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
      model: ${{ steps.generate_aw_info.outputs.model }}
      secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
      setup-trace-id: ${{ steps.setup.outputs.trace-id }}
      stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }}
    steps:
      - name: Setup Scripts
        id: setup
        uses: github/gh-aw-actions/setup@07c7335cd76c4d4d9f00dd7874f85ff55ed71f24 # v0.71.3
        with:
          destination: ${{ runner.temp }}/gh-aw/actions
          job-name: ${{ github.job }}
      - name: Generate agentic run info
        id: generate_aw_info
        env:
          GH_AW_INFO_ENGINE_ID: "copilot"
          GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
          GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }}
          GH_AW_INFO_VERSION: "1.0.21"
          GH_AW_INFO_AGENT_VERSION: "1.0.21"
          GH_AW_INFO_CLI_VERSION: "v0.68.3"
          GH_AW_INFO_WORKFLOW_NAME: "Fixture Metadata Triage"
          GH_AW_INFO_EXPERIMENTAL: "false"
          GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
          GH_AW_INFO_STAGED: "false"
          GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
          GH_AW_INFO_FIREWALL_ENABLED: "true"
          GH_AW_INFO_AWF_VERSION: "v0.25.20"
          GH_AW_INFO_AWMG_VERSION: ""
          GH_AW_INFO_FIREWALL_TYPE: "squid"
          GH_AW_COMPILED_STRICT: "true"
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
            await main(core, context);
      - name: Validate COPILOT_GITHUB_TOKEN secret
        id: validate-secret
        run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
        env:
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
      - name: Checkout .github and .agents folders
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
          sparse-checkout: |
            .github
            .agents
          sparse-checkout-cone-mode: true
          fetch-depth: 1
      - name: Check workflow lock file
        id: check-lock-file
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_WORKFLOW_FILE: "fixture-metadata-triage.lock.yml"
          GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
            await main();
      - name: Check compile-agentic version
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_COMPILED_VERSION: "v0.68.3"
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs');
            await main();
      - name: Create prompt with built-in context
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
          GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
          GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
          GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        # poutine:ignore untrusted_checkout_exec
        run: |
          bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
          {
          cat << 'GH_AW_PROMPT_b772d228880039bc_EOF'
          <system>
          GH_AW_PROMPT_b772d228880039bc_EOF
          cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
          cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
          cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
          cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
          cat << 'GH_AW_PROMPT_b772d228880039bc_EOF'
          <safe-output-tools>
          Tools: add_comment, add_labels, missing_tool, missing_data, noop
          </safe-output-tools>
          <github-context>
          The following GitHub context information is available for this workflow:
          {{#if __GH_AW_GITHUB_ACTOR__ }}
          - **actor**: __GH_AW_GITHUB_ACTOR__
          {{/if}}
          {{#if __GH_AW_GITHUB_REPOSITORY__ }}
          - **repository**: __GH_AW_GITHUB_REPOSITORY__
          {{/if}}
          {{#if __GH_AW_GITHUB_WORKSPACE__ }}
          - **workspace**: __GH_AW_GITHUB_WORKSPACE__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
          - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
          - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
          - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
          {{/if}}
          {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
          - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
          {{/if}}
          {{#if __GH_AW_GITHUB_RUN_ID__ }}
          - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
          {{/if}}
          </github-context>
          
          GH_AW_PROMPT_b772d228880039bc_EOF
          cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
          cat << 'GH_AW_PROMPT_b772d228880039bc_EOF'
          </system>
          {{#runtime-import .github/workflows/fixture-metadata-triage.md}}
          GH_AW_PROMPT_b772d228880039bc_EOF
          } > "$GH_AW_PROMPT"
      - name: Interpolate variables and render templates
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
            await main();
      - name: Substitute placeholders
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_GITHUB_ACTOR: ${{ github.actor }}
          GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
          GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
          GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
          GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
          GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
          GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
          GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            
            const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
            
            // Call the substitution function
            return await substitutePlaceholders({
              file: process.env.GH_AW_PROMPT,
              substitutions: {
                GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
                GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
                GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
                GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
                GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
                GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
                GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
                GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE
              }
            });
      - name: Validate prompt placeholders
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        # poutine:ignore untrusted_checkout_exec
        run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
      - name: Print prompt
        env:
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
        # poutine:ignore untrusted_checkout_exec
        run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
      - name: Upload activation artifact
        if: success()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: activation
          path: |
            /tmp/gh-aw/aw_info.json
            /tmp/gh-aw/aw-prompts/prompt.txt
            /tmp/gh-aw/github_rate_limits.jsonl
          if-no-files-found: ignore
          retention-days: 1

  agent:
    needs: activation
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
    env:
      DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
      GH_AW_ASSETS_ALLOWED_EXTS: ""
      GH_AW_ASSETS_BRANCH: ""
      GH_AW_ASSETS_MAX_SIZE_KB: 0
      GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
      GH_AW_WORKFLOW_ID_SANITIZED: fixturemetadatatriage
    outputs:
      agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }}
      checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
      effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }}
      has_patch: ${{ steps.collect_output.outputs.has_patch }}
      inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }}
      mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }}
      model: ${{ needs.activation.outputs.model }}
      model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }}
      output: ${{ steps.collect_output.outputs.output }}
      output_types: ${{ steps.collect_output.outputs.output_types }}
      setup-trace-id: ${{ steps.setup.outputs.trace-id }}
    steps:
      - name: Setup Scripts
        id: setup
        uses: github/gh-aw-actions/setup@07c7335cd76c4d4d9f00dd7874f85ff55ed71f24 # v0.71.3
        with:
          destination: ${{ runner.temp }}/gh-aw/actions
          job-name: ${{ github.job }}
          trace-id: ${{ needs.activation.outputs.setup-trace-id }}
      - name: Set runtime paths
        id: set-runtime-paths
        run: |
          {
            echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
            echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
            echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
          } >> "$GITHUB_OUTPUT"
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Create gh-aw temp directory
        run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh"
      - name: Configure gh CLI for GitHub Enterprise
        run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh"
        env:
          GH_TOKEN: ${{ github.token }}
      - name: Configure Git credentials
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Checkout PR branch
        id: checkout-pr
        if: |
          github.event.pull_request || github.event.issue.pull_request
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
            await main();
      - name: Install GitHub Copilot CLI
        run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21
        env:
          GH_HOST: github.com
      - name: Install AWF binary
        run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20
      - name: Parse integrity filter lists
        id: parse-guard-vars
        env:
          GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }}
          GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }}
          GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }}
        run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh"
      - name: Download container images
        run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine
      - name: Write Safe Outputs Config
        run: |
          mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
          mkdir -p /tmp/gh-aw/safeoutputs
          mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
          cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0d0dfcc4303f7582_EOF'
          {"add_comment":{"max":1},"add_labels":{"allowed":["incomplete"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
          GH_AW_SAFE_OUTPUTS_CONFIG_0d0dfcc4303f7582_EOF
      - name: Write Safe Outputs Tools
        env:
          GH_AW_TOOLS_META_JSON: |
            {
              "description_suffixes": {
                "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading.",
                "add_labels": " CONSTRAINTS: Only these labels are allowed: [\"incomplete\"]."
              },
              "repo_params": {},
              "dynamic_tools": []
            }
          GH_AW_VALIDATION_JSON: |
            {
              "add_comment": {
                "defaultMax": 1,
                "fields": {
                  "body": {
                    "required": true,
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 65000
                  },
                  "item_number": {
                    "issueOrPRNumber": true
                  },
                  "reply_to_id": {
                    "type": "string",
                    "maxLength": 256
                  },
                  "repo": {
                    "type": "string",
                    "maxLength": 256
                  }
                }
              },
              "add_labels": {
                "defaultMax": 5,
                "fields": {
                  "item_number": {
                    "issueNumberOrTemporaryId": true
                  },
                  "labels": {
                    "required": true,
                    "type": "array",
                    "itemType": "string",
                    "itemSanitize": true,
                    "itemMaxLength": 128
                  },
                  "repo": {
                    "type": "string",
                    "maxLength": 256
                  }
                }
              },
              "missing_data": {
                "defaultMax": 20,
                "fields": {
                  "alternatives": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 256
                  },
                  "context": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 256
                  },
                  "data_type": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 128
                  },
                  "reason": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 256
                  }
                }
              },
              "missing_tool": {
                "defaultMax": 20,
                "fields": {
                  "alternatives": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 512
                  },
                  "reason": {
                    "required": true,
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 256
                  },
                  "tool": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 128
                  }
                }
              },
              "noop": {
                "defaultMax": 1,
                "fields": {
                  "message": {
                    "required": true,
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 65000
                  }
                }
              },
              "report_incomplete": {
                "defaultMax": 5,
                "fields": {
                  "details": {
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 65000
                  },
                  "reason": {
                    "required": true,
                    "type": "string",
                    "sanitize": true,
                    "maxLength": 1024
                  }
                }
              }
            }
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs');
            await main();
      - name: Generate Safe Outputs MCP Server Config
        id: safe-outputs-config
        run: |
          # Generate a secure random API key (360 bits of entropy, 40+ chars)
          # Mask immediately to prevent timing vulnerabilities
          API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
          echo "::add-mask::${API_KEY}"
          
          PORT=3001
          
          # Set outputs for next steps
          {
            echo "safe_outputs_api_key=${API_KEY}"
            echo "safe_outputs_port=${PORT}"
          } >> "$GITHUB_OUTPUT"
          
          echo "Safe Outputs MCP server will run on port ${PORT}"
          
      - name: Start Safe Outputs MCP HTTP Server
        id: safe-outputs-start
        env:
          DEBUG: '*'
          GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
          GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
          GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
          GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json
          GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json
          GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
        run: |
          # Environment variables are set above to prevent template injection
          export DEBUG
          export GH_AW_SAFE_OUTPUTS
          export GH_AW_SAFE_OUTPUTS_PORT
          export GH_AW_SAFE_OUTPUTS_API_KEY
          export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
          export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
          export GH_AW_MCP_LOG_DIR
          
          bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh"
          
      - name: Start MCP Gateway
        id: start-mcp-gateway
        env:
          GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
          GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
          GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
          GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
        run: |
          set -eo pipefail
          mkdir -p /tmp/gh-aw/mcp-config
          
          # Export gateway environment variables for MCP config and gateway script
          export MCP_GATEWAY_PORT="80"
          export MCP_GATEWAY_DOMAIN="host.docker.internal"
          MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
          echo "::add-mask::${MCP_GATEWAY_API_KEY}"
          export MCP_GATEWAY_API_KEY
          export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
          mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
          export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
          export DEBUG="*"
          
          export GH_AW_ENGINE="copilot"
          export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.19'
          
          mkdir -p /home/runner/.copilot
          cat << GH_AW_MCP_CONFIG_08bc358d5f12b180_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh"
          {
            "mcpServers": {
              "github": {
                "type": "stdio",
                "container": "ghcr.io/github/github-mcp-server:v0.32.0",
                "env": {
                  "GITHUB_HOST": "\${GITHUB_SERVER_URL}",
                  "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
                  "GITHUB_READ_ONLY": "1",
                  "GITHUB_TOOLSETS": "pull_requests,repos"
                },
                "guard-policies": {
                  "allow-only": {
                    "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }},
                    "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }},
                    "min-integrity": "approved",
                    "repos": "all",
                    "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }}
                  }
                }
              },
              "safeoutputs": {
                "type": "http",
                "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
                "headers": {
                  "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
                },
                "guard-policies": {
                  "write-sink": {
                    "accept": [
                      "*"
                    ]
                  }
                }
              }
            },
            "gateway": {
              "port": $MCP_GATEWAY_PORT,
              "domain": "${MCP_GATEWAY_DOMAIN}",
              "apiKey": "${MCP_GATEWAY_API_KEY}",
              "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
            }
          }
          GH_AW_MCP_CONFIG_08bc358d5f12b180_EOF
      - name: Download activation artifact
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: activation
          path: /tmp/gh-aw
      - name: Clean git credentials
        continue-on-error: true
        run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh"
      - name: Execute GitHub Copilot CLI
        id: agentic_execution
        # Copilot CLI tool arguments (sorted):
        timeout-minutes: 20
        run: |
          set -o pipefail
          touch /tmp/gh-aw/agent-step-summary.md
          (umask 177 && touch /tmp/gh-aw/agent-stdio.log)
          # shellcheck disable=SC1003
          sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \
            -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
        env:
          COPILOT_AGENT_RUNNER_TYPE: STANDALONE
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
          GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
          GH_AW_PHASE: agent
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
          GH_AW_VERSION: v0.68.3
          GITHUB_API_URL: ${{ github.api_url }}
          GITHUB_AW: true
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          GITHUB_REF_NAME: ${{ github.ref_name }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
          GITHUB_WORKSPACE: ${{ github.workspace }}
          GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
          GIT_AUTHOR_NAME: github-actions[bot]
          GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
          GIT_COMMITTER_NAME: github-actions[bot]
          XDG_CONFIG_HOME: /home/runner
      - name: Detect Copilot errors
        id: detect-copilot-errors
        if: always()
        continue-on-error: true
        run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs"
      - name: Configure Git credentials
        env:
          REPO_NAME: ${{ github.repository }}
          SERVER_URL: ${{ github.server_url }}
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git config --global am.keepcr true
          # Re-authenticate git with GitHub token
          SERVER_URL_STRIPPED="${SERVER_URL#https://}"
          git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
          echo "Git configured with standard GitHub Actions identity"
      - name: Copy Copilot session state files to logs
        if: always()
        continue-on-error: true
        run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh"
      - name: Stop MCP Gateway
        if: always()
        continue-on-error: true
        env:
          MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
          MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
          GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
        run: |
          bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
      - name: Redact secrets in logs
        if: always()
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
            await main();
        env:
          GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
          SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
          SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
          SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Append agent step summary
        if: always()
        run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh"
      - name: Copy Safe Outputs
        if: always()
        env:
          GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
        run: |
          mkdir -p /tmp/gh-aw
          cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true
      - name: Ingest agent output
        id: collect_output
        if: always()
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
          GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_API_URL: ${{ github.api_url }}
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
            await main();
      - name: Parse agent logs for step summary
        if: always()
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
            await main();
      - name: Parse MCP Gateway logs for step summary
        if: always()
        id: parse-mcp-gateway
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
            await main();
      - name: Print firewall logs
        if: always()
        continue-on-error: true
        env:
          AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
        run: |
          # Fix permissions on firewall logs so they can be uploaded as artifacts
          # AWF runs with sudo, creating files owned by root
          sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
          # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
          if command -v awf &> /dev/null; then
            awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
          else
            echo 'AWF binary not installed, skipping firewall log summary'
          fi
      - name: Parse token usage for step summary
        if: always()
        continue-on-error: true
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs');
            await main();
      - name: Write agent output placeholder if missing
        if: always()
        run: |
          if [ ! -f /tmp/gh-aw/agent_output.json ]; then
            echo '{"items":[]}' > /tmp/gh-aw/agent_output.json
          fi
      - name: Upload agent artifacts
        if: always()
        continue-on-error: true
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: agent
          path: |
            /tmp/gh-aw/aw-prompts/prompt.txt
            /tmp/gh-aw/sandbox/agent/logs/
            /tmp/gh-aw/redacted-urls.log
            /tmp/gh-aw/mcp-logs/
            /tmp/gh-aw/proxy-logs/
            !/tmp/gh-aw/proxy-logs/proxy-tls/
            /tmp/gh-aw/agent_usage.json
            /tmp/gh-aw/agent-stdio.log
            /tmp/gh-aw/agent/
            /tmp/gh-aw/github_rate_limits.jsonl
            /tmp/gh-aw/safeoutputs.jsonl
            /tmp/gh-aw/agent_output.json
            /tmp/gh-aw/aw-*.patch
            /tmp/gh-aw/aw-*.bundle
            /tmp/gh-aw/sandbox/firewall/logs/
            /tmp/gh-aw/sandbox/firewall/audit/
          if-no-files-found: ignore

  conclusion:
    needs:
      - activation
      - agent
      - detection
      - safe_outputs
    if: >
      always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
      needs.activation.outputs.stale_lock_file_failed == 'true')
    runs-on: ubuntu-slim
    permissions:
      contents: read
      discussions: write
      issues: write
      pull-requests: write
    concurrency:
      group: "gh-aw-conclusion-fixture-metadata-triage"
      cancel-in-progress: false
    outputs:
      incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }}
      noop_message: ${{ steps.noop.outputs.noop_message }}
      tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
      total_count: ${{ steps.missing_tool.outputs.total_count }}
    steps:
      - name: Setup Scripts
        id: setup
        uses: github/gh-aw-actions/setup@07c7335cd76c4d4d9f00dd7874f85ff55ed71f24 # v0.71.3
        with:
          destination: ${{ runner.temp }}/gh-aw/actions
          job-name: ${{ github.job }}
          trace-id: ${{ needs.activation.outputs.setup-trace-id }}
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: agent
          path: /tmp/gh-aw/
      - name: Setup agent output environment variable
        id: setup-agent-output-env
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/
          find "/tmp/gh-aw/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
      - name: Process no-op messages
        id: noop
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
          GH_AW_NOOP_MAX: "1"
          GH_AW_WORKFLOW_NAME: "Fixture Metadata Triage"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
          GH_AW_NOOP_REPORT_AS_ISSUE: "true"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
            await main();
      - name: Log detection run
        id: detection_runs
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Fixture Metadata Triage"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
          GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs');
            await main();
      - name: Record missing tool
        id: missing_tool
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
          GH_AW_MISSING_TOOL_CREATE_ISSUE: "true"
          GH_AW_WORKFLOW_NAME: "Fixture Metadata Triage"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
            await main();
      - name: Record incomplete
        id: report_incomplete
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
          GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true"
          GH_AW_WORKFLOW_NAME: "Fixture Metadata Triage"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs');
            await main();
      - name: Handle agent failure
        id: handle_agent_failure
        if: always()
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
          GH_AW_WORKFLOW_NAME: "Fixture Metadata Triage"
          GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
          GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
          GH_AW_WORKFLOW_ID: "fixture-metadata-triage"
          GH_AW_ENGINE_ID: "copilot"
          GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
          GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
          GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
          GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }}
          GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
          GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
          GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
          GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
          GH_AW_GROUP_REPORTS: "false"
          GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
          GH_AW_TIMEOUT_MINUTES: "20"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
            await main();

  detection:
    needs:
      - activation
      - agent
    if: >
      always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true')
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
      detection_reason: ${{ steps.detection_conclusion.outputs.reason }}
      detection_success: ${{ steps.detection_conclusion.outputs.success }}
    steps:
      - name: Setup Scripts
        id: setup
        uses: github/gh-aw-actions/setup@07c7335cd76c4d4d9f00dd7874f85ff55ed71f24 # v0.71.3
        with:
          destination: ${{ runner.temp }}/gh-aw/actions
          job-name: ${{ github.job }}
          trace-id: ${{ needs.activation.outputs.setup-trace-id }}
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: agent
          path: /tmp/gh-aw/
      - name: Setup agent output environment variable
        id: setup-agent-output-env
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/
          find "/tmp/gh-aw/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
      - name: Checkout repository for patch context
        if: needs.agent.outputs.has_patch == 'true'
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      # --- Threat Detection ---
      - name: Clean stale firewall files from agent artifact
        run: |
          rm -rf /tmp/gh-aw/sandbox/firewall/logs
          rm -rf /tmp/gh-aw/sandbox/firewall/audit
      - name: Download container images
        run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20
      - name: Check if detection needed
        id: detection_guard
        if: always()
        env:
          OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
          HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
        run: |
          if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
            echo "run_detection=true" >> "$GITHUB_OUTPUT"
            echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
          else
            echo "run_detection=false" >> "$GITHUB_OUTPUT"
            echo "Detection skipped: no agent outputs or patches to analyze"
          fi
      - name: Clear MCP configuration for detection
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
          rm -f /home/runner/.copilot/mcp-config.json
          rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
      - name: Prepare threat detection files
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
          cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
          cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
          for f in /tmp/gh-aw/aw-*.patch; do
            [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
          done
          for f in /tmp/gh-aw/aw-*.bundle; do
            [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
          done
          echo "Prepared threat detection files:"
          ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
      - name: Setup threat detection
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          WORKFLOW_NAME: "Fixture Metadata Triage"
          WORKFLOW_DESCRIPTION: "Daily triage of PRs with new-fixture/via-editor labels to validate fixture JSON metadata"
          HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
            await main();
      - name: Ensure threat-detection directory and log
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        run: |
          mkdir -p /tmp/gh-aw/threat-detection
          touch /tmp/gh-aw/threat-detection/detection.log
      - name: Install GitHub Copilot CLI
        run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21
        env:
          GH_HOST: github.com
      - name: Install AWF binary
        run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20
      - name: Execute GitHub Copilot CLI
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        id: detection_agentic_execution
        # Copilot CLI tool arguments (sorted):
        timeout-minutes: 20
        run: |
          set -o pipefail
          touch /tmp/gh-aw/agent-step-summary.md
          (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
          # shellcheck disable=SC1003
          sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \
            -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
        env:
          COPILOT_AGENT_RUNNER_TYPE: STANDALONE
          COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
          COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
          GH_AW_PHASE: detection
          GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
          GH_AW_VERSION: v0.68.3
          GITHUB_API_URL: ${{ github.api_url }}
          GITHUB_AW: true
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_REF_NAME: ${{ github.ref_name }}
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
          GITHUB_WORKSPACE: ${{ github.workspace }}
          GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
          GIT_AUTHOR_NAME: github-actions[bot]
          GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
          GIT_COMMITTER_NAME: github-actions[bot]
          XDG_CONFIG_HOME: /home/runner
      - name: Upload threat detection log
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: detection
          path: /tmp/gh-aw/threat-detection/detection.log
          if-no-files-found: ignore
      - name: Parse and conclude threat detection
        id: detection_conclusion
        if: always()
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
          GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
        with:
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
            await main();

  safe_outputs:
    needs:
      - activation
      - agent
      - detection
    if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
    runs-on: ubuntu-slim
    permissions:
      contents: read
      discussions: write
      issues: write
      pull-requests: write
    timeout-minutes: 15
    env:
      GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/fixture-metadata-triage"
      GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
      GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
      GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
      GH_AW_ENGINE_ID: "copilot"
      GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
      GH_AW_WORKFLOW_ID: "fixture-metadata-triage"
      GH_AW_WORKFLOW_NAME: "Fixture Metadata Triage"
    outputs:
      code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
      code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
      comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }}
      comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
      create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
      create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
      process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
      process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
    steps:
      - name: Setup Scripts
        id: setup
        uses: github/gh-aw-actions/setup@07c7335cd76c4d4d9f00dd7874f85ff55ed71f24 # v0.71.3
        with:
          destination: ${{ runner.temp }}/gh-aw/actions
          job-name: ${{ github.job }}
          trace-id: ${{ needs.activation.outputs.setup-trace-id }}
      - name: Download agent output artifact
        id: download-agent-output
        continue-on-error: true
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: agent
          path: /tmp/gh-aw/
      - name: Setup agent output environment variable
        id: setup-agent-output-env
        if: steps.download-agent-output.outcome == 'success'
        run: |
          mkdir -p /tmp/gh-aw/
          find "/tmp/gh-aw/" -type f -print
          echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
      - name: Configure GH_HOST for enterprise compatibility
        id: ghes-host-config
        shell: bash
        run: |
          # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
          # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
          GH_HOST="${GITHUB_SERVER_URL#https://}"
          GH_HOST="${GH_HOST#http://}"
          echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
      - name: Process Safe Outputs
        id: process_safe_outputs
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
        env:
          GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
          GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
          GITHUB_SERVER_URL: ${{ github.server_url }}
          GITHUB_API_URL: ${{ github.api_url }}
          GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"incomplete\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
        with:
          github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
          script: |
            const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
            setupGlobals(core, github, context, exec, io, getOctokit);
            const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
            await main();
      - name: Upload Safe Outputs Items
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: safe-outputs-items
          path: |
            /tmp/gh-aw/safe-output-items.jsonl
            /tmp/gh-aw/temporary-id-map.json
          if-no-files-found: ignore



================================================
FILE: .github/workflows/fixture-metadata-triage.md
================================================
---
name: Fixture Metadata Triage
description: Daily triage of PRs with new-fixture/via-editor labels to validate fixture JSON metadata

on:
  workflow_dispatch:
    inputs:
      dry-run:
        type: boolean
        description: If true, report what would be done without making changes
        default: false

permissions:
  contents: read
  pull-requests: read

safe-outputs:
  add-comment:
    pull-requests: true
  add-labels:
    allowed: [incomplete]

tools:
  github:
    min-integrity: approved
    toolsets: [pull_requests, repos]
---

# Fixture Metadata Triage Agent

You are an automated triage system for PRs adding new fixtures to the Open Fixture Library. Your task is to identify PRs that have changed fixture JSON files but are missing required metadata.

## Your Mission

1. Find all open PRs with BOTH labels: `new-fixture` AND `via-editor`
2. Exclude any PRs that have already been reviewed manually (by a human)
3. Exclude any PRs that already have the `incomplete` label (already processed)
4. For each PR, get the list of changed files
5. Identify JSON files that follow the fixture schema
6. Check for required metadata (links.manual OR links.productPage)
7. Flag incomplete PRs by adding the `incomplete` label and commenting

## Triage Rules

- **Skip** a PR if:

  - Already has the `incomplete` label
  - Has any existing reviews
  - No files match the fixture schema ("$schema": ".../schemas/fixture.json")

- **Flag** a PR if:
  - Modified at least one file with the fixture schema
  - AND that file has empty/missing `links` object
  - OR both `links.manual` AND `links.productPage` are missing or empty

## Validation Logic

For each fixture JSON file in the PR:

1. Check if file contains `"$schema": ".../schemas/fixture.json"`
2. Parse the `links` object
3. If `links.manual` EXISTS and is not empty → valid
4. If `links.productPage` EXISTS and is not empty → valid
5. If NEITHER exists OR both are empty → flag the PR

## Actions on Flagged PR

Only if `dry-run` input is NOT true:

1. Add label: `incomplete`
2. Add comment: "Missing required metadata: Manual and Product Page"

If `dry-run` IS true, only report what would be done - do NOT add labels or comments.

## Output

Generate a report:

- Total PRs checked this run
- PRs skipped (no fixture files or already labelled)
- PRs flagged (incomplete metadata)
- PRs valid (have metadata)


================================================
FILE: .github/workflows/lighthouse-production.yaml
================================================
name: Lighthouse
on:
  schedule:
    - cron: '30 18 * * *' # everyday 18:30 UTC
  workflow_dispatch:
jobs:
  production:
    name: Production
    runs-on: ubuntu-latest
    if: github.repository == 'OpenLightingProject/open-fixture-library'
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
      - name: Run the Lighthouse CI
        uses: treosh/lighthouse-ci-action@12.6.2
        with:
          urls: |
            https://open-fixture-library.org/
            https://open-fixture-library.org/chroma-q/color-force-ii-72
            https://open-fixture-library.org/categories/Color%20Changer
          runs: 3
          uploadArtifacts: true
          temporaryPublicStorage: true


================================================
FILE: .github/workflows/lighthouse-review.yaml
================================================
name: Lighthouse
on:
  pull_request:
    types: [opened, synchronize, reopened, labeled]
concurrency:
  group: lighthouse-review-${{ github.ref }}
  cancel-in-progress: true
jobs:
  review:
    name: Review
    runs-on: ubuntu-latest
    if: contains(github.event.pull_request.labels.*.name, 'component-ui')
    env:
      NODE_ENV: production
      ALLOW_SEARCH_INDEXING: allowed
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Setup Node.js v24
        uses: actions/setup-node@v6.4.0
        with:
          node-version: 24
          cache: 'npm'
      - name: Install dependencies
        run: npm ci --include=dev
      - name: Build
        run: npm run build
      - name: Run the Lighthouse CI
        uses: treosh/lighthouse-ci-action@12.6.2
        with:
          urls: |
            http://localhost:3000/
            http://localhost:3000/chroma-q/color-force-ii-72
            http://localhost:3000/categories/Color%20Changer
          runs: 1
          configPath: '.github/lighthouserc.json'
          uploadArtifacts: true
          temporaryPublicStorage: true
        env:
          LHCI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_PR_NUMBER: ${{ github.event.number }}


================================================
FILE: .github/workflows/test.yaml
================================================
name: Test
on: pull_request
concurrency:
  group: test-${{ github.ref }}
  cancel-in-progress: true
jobs:
  required:
    name: Required
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
      - name: Setup Node.js v24
        uses: actions/setup-node@v6.4.0
        with:
          node-version: 24
          cache: 'npm'
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Run ESLint
        run: npm run lint:eslint
      - name: Run Stylelint
        run: npm run lint:stylelint -- --formatter compact
      - name: Run dmx-value-scaling test
        run: npm run test:dmx-value-scaling
      - name: Run fixtures-valid test
        run: npm run test:fixtures-valid
      - name: Run http-status test
        run: npm run test:http-status
      - name: Run built-files-committed test
        run: npm run test:built-files-committed
  status:
    name: Status
    runs-on: ubuntu-latest
    continue-on-error: true
    env:
      GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
      GITHUB_PR_HEAD_REF: origin/${{ github.head_ref }}
      GITHUB_PR_BASE_REF: origin/${{ github.base_ref }}
      GITHUB_USER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
        with:
          fetch-depth: 0 # complete history for all branches and tags
      - name: Setup Node.js v24
        uses: actions/setup-node@v6.4.0
        with:
          node-version: 24
          cache: 'npm'
      - name: Install dependencies
        run: |
          sudo pip install lxml # for QLC+ fixture-tool-validation export test
          npm ci
      - name: Build
        run: npm run build
      - name: Run export-diff test
        run: node tests/github/export-diff.js
      - name: Run exports-valid test
        run: node tests/github/exports-valid.js
      - name: Run schema-version-reminder
        run: node tests/github/schema-version-reminder.js
  markdown-link-check:
    name: Markdown Links
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
      - name: Check Markdown Links in modified files
        uses: gaurav-nelson/github-action-markdown-link-check@v1
        with:
          use-verbose-mode: 'yes'
          check-modified-files-only: 'yes'
          base-branch: 'master'


================================================
FILE: .gitignore
================================================
# Node build artifacts
node_modules/
npm-debug.log

# Local development
*.env
*.dev.*
.DS_Store
*.sublime-*
.vscode/
.idea/

# Docker
Dockerfile
docker-compose.yml

# Generated files
.nuxt/
fixtures/register.json

# Temporary files
tmp/
.eslintcache

# secret files
server/ofl-secrets.json
server/*.log


================================================
FILE: .stylelintrc.yaml
================================================
reportNeedlessDisables: true
reportInvalidScopeDisables: true
reportDescriptionlessDisables: true
extends:
  - '@stylistic/stylelint-config'
  - stylelint-config-standard-scss
  - stylelint-config-html/vue
  - stylelint-config-recess-order
rules:
  # `without-alpha` not supported by Sass
  color-function-alias-notation: with-alpha

  # `modern` not supported by Sass
  color-function-notation: legacy

  # always long to be consistent
  color-hex-length: long

  # always numeric to be consistent
  font-weight-notation: numeric

  # avoid using non-global functions
  function-no-unknown:
    - true
    - ignoreFunctions:
      - theme-color
      - color-to-rgb-string
      - /^[a-z]+\.[a-z\-]+$/ # all `@use`d Sass functions

  # causes false positives with Sass
  nesting-selector-no-missing-scoping-root: null

  # decrease severity for this rule only
  no-descending-specificity:
    - true
    - severity: warning

  # allow Vue pseudo elements
  selector-pseudo-element-no-unknown:
    - true
    - ignorePseudoElements: ["v-deep"]

  # simplify rules
  scss/block-no-redundant-nesting: true

  # prevent comments in generated CSS
  scss/comment-no-loud: true

  # prevent deprecated color functions
  scss/function-color-channel: true

  # make imported files more explicit
  scss/load-partial-extension: always

  # special `@if`/`@else` formatting
  '@stylistic/block-closing-brace-newline-after':
    - always
    - ignoreAtRules:
      - if
      - else

  # allow grouping in Sass lists
  '@stylistic/value-list-max-empty-lines': 1


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Florian & Felix Edelmann

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Open Fixture Library<br><a href="https://open-fixture-library.org/">open-fixture-library.org</a>

[![Mozilla HTTP Observatory Grade](https://img.shields.io/mozilla-observatory/grade-score/open-fixture-library.org?publish)](https://developer.mozilla.org/en-US/observatory/analyze?host=open-fixture-library.org)
[![Beacon](https://img.shields.io/badge/dynamic/json?color=blue&label=Beacon&query=%24.co2&suffix=%20CO%E2%82%82%2Fview&url=https%3A%2F%2Fdigitalbeacon.co%2Fbadge%3Furl%3Dhttps%253A%252F%252Fopen-fixture-library.org&cacheSeconds=604800)](https://digitalbeacon.co/report/open-fixture-library-org)

[<img alt="OFL logo" src="https://cdn.rawgit.com/OpenLightingProject/open-fixture-library/master/ui/static/ofl-logo.svg" width="250" />](ui/static/ofl-logo.svg)

To use lighting control software like [QLC+](https://www.qlcplus.org/), [DMXControl](https://www.dmxcontrol.org/) or [e:cue](https://www.osram.de/ecue/), you need fixture definition files that describe your lighting hardware. Since one software can usually only understand its own fixture definition format, switching between different programs can be difficult.

The *Open Fixture Library* tries to solve this problem by collecting fixture definitions and making them downloadable in various formats. Internally, it uses a [JSON format](docs/fixture-format.md) that tries to bundle as much information as possible for all the different output formats.


## Contribute without coding

The easiest way to help: Head over to the online [Fixture Editor](https://open-fixture-library.org/fixture-editor) and add your favorite fixture that is not yet included in our library!

There are also [other ways you can help without coding](docs/CONTRIBUTING.md#how-you-can-help).

**🙏 Help wanted!** There are [a lot of pull requests](https://github.com/OpenLightingProject/open-fixture-library/pulls?q=is%3Apr+is%3Aopen+label%3Anew-fixture+sort%3Aupdated-desc) for new fixtures that are not yet reviewed and merged. Reviewing them (and maybe fixing smaller issues) helps get this number down and the number of fixtures in OFL up! See the [step-by-step instructions for fixture reviews](docs/CONTRIBUTING.md#fixtures).


## Contribute code

See [CONTRIBUTING.md](docs/CONTRIBUTING.md) and our [Developer Documentation](docs/README.md).


================================================
FILE: cli/build-plugin-data.js
================================================
#!/usr/bin/env node

import { readdir, writeFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { styleText } from 'util';
import importJson from '../lib/import-json.js';

const plugins = {
  importPlugins: [],
  exportPlugins: [],
  data: {},
};

const allPreviousVersions = {};

const pluginDirectoryUrl = new URL('../plugins/', import.meta.url);

const directoryEntries = await readdir(pluginDirectoryUrl, { withFileTypes: true });
const pluginKeys = directoryEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);

for (const pluginKey of pluginKeys) {
  plugins.data[pluginKey] = {};

  await readPluginJson(pluginKey);
  await readPluginImport(pluginKey);
  await readPluginExport(pluginKey);
  await readPluginExportTests(pluginKey);
}

for (const [key, data] of Object.entries(allPreviousVersions)) {
  if (key in plugins.data) {
    plugins.data[key].newPlugin = data.newPlugin;
  }
  else {
    plugins.data[key] = data;
  }
}

// sort plugin data object by key
plugins.data = Object.fromEntries(
  Object.keys(plugins.data).toSorted().map((key) => [key, plugins.data[key]]),
);

const filePath = fileURLToPath(new URL('plugins.json', pluginDirectoryUrl));

try {
  await writeFile(filePath, `${JSON.stringify(plugins, null, 2)}\n`, 'utf-8');
  console.log(styleText('green', '[Success]'), 'Updated plugin data file', filePath);
  process.exit(0);
}
catch (error) {
  console.error(styleText('red', '[Fail]'), 'Could not write plugin data file.', error);
  process.exit(1);
}

/**
 * Reads information from the plugin's `plugin.json` file into `plugins` and `allPreviousVersions`.
 * @param {string} pluginKey The plugin key.
 */
async function readPluginJson(pluginKey) {
  try {
    const pluginJson = await importJson(`${pluginKey}/plugin.json`, pluginDirectoryUrl);
    plugins.data[pluginKey].name = pluginJson.name;

    if (pluginJson.previousVersions) {
      for (const [key, name] of Object.entries(pluginJson.previousVersions)) {
        allPreviousVersions[key] = {
          name,
          outdated: true,
          newPlugin: pluginKey,
        };
      }
    }
  }
  catch (error) {
    console.error(`Plugin ${pluginKey} does not contain a valid plugin.json file:`, error);
    process.exit(1);
  }
}

/**
 * Reads information from the plugin's `import.js` file (if it exists) into `plugins`.
 * @param {string} pluginKey The plugin key.
 */
async function readPluginImport(pluginKey) {
  try {
    const importPlugin = await import(new URL(`${pluginKey}/import.js`, pluginDirectoryUrl));
    plugins.importPlugins.push(pluginKey);
    plugins.data[pluginKey].importPluginVersion = importPlugin.version;
  }
  catch (error) {
    if (error.code === 'ERR_MODULE_NOT_FOUND') {
      // ignore non-existing file
      return;
    }

    console.error(`Import plugin ${pluginKey} could not be parsed:`, error.message);
    process.exit(1);
  }
}

/**
 * Reads information from the plugin's `export.js` file (if it exists) into `plugins`.
 * @param {string} pluginKey The plugin key.
 */
async function readPluginExport(pluginKey) {
  try {
    const exportPlugin = await import(new URL(`${pluginKey}/export.js`, pluginDirectoryUrl));
    plugins.exportPlugins.push(pluginKey);
    plugins.data[pluginKey].exportPluginVersion = exportPlugin.version;
    plugins.data[pluginKey].exportTests = [];
  }
  catch (error) {
    if (error.code === 'ERR_MODULE_NOT_FOUND') {
      // ignore non-existing file
      return;
    }

    console.error(`Export plugin ${pluginKey} could not be parsed:`, error.message);
    process.exit(1);
  }
}

/**
 * Adds the plugin's export tests (if any) to `plugins`.
 * @param {string} pluginKey The plugin key.
 */
async function readPluginExportTests(pluginKey) {
  try {
    const exportTestFiles = await readdir(new URL(`${pluginKey}/exportTests/`, pluginDirectoryUrl));
    for (const test of exportTestFiles) {
      const testKey = path.basename(test, path.extname(test));
      plugins.data[pluginKey].exportTests.push(testKey);
    }
  }
  catch (error) {
    if (error.code === 'ENOENT') {
      // ignore non-existing directory
      return;
    }

    console.error(`Export tests for plugin ${pluginKey} could not be read:`, error.message);
    process.exit(1);
  }
}


================================================
FILE: cli/build-register.js
================================================
#!/usr/bin/env node

import { readdir, writeFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { styleText } from 'util';
import importJson from '../lib/import-json.js';
import { Register } from '../lib/register.js';

let register;
let manufacturers;

const fixturesPath = fileURLToPath(new URL('../fixtures/', import.meta.url));

try {
  manufacturers = await importJson('../fixtures/manufacturers.json', import.meta.url);
  register = new Register(manufacturers);

  await addFixturesToRegister();
}
catch (readError) {
  console.error('Read error:', readError);
  process.exit(1);
}

const registerFilename = path.join(fixturesPath, (process.argv.length === 3 ? process.argv[2] : 'register.json'));
const fileContents = `${JSON.stringify(register.getAsSortedObject(), null, 2)}\n`;

try {
  await writeFile(registerFilename, fileContents, 'utf-8');
  console.log(styleText('green', '[Success]'), 'Updated register file', registerFilename);
  process.exit(0);
}
catch (error) {
  console.error(styleText('red', '[Fail]'), 'Could not write register file.', error);
  process.exit(1);
}

/**
 * Loop through all manufacturer directories and fixture files and add them to the register.
 */
async function addFixturesToRegister() {
  const directoryEntries = await readdir(fixturesPath, { withFileTypes: true });
  const manufacturerKeys = directoryEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);

  for (const manufacturerKey of manufacturerKeys) {
    register.addManufacturer(manufacturerKey, manufacturers[manufacturerKey]);

    const manufacturerDirectory = path.join(fixturesPath, manufacturerKey);
    const fixtureFiles = await readdir(manufacturerDirectory);
    for (const filename of fixtureFiles) {
      if (path.extname(filename) !== '.json') {
        continue;
      }

      const fixtureKey = path.basename(filename, '.json');
      const fixtureData = await importJson(`${manufacturerKey}/${filename}`, fixturesPath);

      if (fixtureData.$schema.endsWith('/fixture-redirect.json')) {
        const redirectToData = await importJson(`${fixtureData.redirectTo}.json`, fixturesPath);

        register.addFixtureRedirect(manufacturerKey, fixtureKey, fixtureData, redirectToData);
      }
      else {
        register.addFixture(manufacturerKey, fixtureKey, fixtureData);
      }
    }
  }
}


================================================
FILE: cli/build-test-fixtures.js
================================================
#!/usr/bin/env node

/**
 * @fileoverview This script generates a set of test fixtures that cover all defined fixture features (while
 * keeping the set as small as possible) and updates tests/test-fixtures.json and tests/test-fixtures.md.
 */

import { readdir, writeFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { styleText } from 'util';
import importJson from '../lib/import-json.js';
import { fixtureFromRepository } from '../lib/model.js';

const fixtureFeaturesDirectoryUrl = new URL('../lib/fixture-features/', import.meta.url);
const jsonPath = fileURLToPath(new URL('../tests/test-fixtures.json', import.meta.url));
const markdownPath = fileURLToPath(new URL('../tests/test-fixtures.md', import.meta.url));

/**
 * @typedef {object} FixtureFeature
 * @property {string | undefined} id The fixture feature's ID
 * @property {string} name A short name of the fixture feature.
 * @property {string} description A longer description of the fixture feature.
 * @property {(fixture: Fixture) => boolean | Promise<boolean>} hasFeature A function that returns whether a given fixture supports this feature.
 */

/**
 * @typedef {object} FixtureFeatureResult
 * @property {string} man The fixture manufacturer's name.
 * @property {string} key The combined manufacturer/fixture key.
 * @property {string} name The fixture name.
 * @property {string[]} features The IDs of all fixture features that the fixture supports.
 */

const register = await importJson('../fixtures/register.json', import.meta.url);

const allFixtureFeatures = await getFixtureFeatures();
const featuresUsed = Object.fromEntries(allFixtureFeatures.map((feature) => [feature.id, 0]));// check which features each fixture supports

/** @type {FixtureFeatureResult[]} */
let fixtureFeatureResults = [];

for (const manufacturerFixture of Object.keys(register.filesystem)) {
  const [manufacturerKey, fixtureKey] = manufacturerFixture.split('/');

  // pre-process data
  const fixture = await fixtureFromRepository(manufacturerKey, fixtureKey);
  const fixtureResult = {
    man: manufacturerKey,
    key: fixtureKey,
    name: fixture.name,
    features: [],
  };

  // check all features
  for (const fixtureFeature of allFixtureFeatures) {
    if (await fixtureFeature.hasFeature(fixture)) {
      fixtureResult.features.push(fixtureFeature.id);
      featuresUsed[fixtureFeature.id]++;
    }
  }

  fixtureFeatureResults.push(fixtureResult);
}

// first fixtures are more likely to be filtered out, so we start with the ones with the fewest features
fixtureFeatureResults.sort((a, b) => {
  if (a.features.length === b.features.length) {
    return `${a.man}/${a.key}`.localeCompare(`${b.man}/${b.key}`, 'en');
  }

  return a.features.length - b.features.length;
});

// filter out
fixtureFeatureResults = fixtureFeatureResults.filter((fixture) => {
  for (const feature of fixture.features) {
    // this is the only remaining fixture with that feature -> keep it
    if (featuresUsed[feature] === 1) {
      return true;
    }
  }
  // has no new features -> filter out
  for (const feature of fixture.features) {
    featuresUsed[feature]--;
  }
  return false;
});

// original alphabetic ordering
fixtureFeatureResults.sort((a, b) => {
  return `${a.man}/${a.key}`.localeCompare(`${b.man}/${b.key}`, 'en');
});

console.log(styleText('yellow', 'Generated list of test fixtures:'));
for (const fixture of fixtureFeatureResults) {
  console.log(` - ${fixture.man}/${fixture.key}`);
}

try {
  await writeFile(jsonPath, `${JSON.stringify(fixtureFeatureResults, null, 2)}\n`, 'utf-8');
  console.log(styleText('green', '[Success]'), `Updated ${jsonPath}`);

  await writeFile(markdownPath, await getMarkdownCode(fixtureFeatureResults, allFixtureFeatures), 'utf-8');
  console.log(styleText('green', '[Success]'), `Updated ${markdownPath}`);
}
catch (error) {
  console.error(styleText('red', '[Fail]'), 'Could not write test fixtures file:', error);
}

/**
 * @returns {Promise<FixtureFeature[]>} A Promise that resolves to an array of all defined fixture features.
 */
async function getFixtureFeatures() {
  const fixtureFeatures = [];

  for (const fileName of await readdir(fixtureFeaturesDirectoryUrl)) {
    if (path.extname(fileName) !== '.js') {
      continue;
    }

    // module exports array of fix features
    const fixtureFeatureFileUrl = new URL(fileName, fixtureFeaturesDirectoryUrl);
    const { default: fixtureFeatureFile } = await import(fixtureFeatureFileUrl);

    for (const [index, fixtureFeature] of fixtureFeatureFile.entries()) {
      // default id
      if (!('id' in fixtureFeature)) {
        fixtureFeature.id = path.basename(fileName, '.js');
        if (fixtureFeatureFile.length > 1) {
          fixtureFeature.id += `-${index}`;
        }
      }

      // check uniqueness of id
      const featureIdExists = fixtureFeatures.some((feature) => feature.id === fixtureFeature.id);
      if (featureIdExists) {
        console.error(styleText('red', '[Error]'), `Fixture feature id '${fixtureFeature.id}' is used multiple times.`);
        process.exit(1);
      }

      fixtureFeatures.push(fixtureFeature);
    }
  }

  return fixtureFeatures;
}

/**
 * Generates a markdown table presenting the test fixtures and all fix features.
 * @param {FixtureFeatureResult[]} fixtures The fixture feature results.
 * @param {FixtureFeature[]} fixtureFeatures All fixture features.
 * @returns {Promise<string>} A Promise that resolves to the markdown code to be used in a markdown file.
 */
async function getMarkdownCode(fixtures, fixtureFeatures) {
  const manufacturers = await importJson('../fixtures/manufacturers.json', import.meta.url);

  const mdLines = [
    '# Test fixtures',
    '',
    'See the [fixture feature documentation](../docs/fixture-features.md). This file is automatically',
    'generated by [`cli/build-test-fixtures.js`](../cli/build-test-fixtures.js).',
    '',
    ...fixtures.map(
      (fixture, index) => `${index + 1}. [*${manufacturers[fixture.man].name}* ${fixture.name}](../fixtures/${fixture.man}/${fixture.key}.json)`,
    ),
    '',
  ];

  // table head
  const tableHead = ['*Fixture number*', ...fixtures.map((fixture, index) => index + 1)].join(' | ');

  mdLines.push(
    tableHead,
    '|-'.repeat(fixtures.length + 1),
  );

  // table body
  const footnotes = [];
  for (const [index, fixtureFeature] of fixtureFeatures.entries()) {
    let line = `**${fixtureFeature.name}**`;

    if (fixtureFeature.description) {
      footnotes.push(fixtureFeature.description);
      const n = footnotes.length;
      line += ` [<sup>[${n}]</sup>](#user-content-footnote-${n})`;
    }

    for (const fixture of fixtures) {
      line += fixture.features.includes(fixtureFeature.id) ? ' | ✅' : ' | ❌';
    }

    mdLines.push(line);

    // repeat table head
    if ((index + 1) % 15 === 0) {
      mdLines.push(tableHead);
    }
  }

  // footnotes
  mdLines.push('', '## Footnotes', '');
  for (const [index, footnote] of footnotes.entries()) {
    mdLines.push(`**<a id="user-content-footnote-${index + 1}">[${index + 1}]</a>**: ${footnote}  `);
  }
  mdLines.push('');

  return mdLines.join('\n');
}


================================================
FILE: cli/debug-env-variables.js
================================================
#!/usr/bin/env node

import { styleText } from 'util';

const usedVariables = [
  'ALLOW_SEARCH_INDEXING',
  'GITHUB_USER_TOKEN',
  'GITHUB_BROKEN_LINKS_ISSUE_NUMBER',
  'NODE_ENV',
  'PORT',
  'HOST',
  'WEBSITE_URL',
  'GITHUB_PR_NUMBER',
  'GITHUB_PR_HEAD_REF',
  'GITHUB_PR_BASE_REF',
  'GITHUB_REPOSITORY',
  'GITHUB_RUN_ID',
  'GITHUB_REF',
];

console.log('This scripts lists all environment variables that are used in the Open Fixture Library.\n');

console.log('Process environment variables:');
printVariables();
console.log();

await import('../lib/load-env-file.js');

console.log('Environment variables after reading .env:');
printVariables();

/**
 * Prints all used environment variables and their values / unset
 */
function printVariables() {
  for (const key of usedVariables) {
    const coloredKey = styleText('yellow', key);
    if (key in process.env) {
      const coloredValue = styleText('green', process.env[key]);
      console.log(`${coloredKey}=${coloredValue}`);
    }
    else {
      const unsetLabel = styleText('red', ' is unset');
      console.log(`${coloredKey}${unsetLabel}`);
    }
  }
}


================================================
FILE: cli/diff-plugin-outputs.js
================================================
#!/usr/bin/env node

import { styleText } from 'util';
import minimist from 'minimist';
import diffPluginOutputs from '../lib/diff-plugin-outputs.js';
import importJson from '../lib/import-json.js';

const plugins = await importJson('../plugins/plugins.json', import.meta.url);
const testFixtures = await importJson('../tests/test-fixtures.json', import.meta.url);
const testFixtureKeys = testFixtures.map((fixture) => `${fixture.man}/${fixture.key}`);

const cliArguments = minimist(process.argv.slice(2), {
  string: ['p', 'c', 'r'],
  boolean: ['t', 'h'],
  alias: { p: 'plugin', c: 'compare-plugin', r: 'ref', t: 'test-fix', h: 'help' },
  default: { r: 'HEAD' },
});
cliArguments.comparePlugin = cliArguments['compare-plugin'];
cliArguments.testFix = cliArguments['test-fix'];
cliArguments.fixtures = cliArguments._;

const scriptName = import.meta.url.split('/').slice(-2).join('/');
const exportPlugins = plugins.exportPlugins.join(', ');

const helpMessage = [
  'This script exports the given fixtures with the current version of the given plugin and diffs the results',
  'against the files exported with the comparePlugin at the state of the given Git reference.',
  'Fixtures have to be declared with the path to its file in the fixtures/ directory.',
  `Usage: node ${scriptName} -p <plugin-key> [-c <compare-plugin-key>] [-r <git-ref>] [ -t | <fixture> [<more fixtures>] ]`,
  'Options:',
  '  --plugin,         -p: Which plugin should be used to output fixtures. Allowed values:',
  `                        ${exportPlugins}`,
  '  --compare-plugin, -c: A plugin from the given git reference (may not exist anymore). Defaults to --plugin.',
  '  --ref,            -r: The Git reference with which the current repo should be compared.',
  '                        E. g. 02ba13, HEAD~1 or master.',
  '                        Defaults to HEAD.',
  '  --test-fix,       -t: Use the test fixtures instead of specifing custom fixtures.',
  '  --help,           -h: Show this help message.',
].join('\n');

if (cliArguments.help) {
  console.log(helpMessage);
  process.exit(0);
}

if (!cliArguments.plugin) {
  console.error(styleText('red', '[Error]'), 'Plugin has to be specified using --plugin');
  console.log(helpMessage);
  process.exit(1);
}

if (!cliArguments.comparePlugin) {
  cliArguments.comparePlugin = cliArguments.plugin;
  cliArguments.c = cliArguments.p;
}

if (cliArguments.fixtures.length === 0 && !cliArguments.testFix) {
  console.log(styleText('yellow', '[Warning]'), 'No fixtures specified. See --help for usage.');
}

diffPluginOutputs(cliArguments.plugin, cliArguments.comparePlugin, cliArguments.ref, cliArguments.testFix ? testFixtureKeys : cliArguments.fixtures);


================================================
FILE: cli/export-fixture.js
================================================
#!/usr/bin/env node
import { mkdir, writeFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { styleText } from 'util';
import minimist from 'minimist';
import importJson from '../lib/import-json.js';
import { fixtureFromRepository } from '../lib/model.js';

try {
  const cliArguments = minimist(process.argv.slice(2), {
    string: ['p', 'o'],
    boolean: ['h', 'a'],
    alias: { p: 'plugin', h: 'help', a: 'all-fixtures', o: 'output-dir' },
  });

  await checkCliArguments(cliArguments);

  let fixtures;
  if (cliArguments.a) {
    const register = await importJson('../fixtures/register.json', import.meta.url);
    fixtures = Object.keys(register.filesystem).filter(
      (fixtureKey) => !('redirectTo' in register.filesystem[fixtureKey]) || register.filesystem[fixtureKey].reason === 'SameAsDifferentBrand',
    ).map((fixtureKey) => fixtureKey.split('/'));
  }
  else {
    fixtures = cliArguments._.map((relativePath) => {
      const absolutePath = path.join(process.cwd(), relativePath);
      return [
        path.basename(path.dirname(absolutePath)), // man key
        path.basename(absolutePath, path.extname(absolutePath)), // fix key
      ];
    });
  }

  const outDirectory = cliArguments.o ? path.resolve(process.cwd(), cliArguments.o) : null;

  const plugin = await import(`../plugins/${cliArguments.plugin}/export.js`);
  const files = await plugin.exportFixtures(
    await Promise.all(fixtures.map(
      ([manufacturer, fixture]) => fixtureFromRepository(manufacturer, fixture),
    )),
    {
      baseDirectory: fileURLToPath(new URL('../', import.meta.url)),
      date: new Date(),
    },
  );
  for (const file of files) {
    if (cliArguments.o) {
      const filePath = path.join(outDirectory, file.name);
      await mkdir(path.dirname(filePath), { recursive: true });
      await writeFile(filePath, file.content);
      console.log(`Created file ${filePath}`);
    }
    else {
      console.log();
      console.log(styleText('yellow', `File name: '${file.name}'`));
      console.log(file.content);
    }
  }
}
catch (error) {
  console.error(styleText('red', '[Error]'), 'Exporting failed:', error);
  process.exit(1);
}

/**
 * @param {Record<string, unknown>} cliArguments Command line interface arguments parsed by minimist.
 */
async function checkCliArguments(cliArguments) {
  const helpMessage = [
    `Usage: ${process.argv[1]} -p <plugin name> [ -a | <fixture> [<more fixtures>] ]`,
    'Options:',
    '  --plugin,       -p: Which plugin should be used to export fixtures.',
    '                      E. g. ecue or qlcplus',
    '  --all-fixtures, -a: Use all fixtures from register',
    '  --output-dir,   -o: If set, save outputted files in this directory',
    '                      instead of printing the contents in the console',
    '  --help,         -h: Show this help message.',
  ].join('\n');

  if (cliArguments.help) {
    console.log(helpMessage);
    process.exit(0);
  }

  if (!cliArguments.plugin) {
    console.error(styleText('red', '[Error]'), 'No plugin specified. See --help for usage.');
    process.exit(1);
  }

  if (cliArguments._.length === 0 && !cliArguments.a) {
    console.error(styleText('red', '[Error]'), 'No fixtures specified. See --help for usage.');
    process.exit(1);
  }

  const plugins = await importJson('../plugins/plugins.json', import.meta.url);

  if (!plugins.exportPlugins.includes(cliArguments.plugin)) {
    console.error(styleText('red', '[Error]'), `Plugin '${cliArguments.plugin}' does not exist or does not support exporting.\n\navailable plugins:`, plugins.exportPlugins.join(', '));
    process.exit(1);
  }
}


================================================
FILE: cli/import-fixture.js
================================================
#!/usr/bin/env node

import { readFile } from 'fs/promises';
import minimist from 'minimist';
import createPullRequest from '../lib/create-github-pr.js';
import fixtureJsonStringify from '../lib/fixture-json-stringify.js';
import importJson from '../lib/import-json.js';
import { checkFixture } from '../tests/fixture-valid.js';
/** @import { FixtureCreateResult } from '../lib/types.js' */

const cliArguments = minimist(process.argv.slice(2), {
  string: ['p', 'a'],
  boolean: 'c',
  alias: {
    a: 'author-name',
    p: 'plugin',
    c: 'create-pull-request',
  },
});

await checkCliArguments();

const filename = cliArguments._[0];

try {
  const buffer = await readFile(filename);

  const plugin = await import(`../plugins/${cliArguments.plugin}/import.js`);
  const { manufacturers, fixtures, warnings } = await plugin.importFixtures(buffer, filename, cliArguments['author-name']);

  /** @type {FixtureCreateResult} */
  const result = {
    manufacturers,
    fixtures,
    warnings,
    errors: {},
  };

  for (const key of Object.keys(result.fixtures)) {
    const [manufacturerKey, fixtureKey] = key.split('/');

    const checkResult = await checkFixture(manufacturerKey, fixtureKey, result.fixtures[key]);

    result.warnings[key].push(...checkResult.warnings);
    result.errors[key] = checkResult.errors;
  }

  if (cliArguments['create-pull-request']) {
    try {
      const pullRequestUrl = await createPullRequest(result);
      console.log(`URL: ${pullRequestUrl}`);
    }
    catch (error) {
      console.log(fixtureJsonStringify(result));
      console.error(`Error creating pull request: ${error.message}`);
    }
  }
  else {
    console.log(fixtureJsonStringify(result));
  }
}
catch (error) {
  console.error(`Error parsing '${filename}':`);
  console.error(error);
  process.exit(1);
}

/**
 * Checks the command line interface arguments parsed by minimist.
 */
async function checkCliArguments() {
  const plugins = await importJson('../plugins/plugins.json', import.meta.url);

  if (cliArguments._.length !== 1 || !plugins.importPlugins.includes(cliArguments.plugin) || !cliArguments['author-name']) {
    const importPlugins = plugins.importPlugins.join(', ');
    console.error(`Usage: ${process.argv[1]} -p <plugin> -a <author name> [--create-pull-request] <filename>\n\navailable plugins: ${importPlugins}`);
    process.exit(1);
  }
}


================================================
FILE: cli/run-export-test.js
================================================
#!/usr/bin/env node
import path from 'path';
import { fileURLToPath } from 'url';
import { styleText } from 'util';
import minimist from 'minimist';
import importJson from '../lib/import-json.js';
import { fixtureFromFile, fixtureFromRepository } from '../lib/model.js';

const failLabel = styleText('red', '[FAIL]');
const passLabel = styleText('green', '[PASS]');

try {
  const plugins = await importJson('../plugins/plugins.json', import.meta.url);
  const testFixtures = await importJson('../tests/test-fixtures.json', import.meta.url);

  const cliArguments = minimist(process.argv.slice(2), {
    string: ['p'],
    boolean: ['h'],
    alias: { p: 'plugin', h: 'help' },
  });

  const scriptName = import.meta.url.split('/').pop();

  const helpMessage = [
    'Run the plugin\'s export tests against the specified fixtures',
    '(or the test fixtures, if no fixtures are specified).',
    `Usage: node ${scriptName} -p <plugin> [ <fixtures> ]`,
    'Options:',
    '  --plugin,   -p: Key of the plugin whose export tests should be called',
    '  --help,     -h: Show this help message.',
  ].join('\n');

  if (cliArguments.help) {
    console.log(helpMessage);
    process.exit(0);
  }

  if (!cliArguments.plugin) {
    console.error(styleText('red', '[Error]'), 'Plugin has to be specified using --plugin');
    console.log(helpMessage);
    process.exit(1);
  }

  if (!plugins.exportPlugins.includes(cliArguments.plugin)) {
    console.error(styleText('red', '[Error]'), `Plugin '${cliArguments.plugin}' is not a valid export plugin.\nAvailable export plugins:`, plugins.exportPlugins.join(', '));
    process.exit(1);
  }

  const pluginData = plugins.data[cliArguments.plugin];
  if (pluginData.exportTests.length === 0) {
    console.log(styleText('green', '[PASS]'), `Plugin '${cliArguments.plugin}' has no export tests.`);
    process.exit(0);
  }

  const fixtures = cliArguments._.length === 0
    ? testFixtures.map((fixture) => fixtureFromRepository(fixture.man, fixture.key))
    : cliArguments._.map((relativePath) => fixtureFromFile(path.join(process.cwd(), relativePath)));

  const exportPlugin = await import(`../plugins/${cliArguments.plugin}/export.js`);

  const files = await exportPlugin.exportFixtures(
    await Promise.all(fixtures),
    {
      baseDirectory: fileURLToPath(new URL('../', import.meta.url)),
      date: new Date(),
    },
  );

  await Promise.all(pluginData.exportTests.map(async (testKey) => {
    const { default: exportTest } = await import(`../plugins/${cliArguments.plugin}/exportTests/${testKey}.js`);

    const outputPerFile = await Promise.all(files.map(async (file) => {
      try {
        await exportTest(file, files);
        return `${passLabel} ${file.name}`;
      }
      catch (testError) {
        const errors = Array.isArray(testError) ? testError : [testError];

        return [
          `${failLabel} ${file.name}`,
          ...errors.map((error) => `- ${error}`),
        ].join('\n');
      }
    }));

    console.log();
    console.log(styleText('yellow', `Test ${testKey}`));
    console.log(outputPerFile.join('\n'));
  }));
}
catch (error) {
  console.error(styleText('red', '[Error]'), 'Exporting failed:', error);
  process.exit(1);
}


================================================
FILE: docs/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at flo@open-fixture-library.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]

[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/


================================================
FILE: docs/CONTRIBUTING.md
================================================
# Contributing Guidelines

We believe in the power of open source development and want to encourage everyone to contribute to this project. This document should help new developers and documents our coding workflow. If there are any questions left, don't hesitate to [open a new issue](https://github.com/OpenLightingProject/open-fixture-library/issues/new) explaining that you haven't found the information in the docs.

## Developer vibes

Please keep being friendly and don't troll. See our [Code of Conduct](CODE_OF_CONDUCT.md) for more information on this.

## Issues

You have an idea about a new feature or you spotted a mistake? Feel free to create an [issue](https://github.com/OpenLightingProject/open-fixture-library/issues) in which you describe the problem / the feature requirements. Please try to find similar issues first though, and add your information there to keep it organized.

As soon as an issue is assigned to somebody, it means that this person is responsible for fixing it.

## How you can help

### Fixtures

* **Add new fixtures**  
  Either add fixtures via the online [Fixture Editor](https://open-fixture-library.org/fixture-editor) or by manually writing a [fixture format JSON](fixture-format.md).

* **Answer questions in existing fixtures**  
  Go through fixtures you know on the [website](https://open-fixture-library.org/manufacturers) and look for yellow "Help wanted" boxes with questions you can answer.

* **Review fixtures that are not yet merged**  
  These are the steps that take the longest when reviewing a fixture:

  > 1. Checking whether at least 3 links are present. Otherwise, finding suitable links.
  >     - link to a manual PDF containing all DMX modes
  >     - link to a product page on the official manufacturer website
  >     - link to a YouTube or Vimeo video that showcases or explains the fixture
  > 2. Checking whether all DMX modes from the manual are included in the fixture definition.  
  > 3. Checking whether all DMX modes' channels from the manual are included in the fixture definition.  
  > 4. Checking whether all DMX channels' capabilities from the manual are included in the fixture definition.

  If you could go through some [PRs with the `new-fixture` label](https://github.com/OpenLightingProject/open-fixture-library/pulls?q=is%3Apr+is%3Aopen+label%3Anew-fixture) and comment on them with the results of those checks, that would help immensely. Thank you already :)

  For the fixtures passing these tests, these are some common tasks that often need to be fixed:

  - replace capability type `StrobeSpeed` with `ShutterStrobe` where that one fits better
  - replace capability type `Generic` with `Maintenance` where that one fits better
  - rename modes that have no name in the manual to the usual `5-channel`/`5ch` format
  - link to an English manual / product page (if one exists) instead of one in another language

  Pull requests with these fixes against the original fixture pull requests (see e.g. [#2023](https://github.com/OpenLightingProject/open-fixture-library/pull/2023)) are highly appreciated!

* **Add links and colors to existing fixtures**  
  See [#578](https://github.com/OpenLightingProject/open-fixture-library/issues/578) and [#723](https://github.com/OpenLightingProject/open-fixture-library/issues/723). There are also some broken links (see [#999](https://github.com/OpenLightingProject/open-fixture-library/issues/999)), maybe you can find some alternative links to replace them.

### Code

* **Work on easy issues**  
  Browse the [`good-first-issues` label](https://github.com/OpenLightingProject/open-fixture-library/issues?q=is:open+is:issue+label:%22good+first+issue%22) to find some easy tasks.

* **Implement your own idea**  
  Please create a new issue first if it's a bigger change.

* **Create new import/export plugins**  
  This is a bit more involved. See the [list of new plugins](https://github.com/OpenLightingProject/open-fixture-library/issues?q=is%3Aissue+is%3Aopen+label%3Anew-plugin) for inspiration. Use existing plugins as reference and look at the [plugin documentation](plugins.md).

* **Improve the [documentation](README.md)**  
  Especially after you have made other changes, there is likely something you've been missing in the documentation. Help others find it there!

## Local installation

After [forking](https://help.github.com/articles/fork-a-repo/) the repository, follow the [GitHub flow](https://guides.github.com/introduction/flow/) to implement your changes.

You can also create a (draft) pull request if you're not done yet to involve the reviewers into the development process and get help if you're stuck.

See [README.md](README.md#local-installation) for how to setup and test run the code locally.

## Code style

We always aim to have clear, readable code. So please try to respect these principles:

* Document every new function with [JSDoc](https://jsdoc.app/about-getting-started.html)
* Use [self-describing variable names](https://wiki.c2.com/?GoodVariableNames) and prefer constant variables over literal values without explanation
* Prefer code readability over [micro-optimisation](https://softwareengineering.stackexchange.com/questions/99445/is-micro-optimisation-important-when-coding)
* Use JavaScript features that improve code readability, for example:
  - Prefer [Array iteration methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Instance_methods) (like [`map(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map), [`filter(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), [`some(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some), [`every(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every), [`find(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)) with arrow functions over loops
  - Always use [template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) (backticks instead of single or double quotes: ``const str = `My name is ${name}.`;``) as [they are strictly better strings](https://ponyfoo.com/articles/template-literals-strictly-better-strings) and make string concatenation (`const str = 'My name is ' + name + '.';`) more readable
* Try to make a piece of code not too complex. That is, if a function contains lots of ifs and for-loops, extract some parts into helper functions. (For example, the `checkFixture()` function calls `checkPhysical()` and `checkChannels()`, `checkChannel()` calls `checkCapabilities()`, etc.)

We automatically check code style using [ESLint](https://eslint.org/). Maybe your IDE supports ESLint highlighting (there's a good [extension for VSCode](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)) – this helps spotting bad code style as quickly as possible.

## Developer tips

* To understand how OFL works, read the [Documentation Overview](README.md) and its related pages. We try to document every part of our software.
* Run [tests](testing.md) in the `tests/` directory locally before pushing – that's faster than waiting for the automated GitHub Actions tests in pull requests.
* Run `npm run build` to be sure that auto-generated contents are up-to-date.


================================================
FILE: docs/README.md
================================================
# Documentation Overview

This is the developer documentation for the *Open Fixture Library*. Please follow links in the folder structure below to get more information on a specific topic.

## Repository folder structure

* `cli/` – Useful scripts to be called from the command line
* `docs/` – Documentation (*you are here!*)
* `fixtures/` – Repository of our [fixture definitions](fixture-format.md)
* `lib/` – Reusable modules used in the project
  - `fixture-features/` – [Fixture features](fixture-features.md), special fixture characteristics used to determine a set of test fixtures
  - `model/` – Classes of the [fixture model](fixture-model.md) that help ease processing fixture data (see the [model API reference](model-api.md))
* `plugins/` – [Plugins](plugins.md) for export / import to / from other software's fixture formats
* `resources/` – Resources (e.g. [gobos](fixture-format.md#gobo-resources)) that fixtures can use
* `schemas/` – Schemas for the [fixture definitions](fixture-format.md#schema)
* `server/` – Load balancer config and auto-deploy script for everyone interested
* `tests/` – [Unit tests](testing.md), many of them run automatically in GitHub Actions
  - `github/` – Special kind of tests which shouldn't be called manually and create comments in GitHub pull requests
* `ui/` – Everything related to the [UI / Website](ui.md) (see docs there for subfolders)
* `tmp/` – Temporary files autogenerated by tests. Safe to delete and not indexed by Git.

## Local installation

### Linux / macOS

Make sure you have these dependencies installed:

* `git`
* [Node.js](https://nodejs.org/en/download/package-manager/)

Then run

```sh
git clone https://github.com/OpenLightingProject/open-fixture-library.git
cd open-fixture-library
npm install
npm run build
```

Continue with [UI docs](ui.md).

### Windows

Make sure you have these dependencies installed:

* [Git](https://gitforwindows.org/)
* [Node.js](https://nodejs.org/en/download/)
  * On the *Tools for Native Modules* page, activate the checkbox *Automatically install the necessary tools. [...]*

Choose a project path that only contains ASCII characters and no spaces. Open a Terminal and run

```bat
git clone https://github.com/OpenLightingProject/open-fixture-library.git
cd open-fixture-library
npm install
npm run build
```

Continue with [UI docs](ui.md).

### Dev Container

Make sure you have these dependencies installed:

* [Git](https://git-scm.com/downloads)
* [Docker Desktop](https://www.docker.com/products/docker-desktop/)
  * **Windows-only:** [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) + [WSL integration](https://docs.docker.com/desktop/wsl/) in Docker Desktop enabled
* [VS Code](https://code.visualstudio.com/Download)
  * [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension installed
  * **Windows-only:** [WSL](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) extension installed

Open a terminal and run:

```sh
git clone https://github.com/OpenLightingProject/open-fixture-library.git
code open-fixture-library
```

In VS Code, select <kbd>F1</kbd> -> *Dev Containers: Reopen in Container*, which reopens the repository in a container and runs `npm install`.

Open a terminal in VS Code and run:

```sh
npm run build
```

Continue with [UI docs](ui.md).

## Contributing

Please see [`CONTRIBUTING.md`](CONTRIBUTING.md).

## Environment variables

See [`environment-variables.md`](environment-variables.md).


================================================
FILE: docs/capability-types.md
================================================
This is a full list of capability types with their properties, entities and units. See the [general information about capabilities](fixture-format.md#capabilities).


### Possible entities and keywords

Most type-specific properties refer to one of the following entities, which allow different units. 

To make common percentage values more readable, one can use specific keywords to replace them. For example, `"speed": "fast"` has the same effect as `"speed": "100%"`.

| Entity           | Allowed Units    | `-100%`      | `-1%`        | `0%`          | `1%`                       | `100%`
| -                | -                | -            | -            | -             | -                          | -
| Speed            | `Hz`, `bpm`, `%` | fast reverse | slow reverse | stop          | slow                       | fast
| RotationSpeed    | `Hz`, `rpm`, `%` | fast CCW     | slow CCW     | stop          | slow CW                    | fast CW
| Time             | `s`, `ms`, `%`   | –            | –            | instant       | short                      | long
| Distance         | `m`, `%`         | –            | –            | –             | near                       | far
| Brightness       | `lm`, `%`        | –            | –            | off           | dark                       | bright
| ColorTemperature | `K`, `%`         | warm / CTO   | –            | default       | –                          | cold / CTB
| FogOutput        | `m^3/min`, `%`   | –            | –            | off           | weak                       | strong
| RotationAngle    | `deg`, `%`       | –            | –            | –             | –                          | –
| BeamAngle        | `deg`, `%`       | –            | –            | closed        | narrow                     | wide
| HorizontalAngle  | `deg`, `%`       | left         | –            | center        | –                          | right
| VerticalAngle    | `deg`, `%`       | top          | –            | center        | –                          | bottom
| SwingAngle       | `deg`, `%`       | –            | –            | off           | narrow                     | wide
| Parameter        | (no unit), `%`   | –            | –            | off / instant | low / slow / small / short | high / fast / big / long
| SlotNumber       | (no unit)        | –            | –            | –             | –                          | –
| Percent          | `%`              | –            | –            | off           | low                        | high
| Insertion        | `%`              | –            | –            | out           | –                          | in
| IrisPercent      | `%`              | –            | –            | closed        | –                          | open


### Possible capability types

**Table of contents**

* [NoFunction](#no-function)
* [ShutterStrobe](#shutter-strobe) / [StrobeSpeed](#strobe-speed) / [StrobeDuration](#strobe-duration)
* [Intensity](#intensity)
* [ColorIntensity](#color-intensity)
* [ColorPreset](#color-preset)
* [ColorTemperature](#color-temperature)
* [Pan](#pan) / [PanContinuous](#pan-continuous)
* [Tilt](#tilt) / [TiltContinuous](#tilt-continuous)
* [PanTiltSpeed](#pan-tilt-speed)
* [WheelSlot](#wheel-slot) / [WheelShake](#wheel-shake) / [WheelSlotRotation](#wheel-slot-rotation) / [WheelRotation](#wheel-rotation)
* [Effect](#effect) / [EffectSpeed](#effect-speed) / [EffectDuration](#effect-duration) / [EffectParameter](#effect-parameter)
* [SoundSensitivity](#sound-sensitivity)
* [BeamAngle](#beam-angle) / [BeamPosition](#beam-position)
* [Focus](#focus)
* [Zoom](#zoom)
* [Iris](#iris) / [IrisEffect](#iris-effect)
* [Frost](#frost) / [FrostEffect](#frost-effect)
* [Prism](#prism) / [PrismRotation](#prism-rotation)
* [BladeInsertion](#blade-insertion)
 / [BladeRotation](#blade-rotation) / [BladeSystemRotation](#blade-system-rotation)
* [Fog](#fog) / [FogOutput](#fog-output) / [FogType](#fog-type)
* Generic types: [Rotation](#rotation) / [Speed](#speed) / [Time](#time) / [Maintenance](#maintenance) / [Generic](#generic)


<table>
<thead>
<tr>
  <th scope="col">Capability type</th>
  <th scope="col">Property</th>
  <th scope="col">Possible values</th>
  <th scope="col">Notes</th>
</tr>
</thead>
<tbody>
<tr>
  <th valign="top" scope="row" id="no-function">
    <img src="../ui/assets/icons/fixture/no-function.svg" />
    NoFunction
  </th>
  <td valign="top" colspan="3">No type-specific properties.</td>
</tr>
  <th valign="top" scope="row" id="shutter-strobe" rowspan="5">
    <img src="../ui/assets/icons/fixture/shutter.svg" />
    <img src="../ui/assets/icons/fixture/strobe.svg" />
    ShutterStrobe
  </th>
  <td valign="top">shutterEffect<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>Open</code>, <code>Closed</code>, <code>Strobe</code>, <code>Pulse</code>, <code>RampUp</code>, <code>RampDown</code>, <code>RampUpDown</code>, <code>Lightning</code>, <code>Spikes</code>, <code>Burst</code></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">soundControlled<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></td>
  <td valign="top">Entity <em>Boolean</em></td>
  <td valign="top">Defaults to <code>false</code></td>
</tr>
<tr>
  <td valign="top">speed<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">duration<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Time</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">randomTiming<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></td>
  <td valign="top">Entity <em>Boolean</em></td>
  <td valign="top">Defaults to <code>false</code></td>
</tr>
<tr>
  <th valign="top" scope="row" id="strobe-speed">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    StrobeSpeed
  </th>
  <td valign="top">speed<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top">global, doesn't activate strobe directly</td>
</tr>
<tr>
  <th valign="top" scope="row" id="strobe-duration">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    StrobeDuration
  </th>
  <td valign="top">duration<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Time</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="intensity">
    <img src="../ui/assets/icons/fixture/dimmer.svg" />
    Intensity
  </th>
  <td valign="top">brightness<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Brightness</em></td>
  <td valign="top">Defaults to <code>brightnessStart: "off", brightnessEnd: "bright"</code></td>
</tr>
<tr>
  <th valign="top" scope="row" id="color-intensity" rowspan="2">
    <img src="../ui/assets/icons/fixture/dimmer.svg" />
    ColorIntensity
  </th>
  <td valign="top">color<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">one of our predefined Single Colors:
    <code>Red</code>, <code>Green</code>, <code>Blue</code>, <code>Cyan</code>, <code>Magenta</code>, <code>Yellow</code>, <code>Amber</code>, <code>White</code>, <code>Warm White</code>, <code>Cold White</code>, <code>UV</code>, <code>Lime</code>, <code>Indigo</code>
  </td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">brightness<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Brightness</em></td>
  <td valign="top">Defaults to <code>brightnessStart: "off", brightnessEnd: "bright"</code></td>
</tr>
<tr>
  <th valign="top" scope="row" id="color-preset" rowspan="2">
    <img src="../ui/assets/icons/fixture/color-changer.svg" />
    ColorPreset
  </th>
  <td valign="top">colors<br><sub>❔ optional</sub></td>
  <td valign="top">array of individual color beams as hex code</td>
  <td valign="top"><a href="#property-colors">see footnote <em>colors</em></a></td>
</tr>
<tr>
  <td valign="top">colorTemperature<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>ColorTemperature</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="color-temperature">
    <img src="../ui/assets/icons/fixture/color-temperature.svg" />
    ColorTemperature
  </th>
  <td valign="top">colorTemperature<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>ColorTemperature</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="pan">
    <img src="../ui/assets/icons/fixture/pan.svg" />
    Pan
  </th>
  <td valign="top">angle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="pan-continuous">
    <img src="../ui/assets/icons/fixture/pan-continuous-cw.svg" />
    PanContinuous
  </th>
  <td valign="top">speed<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="tilt">
    <img src="../ui/assets/icons/fixture/tilt.svg" />
    Tilt
  </th>
  <td valign="top">angle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="tilt-continuous">
    <img src="../ui/assets/icons/fixture/tilt-continuous-cw.svg" />
    TiltContinuous
  </th>
  <td valign="top">speed<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="pan-tilt-speed" rowspan="2">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    PanTiltSpeed
  </th>
  <td valign="top">speed<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td rowspan="2">either <em>speed</em> or <em>duration</em> is allowed</td>
</tr>
<tr>
  <td valign="top">duration<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>Time</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="wheel-slot" rowspan="2">
    <img src="../ui/assets/icons/fixture/gobo.svg" />
    WheelSlot
  </th>
  <td valign="top">wheel<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Wheel name</td>
  <td valign="top">Defaults to channel name</td>
</tr>
<tr>
  <td valign="top">slotNumber<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>SlotNumber</em></td>
  <td valign="top"><a href="#property-slotnumber">see footnote <em>slotNumber</em></a></td>
</tr>
<tr>
  <th valign="top" scope="row" id="wheel-shake" rowspan="5">
    <img src="../ui/assets/icons/fixture/wheel-shake.svg" />
    <img src="../ui/assets/icons/fixture/slot-shake.svg" />
    WheelShake
  </th>
  <td valign="top">isShaking<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>wheel</code> or <code>slot</code></td>
  <td valign="top">Defaults to <code>wheel</code>.</td>
</tr>
<tr>
  <td valign="top">wheel<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Wheel name or array of wheel names</td>
  <td valign="top">Defaults to channel name. Array not allowed when <em>slotNumber</em> is set.</td>
</tr>
<tr>
  <td valign="top">slotNumber<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>SlotNumber</em></td>
  <td valign="top"><a href="#property-slotnumber">see footnote <em>slotNumber</em></a></td>
</tr>
<tr>
  <td valign="top">shakeSpeed<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">shakeAngle<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>SwingAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="wheel-slot-rotation" rowspan="4">
    <img src="../ui/assets/icons/fixture/rotation-cw.svg" />
    WheelSlotRotation
  </th>
  <td valign="top">wheel<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Wheel name or array of wheel names</td>
  <td valign="top">Defaults to channel name. Array not allowed when <em>slotNumber</em> is set.</td>
</tr>
<tr>
  <td valign="top">slotNumber<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>SlotNumber</em></td>
  <td valign="top"><a href="#property-slotnumber">see footnote <em>slotNumber</em></a></td>
</tr>
<tr>
  <td valign="top">speed<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td rowspan="2">either <em>speed</em> or <em>angle</em> is allowed</td>
</tr>
<tr>
  <td valign="top">angle<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="wheel-rotation" rowspan="3">
    <img src="../ui/assets/icons/fixture/rotation-cw.svg" />
    WheelRotation
  </th>
  <td valign="top">wheel<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Wheel name or array of wheel names</td>
  <td valign="top">Defaults to channel name.</td>
</tr>
<tr>
  <td valign="top">speed<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td rowspan="2">either <em>speed</em> or <em>angle</em> is allowed</td>
</tr>
<tr>
  <td valign="top">angle<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="effect" rowspan="7">
    <img src="../ui/assets/icons/fixture/effect.svg" />
    Effect
  </th>
  <td valign="top">effectName<br><sub>🆚 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Free text describing the effect</td>
  <td rowspan="2">either <em>effectName</em> or <em>effectPreset</em> is allowed</td>
</tr>
<tr>
  <td valign="top">effectPreset<br><sub>🆚 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>ColorJump</code> or <code>ColorFade</code></td>
</tr>
<tr>
  <td valign="top">speed<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">duration<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Time</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">parameter<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Parameter</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">soundControlled<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></td>
  <td valign="top">Entity <em>Boolean</em></td>
  <td valign="top">Defaults to <code>false</code></td>
</tr>
<tr>
  <td valign="top">soundSensitivity<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Percent</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="beam-angle">
    <img src="../ui/assets/icons/fixture/beam-angle.svg" />
    BeamAngle
  </th>
  <td valign="top">angle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>BeamAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="beam-position" rowspan="2">
    <img src="../ui/assets/icons/fixture/beam-position.svg" />
    BeamPosition
  </th>
  <td valign="top">horizontalAngle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>HorizontalAngle</em></td>
  <td valign="top" rowspan="2">at least one of <em>horizontalAngle</em> or <em>verticalAngle</em> is required</td>
</tr>
<tr>
  <td valign="top">verticalAngle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>VerticalAngle</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="effect-speed">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    EffectSpeed
  </th>
  <td valign="top">speed<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="effect-duration">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    EffectDuration
  </th>
  <td valign="top">duration<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Time</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="effect-parameter">
    <img src="../ui/assets/icons/fixture/effect-parameter.svg" />
    EffectParameter
  </th>
  <td valign="top">parameter<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Parameter</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="sound-sensitivity">
    <img src="../ui/assets/icons/fixture/sound-sensitivity.svg" />
    SoundSensitivity
  </th>
  <td valign="top">soundSensitivity<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Percent</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="focus">
    <img src="../ui/assets/icons/fixture/focus.svg" />
    Focus
  </th>
  <td valign="top">distance<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Distance</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="zoom">
    <img src="../ui/assets/icons/fixture/zoom.svg" />
    Zoom
  </th>
  <td valign="top">angle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>BeamAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="iris">
    <img src="../ui/assets/icons/fixture/iris.svg" />
    Iris
  </th>
  <td valign="top">openPercent<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>IrisPercent</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="iris-effect" rowspan="2">
    <img src="../ui/assets/icons/fixture/iris.svg" />
    IrisEffect
  </th>
  <td valign="top">effectName<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Free text describing the effect</td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">speed<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="frost">
    <img src="../ui/assets/icons/fixture/frost.svg" />
    Frost
  </th>
  <td valign="top">frostIntensity<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Percent</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="frost-effect" rowspan="2">
    <img src="../ui/assets/icons/fixture/frost.svg" />
    FrostEffect
  </th>
  <td valign="top">effectName<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top">Free text describing the effect</td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">speed<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="prism" rowspan="2">
    <img src="../ui/assets/icons/fixture/prism.svg" />
    Prism
  </th>
  <td valign="top">speed<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td rowspan="2">activates fixture's prism; either <em>speed</em> or <em>angle</em> is allowed</td>
</tr>
<tr>
  <td valign="top">angle<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="prism-rotation" rowspan="2">
    <img src="../ui/assets/icons/fixture/rotation-cw.svg" />
    PrismRotation
  </th>
  <td valign="top">speed<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td rowspan="2">doesn't activate prism directly; either <em>speed</em> or <em>angle</em> is allowed</td>
</tr>
<tr>
  <td valign="top">angle<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="blade-insertion" rowspan="2">
    <img src="../ui/assets/icons/fixture/blade-insertion.svg" />
    BladeInsertion
  </th>
  <td valign="top">blade<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>Top</code>, <code>Right</code>, <code>Bottom</code>, <code>Left</code> or a number if the position is unknown</td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">insertion<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Insertion</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="blade-rotation" rowspan="2">
    <img src="../ui/assets/icons/fixture/rotation-cw.svg" />
    BladeRotation
  </th>
  <td valign="top">blade<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>Top</code>, <code>Right</code>, <code>Bottom</code>, <code>Left</code> or a number if the position is unknown</td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">angle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="blade-system-rotation">
    <img src="../ui/assets/icons/fixture/rotation-cw.svg" />
    BladeSystemRotation
  </th>
  <td valign="top">angle<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="fog" rowspan="2">
    <img src="../ui/assets/icons/fixture/smoke.svg" />
    <img src="../ui/assets/icons/fixture/hazer.svg" />
    Fog
  </th>
  <td valign="top">fogType<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>Fog</code> or <code>Haze</code></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">fogOutput<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>FogOutput</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="fog-output">
    <img src="../ui/assets/icons/fixture/smoke.svg" />
    FogOutput
  </th>
  <td valign="top">fogOutput<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>FogOutput</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="fog-type">
    <img src="../ui/assets/icons/fixture/smoke.svg" />
    <img src="../ui/assets/icons/fixture/hazer.svg" />
    FogType
  </th>
  <td valign="top">fogType<br><sub>🌟 required</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></sub></td>
  <td valign="top"><code>Fog</code> or <code>Haze</code></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="rotation" rowspan="2">
    <img src="../ui/assets/icons/fixture/rotation-cw.svg" />
    Rotation
  </th>
  <td valign="top">speed<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationSpeed</em></td>
  <td rowspan="2">either <em>speed</em> or <em>angle</em> is allowed</td>
</tr>
<tr>
  <td valign="top">angle<br><sub>🆚 required</sub></td>
  <td valign="top">Entity <em>RotationAngle</em></td>
</tr>
<tr>
  <th valign="top" scope="row" id="speed">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    Speed
  </th>
  <td valign="top">speed<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Speed</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="time">
    <img src="../ui/assets/icons/fixture/speed.svg" />
    Time
  </th>
  <td valign="top">time<br><sub>🌟 required</sub></td>
  <td valign="top">Entity <em>Time</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="maintenance" rowspan="2">
    <img src="../ui/assets/icons/fixture/maintenance.svg" />
    Maintenance
  </th>
  <td valign="top">parameter<br><sub>❔ optional</sub></td>
  <td valign="top">Entity <em>Parameter</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <td valign="top">hold<br><sub>❔ optional</sub><br><sub>👣 <a href="#must-be-stepped">must be stepped</a></td>
  <td valign="top">Entity <em>Time</em></td>
  <td valign="top"></td>
</tr>
<tr>
  <th valign="top" scope="row" id="generic">
    <img src="../ui/assets/icons/fixture/other.svg" />
    Generic
  </th>
  <td valign="top" colspan="3">No type-specific properties.</td>
</tr>
</tbody>
</table>

### Footnotes

#### Must be stepped

Properties that must be stepped (they have a 👣 footsteps icon next to them) can't be appended with `Start` or `End`. E.g. only `effectName` (not `effectNameStart`/`effectNameEnd`) is allowed, while both `speed` and `speedStart`/`speedEnd` are valid.

#### Property *colors*

"Individual color beams" means that one beam is visually distinguishable from the others, i.e.:

  * A Red/Green/Blue/White/Amber LED produces a single color beam, as all these color components are mixed together. For a color preset "Red+Blue", `colors` should be set to `["#ff00ff"]`.
  * A laser device has separate light beams that don't mix. If red and green lasers are active, `colors` should be set to `["#ff0000", "#00ff00"]`.
  * UV is always counted as a separate color as the ultraviolet light doesn't really mix with normal RGB colors. For a color preset "Red+Green+UV", `colors` should be set to `["#ffff00", "UV"]`.


#### Property *slotNumber*

Use one-based numbering (e.g. `1` for *Open*, `2` for *Color/Gobo 1*). If the capability shows a split slot, use the value halfway between them (e.g. `2.5` for *Split Color/Gobo 1/2*). If all steps in between can be selected by the proportional capability, use `slotNumberStart` and `slotNumberEnd` (e.g. from *Color/Gobo 1* to *Color/Gobo 2*).

**Note:** If there are e.g. 8 slots, and a capability allows gradually selecting anything between the last slot (*Color/Gobo 7*) and the first (*Open*) in this direction, use `"slotNumberStart": 8, "slotNumberEnd": 9`. If you chose `"slotNumberEnd": 1` instead, that would indicate a rotation in the other direction (i.e. over all other Gobos). Likewise, `"slotNumberStart": 0, "slotNumberEnd": 1` is also allowed.


### How to add new capability types / type-specific properties

* Update the schema (mainly `capability.json`, `definitions.json` for units / entities)
* Update this document (both table of contents and the section itself)
* Add new properties to the model (in `Capability.js`)
* If it's a start/end entity, add its name to `Capability.START_END_ENTITIES`
* Add new types to capability name generation (in `Capability.js`)
* Add new types to channel type generation (in `CoarseChannel.js`)
* Add a capability icon (see `ui/assets/icon` and maybe also the `app-fixture-capability-type-icon` component)
* Update editor:
  * Create new component in `ui/components/editor-capabilities`. Make sure it has a `defaultData` object as component data.
  * Import the new component in the [capability component](../ui/components/editor/EditorCapabilityTypeData.vue) and register it in its `components` section.


================================================
FILE: docs/environment-variables.md
================================================
# Environment Variables

We use environment variables for configuration and storing secret credentials. They are set by [the production / test servers](ui.md#deployment) and [CI environments](testing.md) and can also be set locally; the easiest way is to edit the (gitignored) `.env` file in the project's root directory:

```bash
MY_ENV_VARIABLE=hello

# Lines beginning with # are comments
NODE_ENV=production
```

Every script that uses environment variables must import [`lib/load-env-file.js`](../lib/load-env-file.js) as the very first statement.

Please update these docs and [`cli/debug-env-variables.js`](../cli/debug-env-variables.js) after introducing new variables.

## Environment variables used by OFL

| Name                              | Possible values                           | Description                        |
|-----------------------------------|-------------------------------------------|------------------------------------|
|`ALLOW_SEARCH_INDEXING`            | `allowed` or anything else                | If the value is not `allowed`, a `<meta>` tag is added to tell search engines not to index the page. (This is only `allowed` in the [production deployment](ui.md#deployment)) |
|`GITHUB_USER_TOKEN`                | A 40-char [GitHub access token][gh-token] | Used to create pull request when adding fixtures and create/delete comments after running [GitHub tests](testing.md) |
|`GITHUB_BROKEN_LINKS_ISSUE_NUMBER` | A GitHub issue number                     | Used by [tests/external-links.js](../tests/external-links.js) |
|`NODE_ENV`                         | `production` or `development` (default)   | Introduced by Express.js, `production` enables caching, minimizing and more optimizations [improving the performance a lot][node-env-perf]. |
|`PORT`                             | A free port number                        | On which port to start the Nuxt.js server, defaults to `3000`. |
|`HOST`                             | A host name or IP address                 | On which host to start the Nuxt.js server, defaults to `localhost`. |
|`WEBSITE_URL`                      | An absolute URL with a trailing slash.    | The public URL of the website. Defaults to `http://localhost:${PORT}/`. |
|`GITHUB_PR_NUMBER`                 | A GitHub pull request number              | In a pull request, the PR number. |
|`GITHUB_PR_HEAD_REF`               | A git ref                                 | In a pull request, the PR head ref (e.g. `feature-branch`). |
|`GITHUB_PR_BASE_REF`               | A git ref                                 | In a pull request, the PR base ref (e.g. `master`). |
|`GITHUB_REPOSITORY`                |                                           | [Set by GitHub Actions][gh-actions-docs] |
|`GITHUB_RUN_ID`                    |                                           | [Set by GitHub Actions][gh-actions-docs] |
|`GITHUB_REF`                       |                                           | [Set by GitHub Actions][gh-actions-docs] |

[gh-token]: <https://github.com/settings/tokens>
[node-env-perf]: <https://www.dynatrace.com/blog/the-drastic-effects-of-omitting-node_env-in-your-express-js-applications/>
[gh-actions-docs]: <https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables>


================================================
FILE: docs/fixture-features.md
================================================
# Fixture features

Fixture features are specific [fixture](fixture-format.md) characteristics (like "uses RDM" or "uses fine channel before coarse channel"), especially ones that have produced or are likely to produce bugs and errors.

We use fixture features for the following purposes:
* Generate a minimal collection of test fixtures that cover all fixture features so we can check all special cases with minimum work
* *(planned)* The fixture editor can only edit / import fixtures that only use editor-compatible fixture features
* *(planned)* Search for fixtures with specific fixture features (mainly for testing)

Fixture features are saved in the the [`lib/fixture-features/`](../lib/fixture-features/) directory as JS modules that export an array of features. It is advised to put similar features into one module. A sample module looks like this:

```js
export default [{
  // Optional, default is the filename (without '.js', succeeded by `-${i}` if
  // multiple features per module are provided). Must be unique!
  id: 'fine-channel-alias',

  // Required. Used in generated table header. Try to be as short as possible! Markdown is allowed.
  name: 'Fine channels (16bit)',

  // Optional. Is used as a footnote in generated table header. Markdown is allowed.
  description: 'Whether a channel defines exactly one fine channel alias',

  /**
   * Required. Checks if the given fixture uses this feature.
   * @param {Fixture} fixture The Fixture instance, see fixture-model.md
   * @returns {boolean} true if the fixture uses the feature
   */
  hasFeature: (fixture) => fixture.coarseChannels.some(
    (channel) => channel.fineChannelAliases.length === 1,
  ),
}];
```

Test fixtures are generated by calling `npm run build:test-fixtures` or simply `npm run build`. They are saved machine-readable as [`tests/test-fixtures.json`](../tests/test-fixtures.json) and human-readable as [`tests/test-fixtures.md`](../tests/test-fixtures.md).


================================================
FILE: docs/fixture-format.md
================================================
# JSON Fixture Format

This document gives a high-level overview of the concepts used in the JSON format of the fixture definitions collected in the *Open Fixture Library*. Those fixture definitions (also called *personalities* or *profiles*) specify information about a fixture's DMX controlment and general attributes.

*Note:* The fixture format is not intended to be used directly by other software, as it may introduce breaking, not-backwards-compatible changes at any time. Instead, write a plugin to transform the data into a more stable format for your application. Internally in *OFL*, working with the [fixture model](fixture-model.md) is preferred, as it eases access to the fixture data.


#### Table of contents
- [Schema](#schema)
- [Goals](#goals)
- [Directory structure](#directory-structure)
- [Fixture](#fixture)
  - [Modes](#modes)
  - [Channels](#channels)
    - [Capabilities](#capabilities)
    - [Fine channels](#fine-channels)
    - [Switching channels](#switching-channels)
  - [Matrices](#matrices)
    - [Matrix structure](#matrix-structure)
    - [Template channels](#template-channels)
  - [Wheels](#wheels)
    - [Gobo resources](#gobo-resources)
    - [Using wheels in capabilities](#using-wheels-in-capabilities)
  - [RDM (Remote Device Management) data](#rdm-remote-device-management-data)
- [Fixture redirects](#fixture-redirects)


## Schema

The [JSON Schema](https://json-schema.org/) files can be found in the [`schemas/`](../schemas/) directory. JSON Schema is a declarative way to describe allowed JSON properties and values. The [`fixtures-valid.js` test](../tests/fixtures-valid.js) automatically checks the fixtures against these schemas and additionally tests things like the correct use of channel keys etc. programmatically.

The schema files have a `version` property. Every time the schema is updated, this version needs to be incremented in all schema files using [semantic versioning](https://semver.org).

Given a version number MAJOR.MINOR.PATCH, increment the:
1. MAJOR version when you make incompatible schema changes.  
  i.e. old fixtures are not valid with the new schema anymore.
2. MINOR version when you add functionality in a backwards-compatible manner.  
  i.e. old fixtures are still valid with the new schema, new fixtures aren't valid with the old schema.
3. PATCH version when you make backwards-compatible bug fixes.  
  e.g. an upper bound to an integer value is added, which was likely done right in the past anyway.


## Goals

The JSON fixture format is intended to be

* readable by both humans and machines
* easily extendable
* as general as possible to include information for many fixture formats
* abstract where possible, specific where needed


## Directory structure

The manufacturer of a fixture is determined by its toplevel directory (relative to the `fixtures` directory), the fixture key is the filename without extension. Manufacturer data is stored in [`fixtures/manufacturers.json`](../fixtures/manufacturers.json).

`fixtures/register.json` (which is generated by [`cli/build-register.js`](../cli/build-register.js)) is just an index to make searching specific attributes possible without having to read the whole directory structure, which makes it faster. This file is gitignored.


## Fixture

A fixture's `shortName` must be unique amongst all fixtures.

The `physical` section describes properties not directly used in the DMX protocol, but often in lighting control software to display a preview of the fixtures in action.

The `lastModifyDate` should *NOT* be updated when updating fixture data that is not related to the fixtures capabilities or attributes. This includes things such as changing an author's names, updating links, or updating the order of channel modes.


### Modes

A fixture can have multiple *modes* (also sometimes called *personalities*) like "Basic 3-channel mode" or "Extended 5-channel mode". Our modes are not allowed to have the word "mode" in them, as it is automatically appended at the end.

A mode can contain the `physical` property to override specific physical data of the fixture. E.g. one mode could set the `power` value different than the fixture default.

A mode's `shortName` must be unique amongst all modes of the respective fixture. The `shortname` should be similar to what is displayed on the device's LCD/LED displays if that information is known (e.g. from the manual). If not known, the standard `1ch` format should be followed.


### Channels

All channels that are used in one or more modes are listed in `availableChannels`. The modes then only contain a list of the channel keys.

A channel's `defaultValue` is the DMX value that this channel is set to without the user interacting (most often, this will be `0`, but e.g. for Pan and Tilt channels, other values make more sense). Likewise, `highlightValue` specifies the DMX value if a user *highlights* this channel (defaults to the maximum DMX value). This is not available in every software.

If `constant` is `true`, the channel should be set to a static value in the operating lighting program. `precedence` specifies to which value the channel should be set if there are two conflicting active cues containing this channel: *HTP* (Highest takes precedence) or *LTP* (Latest (change) takes precedence).


#### Capabilities

A channel can do different things depending on which range its DMX value currently is in. Those ranges that can be triggered manually in many programs are called capabilities. Choose a `type` to declare which property of the fixture is changed by this capability, e.g. `ShutterOpen`, `Intensity` or `Pan`. Depending on the type, there exist more properties that further describe the capability, like the pan angle, the strobe rate or the wheel slot number. Most of these are physical entities that require to be entered using specific units (like `"10.5Hz"` or `"100%"`). Some entities offer keywords to replace specific percentage values, e.g. Distance: `"near"` = `"1%"`, `"far"` = `"100%"`. See the [full list of units, entities and capability types with their properties](capability-types.md). Example:

<!-- eslint-skip -->
```js
"availableChannels": {
  "Shutter": {
    "capabilities": [
      {
        "dmxRange": [0, 20],
        "type": "ShutterStrobe",
        "shutterEffect": "Closed",
        "comment": "Blackout"
      },
      {
        "dmxRange": [21, 255],
        "type": "ShutterStrobe",
        "shutterEffect": "Open"
      }
    ]
  }
}
```

If a channel consists of a single 0-255 capability, one should use the `capability` property (instead of `capabilities`), which only needs a single object instead of an array of objects. The `dmxRange` is implicit in this object (see example below).

Capabilities may be _steps_, which means that the whole DMX range has the same effect (e.g. "Gobo 1"), or _proportional_, which means that the effect is increasing / decreasing from the start to the end of the DMX range (e.g. "Intensity 0-100%"). A proportional capability can define a start and an end value of its type-specific properties. Example:

<!-- eslint-skip -->
```js
"availableChannels": {
  "Pan": {
    "capability": {
      "type": "Pan",
      "angleStart": "0deg",
      "angleEnd": "530deg"

      // DMX value 0 -> 0°
      // DMX value 127 -> 264°
      // DMX value 255 -> 530°
    }
  }
}
```

Please note that some properties don't allow start/end values, for example `hold`.

The `menuClick` property defines which DMX value to use if the whole capability is selected: `start` / `center` / `end` sets the channel's DMX value to the start / center / end of the range, respectively. `hidden` hides this capability from the trigger menu. This is one of those special features that are supported only by some lighting programs.


#### Fine channels

A channel can list `fineChannelAliases` to specify which channel keys are used to describe its finer variants. This results in two or more (8 bit) channels being combined into one 16 bit (or 24 bit, ...) channel to increase the resolution of the controlled functionality.

Example: Channel `Dimmer` contains `fineChannelAliases: ["Dimmer 16-bit", "Dimmer 24-bit"]`. Mode "Normal" uses only `Dimmer` in its channel list, mode "Fine" uses `Dimmer` and `Dimmer 16-bit`, mode "Super fine" uses all three.

See the [Generic Desk Channel fixture](../fixtures/generic/desk-channel.json) for a simple application example.

DMX values (e.g. default / highlight value, capability ranges) have to be entered in the maximum resolution: If one fine channel is defined, DMX values from 0 to 65535 are possible. If a lower resolution is sufficient for entering DMX values, the channel's `dmxValueResolution` property can be set to the desired resolution (`8bit` for 0…255, `16bit` for 0…65535, etc.). That means that both of the following examples are equivalent:

<!-- eslint-skip -->
```js
"Iris": {
  "fineChannelAliases": ["Iris fine"],
  "defaultValue": 33792,
  "capabilities": [
    {
      "dmxRange": [0, 33791],
      "type": "Iris",
      "openPercentStart": "100%",
      "openPercentEnd": "4%"
    },
    {
      "dmxRange": [33792, 65535],
      "type": "Iris",
      "openPercent": "4%"
    }
  ]
}
```
<!-- eslint-skip -->
```js
"Iris": {
  "fineChannelAliases": ["Iris fine"],
  "dmxValueResolution": "8bit",
  "defaultValue": 132,
  "capabilities": [
    {
      "dmxRange": [0, 131],
      "type": "Iris",
      "openPercentStart": "100%",
      "openPercentEnd": "4%"
    },
    {
      "dmxRange": [132, 255],
      "type": "Iris",
      "openPercent": "4%"
    }
  ]
}
```


#### Switching channels

A *switching channel* is a channel whose functionality depends on the value of another channel in the same mode.

E.g. in a given mode, the first channel could be used to select auto-programs and channel 2 could be either "Microphone Sensitivity" (if channel 1 is set to *Sound control*) or "Program Speed" (if channel 1 is set to anything else).

To define switching channels, add a `switchChannels` object to all capabilities of the dependency channel (the "Auto-Programs" channel in the example above). This object defines which *switching channel alias* is set to which *available channel key* if this capability is active. The switching channel alias is then used in the mode just like a regular channel. Note that a channel which defines switching channels needs an explicit `defaultValue` to make sure that the switching channel default is also well-defined.

See the [Futurelight PRO Slim PAR-7 HCL fixture](../fixtures/futurelight/pro-slim-par-7-hcl.json) for a simple application example.


### Matrices

Some fixtures have multiple light beams: A horizontal bar of LEDs, a pixel head with a grid of lamps, a fixture consisting of inner and outer rings of LEDs that can be controlled separately, etc. See the [Eurolite LED KLS 801](../fixtures/eurolite/led-kls-801.json) and the [Pixel Bar](https://open-fixture-library.org/categories/Pixel%20Bar) and [Matrix](https://open-fixture-library.org/categories/Matrix) categories for example fixtures.

#### Matrix structure

The information how these pixels are arranged is stored in the fixture's `matrix` object: Either by using the x × y × z syntax from `pixelCount` (e.g. [5, 5, 1] for a 5×5 matrix) or by naming each individual pixel in `pixelKeys`, e.g.:

<!-- eslint-skip -->
```js
"matrix": {
  "pixelKeys": [
    [
      [ null,  "Top",     null  ],
      ["Left", "Center", "Right"],
      [ null,  "Bottom",  null  ]
    ]
  ]
}
```

`null` refers to a "hole", i.e. there's no light beam, which allows for non-cubic frames. The above example represents 5 heads arranged like a "+".

Pixels can also be grouped if a fixture allows control in different fine grades, like fourths or halves of a light bar. There are three ways to define a pixel group:

* The keyword `all` indicates that all pixel keys should belong to this group. This can be useful for channels like "Red Master".
* An array of pixel keys exactly describes which pixels belong to this group.
* An object with `x`, `y`, `z` or `name` constraints. All pixels that fulfill *all* constraints belong to this group. XYZ constraints are `<=5`, `=5`, `>=5`, `3n` (divisible by 3), `3n+1` (divisible by 3 with remainder 1), `even` (≙ `2n`) and `odd` (≙ `2n+1`). Name constraints are regular expressions.

<!-- eslint-skip -->
```js
"matrix": {
  "pixelCount": [12, 1, 1],
  "pixelGroups": {
    "Master": "all",
    "1/3": { "x": ["<=4"] },
    "2/3": { "x": [">=5", "<=8"] },
    "3/3": { "x": [">=9"] },
    "Outer Thirds": ["1", "2", "3", "4", "9", "10", "11", "12"]
  }
}
```

See the [TMB Solaris Flare](../fixtures/tmb/solaris-flare.json) for a rather complex example of pixel group constraints.

Pixel groups can also be used to better describe the pixel structure, for example to define circular rings consisting of virtual pixels, even if these pixels don't physically exist and only the whole rings can be controlled.

<!-- eslint-skip -->
```js
"matrix": {
  "pixelKeys": [
    [
      [null,  null,  "O1",  "O2",  null,  null],
      [null,  "O3",  "M1",  "M2",  "O4",  null],
      ["O5",  "M3",  "I1",  "I2",  "M4",  "O6"],
      ["O7",  "M5",  "I3",  "I4",  "M6",  "O8"],
      [null,  "O9",  "M7",  "M8",  "O10", null],
      [null,  null,  "O11", "O12", null,  null]
    ]
  ],
  "pixelGroups": {
    "Inner ring": { "name": ["^I[1-4]$"] },
    "Middle ring": { "name": ["^M[1-8]$"] },
    "Outer ring":  { "name": ["^O\\d+$"] }
  }
}
```

#### Template channels

To reuse similar channels for each pixel or pixel group (like "Red&nbsp;1", Red&nbsp;2", ...), add template channels: They are specified very similar to normal available channels, except that each template channel key / alias / name must contain the `$pixelKey` variable:

<!-- eslint-skip -->
```js
"templateChannels": {
  "Red $pixelKey": {
    "fineChannelAliases": ["Red $pixelKey fine"],
    "capability": {
      "type": "ColorIntensity",
      "color": "Red"
    }
  }
}
```

Template channels can also introduce fine and switching channels. Specific resolved matrix channels can be overridden by available channels (e.g. if "Speed&nbsp;1" has different capabilities than "Speed&nbsp;2" until "Speed&nbsp;25"). See the [cameo Hydrabeam 300 RGBW](../fixtures/cameo/hydrabeam-300-rgbw.json) that uses these features.

Then, either use the resolved channel keys directly in a mode's channel list, or use a matrix channel insert block that repeats a list of template channels for a list of pixels:

<!-- eslint-skip -->
```js
{
  "name": "14-channel",
  "shortName": "14ch",
  "channels": [
    "Master Dimmer",
    "Strobe",
    {
      "insert": "matrixChannels", // static value for matrix channels
      "repeatFor": "eachPixelXYZ", // see below
      "channelOrder": "perPixel", // see below
      "templateChannels": [
        "Red $pixelKey",
        "Green $pixelKey",
        "Blue $pixelKey"
      ]
    }
  ]
}
```

`repeatFor` defines in which order and for which pixels / pixel groups the template channels shall be repeated. Possible values are:

* An array of pixel (group) keys in the proper order
* `"eachPixelABC"`: Gets computed into an alphanumerically sorted list of all pixelKeys
* `"eachPixelXYZ"` / `"eachPixelZYX"` / ...: Gets computed into a list of all pixelKeys, sorted by position, depending on the used `X`/`Y`/`Z` combination.
  - For example, `XYZ` orders the pixels like reading a book (latin script): First left-to-right (`X`, letter by letter), then top-to-bottom (`Y`, line by line), then front-to-back (`Z`, page by page). For a 3-dimensional 2×2×2 matrix, this results in `["(1, 1, 1)", "(2, 1, 1)", "(1, 2, 1)", "(2, 2, 1)", "(1, 1, 2)", "(2, 1, 2)", "(1, 2, 2)", "(2, 2, 2)]"`.
* `"eachPixelGroup"`: Gets computed into an array of all pixel group keys, ordered by appearance in the JSON file.
  - For the above [matrix structure](#matrix-structure) example, this results in `["Inner ring", "Middle ring", "Outer ring"]`.

`channelOrder` defines how the channels are ordered. Possible values are:

* `"perPixel"`: For the above [matrix structure](#matrix-structure) example, this results in
  ```json
  [
    "Red Inner ring", "Green Inner ring", "Blue Inner ring",
    "Red Middle ring", "Green Middle ring", "Blue Middle ring",
    "Red Outer ring", "Green Outer ring", "Blue Outer ring"
  ]
  ```
* `"perChannel"`: For the above [matrix structure](#matrix-structure) example, this results in
  ```json
  [
    "Red Inner ring", "Red Middle ring", "Red Outer ring",
    "Green Inner ring", "Green Middle ring", "Green Outer ring",
    "Blue Inner ring", "Blue Middle ring", "Blue Outer ring"
  ]
  ```

### Wheels

Fixtures (usually moving heads) can have internal wheels, where you can select the active slot via DMX. In our fixture format, wheels are defined in the fixture's `wheels` object, where the key defines the wheel's name.

The slots in a wheel have types, similar to [capability types](capability-types.md). Depending on the type, different properties can be set on the slot.

* `Open` / `Closed`
* `Color`
  - `name` (string)
  - `colors` (array of hex strings)
  - `colorTemperature` ([Entity](capability-types.md#possible-entities-and-keywords) *ColorTemperature*)
* `Gobo`
  - `resource` (resource reference string, see below)
  - `name` (string)
* `Prism`
  - `name` (string)
  - `facets` (integer)
* `Iris`
  - `openPercent` ([Entity](capability-types.md#possible-entities-and-keywords) *IrisPercent*)
* `Frost`
  - `frostIntensity` ([Entity](capability-types.md#possible-entities-and-keywords) *Percent*)
* `AnimationGoboStart`
  - `name` (string)
* `AnimationGoboEnd`

Animation Gobo slots are wider than normal gobos (sometimes they fill the whole wheel); rotating the wheel over these slots creates an animation. To model the wider slots, an `AnimationGoboEnd` slot must be used directly after an `AnimationGoboStart` slot.

#### Gobo resources

Gobos are referenced with a resource reference in the form `gobos/<gobo key>`.

Gobo resources are stored in the [`resources/gobos/`](../resources/gobos/) directory. Each one consists of a JSON file (`<gobo key>.json`) describing the gobo (with name, keywords, and a source where the gobo image was extracted from) and the gobo image itself (`<gobo key>.svg` or `<gobo key>.png`).

In the [`resources/gobos/aliases/`](../resources/gobos/aliases/) directory, sets of aliases can be defined as separate JSON files in which aliases are mapped to gobo keys. This is useful for plugins (e.g. the QLC+ import plugin knows which OFL gobo key to use when QLC+ gobo `Others/0001.svg` is referenced in a fixture). It also enables referencing gobos in fixture files with an alias like `gobos/aliases/<alias file>/<alias key>` (e.g. a Robe fixture could reference `gobos/aliases/robe/15020246-rafia`) to make validating the gobo information easier with a manual where product numbers are specified.

#### Using wheels in capabilities

In wheel-related capabilities, the `wheel` property references the wheel by its name. If the `wheel` property is not set, the channel name is used as wheel name.

`WheelSlot` capabilities select a slot from the wheel. If the capability selects the place in between two slots, the `slotNumber` property can be set to a fractional value (or be proportional as `slotNumberStart` / `slotNumberEnd`). See also [footnote *slotNumber* in the capability types documentation](capability-types.md#property-slotnumber).

`WheelShake` capabilities set the shaking (i.e. continuously rotating back and forth) of the whole wheel around the currently selected slot. A slot can also be activated directly by setting the `slotNumber` property like in `WheelSlot` capabilities.  
By setting the property `isShaking` to `slot`, one can specify that only the currently selected slot rotates back and forth around its center (sometimes called *Gobo bounce effect*) instead of the whole wheel.

`WheelSlotRotation` capabilities control the rotation of the currently selected slot (i.e. Gobo, Prism, etc.). A slot can also be activated directly by setting the `slotNumber` property like in `WheelSlot` capabilities.

`WheelRotation` capabilities rotate the whole wheel, i.e. over all slots.


### RDM (Remote Device Management) data

We link to [Open Lighting's RDM database](http://rdm.openlighting.org) if possible. Thus, we need to specify the RDM manufacturer ID per manufacturer and the RDM model ID per fixture. Additionally, each mode is mapped to the respective RDM personality via the `rdmPersonalityIndex` property. To ensure compatibility, we also track, for which RDM fixture software (firmware) version the mode indices are specified.

If RDM manufacturer and model ID are known, we open the respective fixture page when going to `/rdm` (handled in [`ui/pages/rdm.vue`](../ui/pages/rdm.vue)).


## Fixture redirects

Fixtures may be renamed by their manufacturers. If we would just update the fixture definition, links to its old fixture page would now lead to a *404 Not found* error. Instead, we add a fixture redirect JSON file (see [its schema](../schemas/fixture-redirect.json)) with the old manufacturer / fixture key, pointing to the updated fixture definition. See [Minuit Une M-Carré](../fixtures/minuit-une/m-carre.json) (and its [fixture page on OFL](https://open-fixture-library.org/minuit-une/m-carre)) as an example.

Another use case for redirects are fixtures that are sold under different brands, but which are technically the same. We add them to the library only once and let redirects with other names point to it.


================================================
FILE: docs/fixture-model.md
================================================
# Fixture model

Instead of parsing [fixtures' JSON data](fixture-format.md) directly, it is recommended to use the model. We developed it to ease handling complicated fixture features like fine channels or switching channels.

All model functions and classes are documented with [JSDoc](https://jsdoc.app/). Those annotations are converted to Markdown with [jsdoc2md](https://github.com/jsdoc2md/jsdoc-to-markdown) to obtain an [API reference](model-api.md).

The model uses [ES2015 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) in [ES2015 modules](https://nodejs.org/api/esm.html) to represent the fixtures. E.g., `fixtureFromRepository('cameo', 'nanospot-120')` returns a [`Fixture`](model-api.md#Fixture) object, instantiated with the specified fixture's data. These objects have several convenient properties that allow easy usage of the fixture data in [plugins](plugins.md), [UI](ui.md) and more.

All model classes are located in the [`lib/model/`](../lib/model) directory. When using the model, it usually suffices to import the `fixtureFromRepository` function from `model.js` which returns a `Fixture` instance:

```js
import { fixtureFromRepository } from './lib/model.js';

const myFixture = await fixtureFromRepository('cameo', 'nanospot-120'); // instanceof Fixture

const physicalData = myFixture.physical; // instanceof Physical
const panFine = myFixture.getChannelByKey('Pan fine'); // instanceof FineChannel

if (panFine.coarseChannel.hasHighlightValue) {
  console.log(`Highlight at ${panFine.coarseChannel.highlightValue}`);
}
```

Model properties are always implemented using getters and setters. To store data, we use backing fields (an internal property prefixed with underscore, e.g. `_jsonObject`) to hold the data. The backing field should never be accessed directly, but only through its getter and setter functions (without underscore).

Avoid returning `undefined` by returning smart default values if necessary. If the default value is not `null`, also provide a `hasXY` boolean getter function. Properties that need further computation or create other objects should be cached using the [lazy-loading property pattern](https://humanwhocodes.com/blog/2021/04/lazy-loading-property-pattern-javascript/) using the [`cacheResult` function](../lib/cache-result.js).

```js
import cacheResult from '../lib/cache-result.js';

export default class Fixture {
  constructor(jsonObject) {
    this._jsonObject = jsonObject;
  }

  // returns backing field to avoid accessing _jsonObject from outside
  get jsonObject() {
    return this._jsonObject;
  }

  // required, no default needed
  get name() {
    return this._jsonObject.name;
  }

  // defaults to the name
  get shortName() {
    return this._jsonObject.shortName || this._jsonObject.name;
  }

  get hasShortName() {
    return 'shortName' in this._jsonObject;
  }

  // avoid creating a new Meta object for each property access by caching it
  get meta() {
    return cacheResult(this, 'meta', new Meta(this._jsonObject.meta));
  }

  // defaults to null as there is no meaningful other default
  get rdm() {
    return this._jsonObject.rdm || null;
  }

  // ...
}
```

## Resource references

Resources (e.g. gobo images) are embedded by the model into the fixture JSON, i.e. instead of returning a string, `WheelSlot.resource` will return the resource object. The relevant code is in the `embedResourcesIntoFixtureJson` function in [`lib/model.js](../lib/model.js).

Thus, all information needed for the fixure is still included in the fixture JSON.


================================================
FILE: docs/model-api.md
================================================
## Classes

<dl>
<dt><a href="#AbstractChannel">AbstractChannel</a></dt>
<dd><p>Base class for channels.</p>
</dd>
<dt><a href="#Capability">Capability</a></dt>
<dd><p>A capability represents a range of a channel.</p>
</dd>
<dt><a href="#CoarseChannel">CoarseChannel</a> ⇐ <code><a href="#AbstractChannel">AbstractChannel</a></code></dt>
<dd><p>A single DMX channel, either created as availableChannel or resolved templateChannel.
Only the MSB (most significant byte) channel if it&#39;s a multi-byte channel.</p>
</dd>
<dt><a href="#Entity">Entity</a></dt>
<dd><p>A physical entity with numerical value and unit information.</p>
</dd>
<dt><a href="#FineChannel">FineChannel</a> ⇐ <code><a href="#AbstractChannel">AbstractChannel</a></code></dt>
<dd><p>Represents a finer channel of a 16+ bit channel.
Also called LSB (least significant byte) channel.</p>
</dd>
<dt><a href="#Fixture">Fixture</a></dt>
<dd><p>A physical DMX device.</p>
</dd>
<dt><a href="#Manufacturer">Manufacturer</a></dt>
<dd><p>A company or brand that produces fixtures. A fixture is associated to exactly one manufacturer.</p>
</dd>
<dt><a href="#Matrix">Matrix</a></dt>
<dd><p>Contains information of how the pixels in a 1-, 2- or 3-dimensional space are arranged and named.</p>
</dd>
<dt><a href="#Meta">Meta</a></dt>
<dd><p>Information about a fixture&#39;s author and history.</p>
</dd>
<dt><a href="#Mode">Mode</a></dt>
<dd><p>A fixture&#39;s configuration that enables a fixed set of channels and channel order.</p>
</dd>
<dt><a href="#NullChannel">NullChannel</a> ⇐ <code><a href="#CoarseChannel">CoarseChannel</a></code></dt>
<dd><p>Dummy channel used to represent <code>null</code> in a mode&#39;s channel list.</p>
</dd>
<dt><a href="#Physical">Physical</a></dt>
<dd><p>A fixture&#39;s technical data, belonging to the hardware and not the DMX protocol.</p>
</dd>
<dt><a href="#Range">Range</a></dt>
<dd><p>Represents a range from one integer to a higher or equal integer. Primarily used for DMX ranges of capabilities.</p>
</dd>
<dt><a href="#Resource">Resource</a></dt>
<dd><p>Information about a resource.</p>
</dd>
<dt><a href="#SwitchingChannel">SwitchingChannel</a> ⇐ <code><a href="#AbstractChannel">AbstractChannel</a></code></dt>
<dd><p>Represents a channel that switches its behavior depending on trigger channel&#39;s value.
The different behaviors are implemented as different <a href="#CoarseChannel">CoarseChannel</a>s or <a href="#FineChannel">FineChannel</a>s.</p>
</dd>
<dt><a href="#TemplateChannel">TemplateChannel</a></dt>
<dd><p>Represents a blueprint channel of which several similar channels can be generated.
Currently used to create matrix channels.</p>
</dd>
<dt><a href="#Wheel">Wheel</a></dt>
<dd><p>Information about a fixture&#39;s wheel.</p>
</dd>
<dt><a href="#WheelSlot">WheelSlot</a></dt>
<dd><p>Information about a single wheel slot (or a split slot).</p>
</dd>
</dl>

## Typedefs

<dl>
<dt><a href="#Resolution">Resolution</a> : <code>number</code></dt>
<dd><p>1 for 8bit, 2 for 16bit, ...</p>
</dd>
<dt><a href="#TriggerCapability">TriggerCapability</a> : <code>object</code></dt>
<dd></dd>
<dt><a href="#SwitchingChannelBehavior">SwitchingChannelBehavior</a> : <code>&#x27;keyOnly&#x27;</code> | <code>&#x27;defaultOnly&#x27;</code> | <code>&#x27;switchedOnly&#x27;</code> | <code>&#x27;all&#x27;</code></dt>
<dd></dd>
</dl>

<a name="AbstractChannel"></a>

## *AbstractChannel*
Base class for channels.

**Kind**: global abstract class  

* *[AbstractChannel](#AbstractChannel)*
    * *[new AbstractChannel(key)](#new_AbstractChannel_new)*
    * **[.fixture](#AbstractChannel+fixture) ⇒ [<code>Fixture</code>](#Fixture)**
    * *[.key](#AbstractChannel+key) ⇒ <code>string</code>*
    * *[.name](#AbstractChannel+name) ⇒ <code>string</code>*
    * *[.uniqueName](#AbstractChannel+uniqueName) ⇒ <code>string</code>*
    * *[.pixelKey](#AbstractChannel+pixelKey) ⇒ <code>string</code> \| <code>null</code>*
    * *[.pixelKey](#AbstractChannel+pixelKey)*

<a name="new_AbstractChannel_new"></a>

### *new AbstractChannel(key)*
Create a new AbstractChannel instance. Call this from child classes as `super(key)`.

**Throws**:

- <code>TypeError</code> If the AbstractChannel is instantiated directly.


| Param | Type | Description |
| --- | --- | --- |
| key | <code>string</code> | The channel's identifier, must be unique in the fixture. |

<a name="AbstractChannel+fixture"></a>

### **abstractChannel.fixture ⇒ [<code>Fixture</code>](#Fixture)**
**Kind**: instance abstract property of [<code>AbstractChannel</code>](#AbstractChannel)  
**Returns**: [<code>Fixture</code>](#Fixture) - The fixture instance this channel is associated to.  
**Throws**:

- <code>TypeError</code> If this property is not overridden in child classes.

<a name="AbstractChannel+key"></a>

### *abstractChannel.key ⇒ <code>string</code>*
**Kind**: instance property of [<code>AbstractChannel</code>](#AbstractChannel)  
**Returns**: <code>string</code> - The channel key.  
<a name="AbstractChannel+name"></a>

### *abstractChannel.name ⇒ <code>string</code>*
Override this method for more sensible implementation.

**Kind**: instance property of [<code>AbstractChannel</code>](#AbstractChannel)  
**Returns**: <code>string</code> - The channel key (as name).  
<a name="AbstractChannel+uniqueName"></a>

### *abstractChannel.uniqueName ⇒ <code>string</code>*
**Kind**: instance property of [<code>AbstractChannel</code>](#AbstractChannel)  
**Returns**: <code>string</code> - Unique version of this channel's name.  
**See**: [uniqueChannelNames](#Fixture+uniqueChannelNames)  
<a name="AbstractChannel+pixelKey"></a>

### *abstractChannel.pixelKey ⇒ <code>string</code> \| <code>null</code>*
**Kind**: instance property of [<code>AbstractChannel</code>](#AbstractChannel)  
**Returns**: <code>string</code> \| <code>null</code> - The key of the pixel (group) that this channel is associated to. Defaults to null.  
<a name="AbstractChannel+pixelKey"></a>

### *abstractChannel.pixelKey*
**Kind**: instance property of [<code>AbstractChannel</code>](#AbstractChannel)  

| Param | Type | Description |
| --- | --- | --- |
| pixelKey | <code>string</code> \| <code>null</code> | The key of the pixel (group) that this channel is associated to. Set to null to dereference a channel from a pixel (group). |

<a name="Capability"></a>

## Capability
A capability represents a range of a channel.

**Kind**: global class  

* [Capability](#Capability)
    * [new Capability(jsonObject, resolution, channel)](#new_Capability_new)
    * _instance_
        * [.jsonObject](#Capability+jsonObject) ⇒ <code>object</code>
        * [.dmxRange](#Capability+dmxRange) ⇒ [<code>Range</code>](#Range)
        * [.rawDmxRange](#Capability+rawDmxRange) ⇒ [<code>Range</code>](#Range)
        * [.type](#Capability+type) ⇒ <code>string</code>
        * [.name](#Capability+name) ⇒ <code>string</code>
        * [.hasComment](#Capability+hasComment) ⇒ <code>boolean</code>
        * [.comment](#Capability+comment) ⇒ <code>string</code>
        * [.isStep](#Capability+isStep) ⇒ <code>boolean</code>
        * [.isInverted](#Capability+isInverted) ⇒ <code>boolean</code>
        * [.usedStartEndEntities](#Capability+usedStartEndEntities) ⇒ <code>Array.&lt;string&gt;</code>
        * [.helpWanted](#Capability+helpWanted) ⇒ <code>string</code> \| <code>null</code>
        * [.menuClick](#Capability+menuClick) ⇒ <code>&#x27;start&#x27;</code> \| <code>&#x27;center&#x27;</code> \| <code>&#x27;end&#x27;</code> \| <code>&#x27;hidden&#x27;</code>
        * [.menuClickDmxValue](#Capability+menuClickDmxValue) ⇒ <code>number</code>
        * [.switchChannels](#Capability+switchChannels) ⇒ <code>Record.&lt;string, string&gt;</code>
        * [.shutterEffect](#Capability+shutterEffect) ⇒ <code>string</code> \| <code>null</code>
        * [.color](#Capability+color) ⇒ <code>&#x27;Red&#x27;</code> \| <code>&#x27;Green&#x27;</code> \| <code>&#x27;Blue&#x27;</code> \| <code>&#x27;Cyan&#x27;</code> \| <code>&#x27;Magenta&#x27;</code> \| <code>&#x27;Yellow&#x27;</code> \| <code>&#x27;Amber&#x27;</code> \| <code>&#x27;White&#x27;</code> \| <code>&#x27;Warm White&#x27;</code> \| <code>&#x27;Cold White&#x27;</code> \| <code>&#x27;UV&#x27;</code> \| <code>&#x27;Lime&#x27;</code> \| <code>&#x27;Indigo&#x27;</code> \| <code>null</code>
        * [.colors](#Capability+colors) ⇒ <code>object</code> \| <code>null</code>
        * [.wheels](#Capability+wheels) ⇒ [<code>Array.&lt;Wheel&gt;</code>](#Wheel)
        * [.isShaking](#Capability+isShaking) ⇒ <code>&#x27;slot&#x27;</code> \| <code>&#x27;wheel&#x27;</code>
        * [.effectName](#Capability+effectName) ⇒ <code>string</code> \| <code>null</code>
        * [.effectPreset](#Capability+effectPreset) ⇒ <code>string</code> \| <code>null</code>
        * [.isSoundControlled](#Capability+isSoundControlled) ⇒ <code>boolean</code>
        * [.randomTiming](#Capability+randomTiming) ⇒ <code>boolean</code>
        * [.blade](#Capability+blade) ⇒ <code>&#x27;Top&#x27;</code> \| <code>&#x27;Right&#x27;</code> \| <code>&#x27;Bottom&#x27;</code> \| <code>&#x27;Left&#x27;</code> \| <code>number</code> \| <code>null</code>
        * [.fogType](#Capability+fogType) ⇒ <code>&#x27;Fog&#x27;</code> \| <code>&#x27;Haze&#x27;</code> \| <code>null</code>
        * [.hold](#Capability+hold) ⇒ [<code>Entity</code>](#Entity) \| <code>null</code>
        * [.speed](#Capability+speed) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.duration](#Capability+duration) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.time](#Capability+time) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.brightness](#Capability+brightness) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.slotNumber](#Capability+slotNumber) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.wheelSlot](#Capability+wheelSlot) ⇒ [<code>Array.&lt;WheelSlot&gt;</code>](#WheelSlot) \| <code>null</code>
        * [.angle](#Capability+angle) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.horizontalAngle](#Capability+horizontalAngle) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.verticalAngle](#Capability+verticalAngle) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.colorTemperature](#Capability+colorTemperature) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.soundSensitivity](#Capability+soundSensitivity) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.shakeAngle](#Capability+shakeAngle) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.shakeSpeed](#Capability+shakeSpeed) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.distance](#Capability+distance) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.openPercent](#Capability+openPercent) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.frostIntensity](#Capability+frostIntensity) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.insertion](#Capability+insertion) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.fogOutput](#Capability+fogOutput) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.parameter](#Capability+parameter) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code>
        * [.getDmxRangeWithResolution(desiredResolution)](#Capability+getDmxRangeWithResolution) ⇒ [<code>Range</code>](#Range)
        * [.canCrossfadeTo(nextCapability)](#Capability+canCrossfadeTo) ⇒ <code>boolean</code>
        * [.getMenuClickDmxValueWithResolution(desiredResolution)](#Capability+getMenuClickDmxValueWithResolution) ⇒ <code>number</code>
        * [.isSlotType(slotType)](#Capability+isSlotType) ⇒ <code>boolean</code>
        * [._getStartEndArray(property)](#Capability+_getStartEndArray) ⇒ [<code>Array.&lt;Entity&gt;</code>](#Entity) \| <code>null</code> ℗
    * _static_
        * [.START_END_ENTITIES](#Capability.START_END_ENTITIES) ⇒ <code>Array.&lt;string&gt;</code>

<a name="new_Capability_new"></a>

### new Capability(jsonObject, resolution, channel)
Create a new Capability instance.


| Param | Type | Description |
| --- | --- | --- |
| jsonObject | <code>object</code> | The capability data from the channel's JSON. |
| resolution | [<code>Resolution</code>](#Resolution) | How fine this capability is declared. |
| channel | [<code>CoarseChannel</code>](#CoarseChannel) | The channel instance this channel is associated to. |

<a name="Capability+jsonObject"></a>

### capability.jsonObject ⇒ <code>object</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>object</code> - The capability data from the channel's JSON.  
<a name="Capability+dmxRange"></a>

### capability.dmxRange ⇒ [<code>Range</code>](#Range)
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: [<code>Range</code>](#Range) - The capability's DMX bounds in the channel's highest resolution.  
<a name="Capability+rawDmxRange"></a>

### capability.rawDmxRange ⇒ [<code>Range</code>](#Range)
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: [<code>Range</code>](#Range) - The capability's DMX bounds from the JSON data.  
<a name="Capability+type"></a>

### capability.type ⇒ <code>string</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>string</code> - Describes which feature is controlled by this capability.  
<a name="Capability+name"></a>

### capability.name ⇒ <code>string</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>string</code> - Short one-line description of the capability, generated from the capability's type and type-specific properties.  
<a name="Capability+hasComment"></a>

### capability.hasComment ⇒ <code>boolean</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>boolean</code> - Whether this capability has a comment set.  
<a name="Capability+comment"></a>

### capability.comment ⇒ <code>string</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>string</code> - Short additional information on this capability  
<a name="Capability+isStep"></a>

### capability.isStep ⇒ <code>boolean</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>boolean</code> - Whether this capability has the same effect from the start to the end.  
<a name="Capability+isInverted"></a>

### capability.isInverted ⇒ <code>boolean</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>boolean</code> - Whether this capability ranges from a high to a low value (e.g. speed fast…slow).  
<a name="Capability+usedStartEndEntities"></a>

### capability.usedStartEndEntities ⇒ <code>Array.&lt;string&gt;</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>Array.&lt;string&gt;</code> - Names of non-null properties with (maybe equal) start/end value.  
<a name="Capability+helpWanted"></a>

### capability.helpWanted ⇒ <code>string</code> \| <code>null</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>string</code> \| <code>null</code> - A string describing the help that is needed for this capability, or null if no help is needed.  
<a name="Capability+menuClick"></a>

### capability.menuClick ⇒ <code>&#x27;start&#x27;</code> \| <code>&#x27;center&#x27;</code> \| <code>&#x27;end&#x27;</code> \| <code>&#x27;hidden&#x27;</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>&#x27;start&#x27;</code> \| <code>&#x27;center&#x27;</code> \| <code>&#x27;end&#x27;</code> \| <code>&#x27;hidden&#x27;</code> - The method which DMX value to set when this capability is chosen in a lighting software's auto menu.  
<a name="Capability+menuClickDmxValue"></a>

### capability.menuClickDmxValue ⇒ <code>number</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>number</code> - The DMX value to set when this capability is chosen in a lighting software's auto menu.  
<a name="Capability+switchChannels"></a>

### capability.switchChannels ⇒ <code>Record.&lt;string, string&gt;</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>Record.&lt;string, string&gt;</code> - Switching channel aliases mapped to the channel key to which the switching channel should be set to when this capability is activated.  
<a name="Capability+shutterEffect"></a>

### capability.shutterEffect ⇒ <code>string</code> \| <code>null</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>string</code> \| <code>null</code> - Behavior for the shutter, for example 'Closed', 'Strobe' or 'Pulse'. Defaults to null.  
<a name="Capability+color"></a>

### capability.color ⇒ <code>&#x27;Red&#x27;</code> \| <code>&#x27;Green&#x27;</code> \| <code>&#x27;Blue&#x27;</code> \| <code>&#x27;Cyan&#x27;</code> \| <code>&#x27;Magenta&#x27;</code> \| <code>&#x27;Yellow&#x27;</code> \| <code>&#x27;Amber&#x27;</code> \| <code>&#x27;White&#x27;</code> \| <code>&#x27;Warm White&#x27;</code> \| <code>&#x27;Cold White&#x27;</code> \| <code>&#x27;UV&#x27;</code> \| <code>&#x27;Lime&#x27;</code> \| <code>&#x27;Indigo&#x27;</code> \| <code>null</code>
**Kind**: instance property of [<code>Capability</code>](#Capability)  
**Returns**: <code>&#x27;Red&#x27;</code> \| <code>&#x27;Green&#x27;</code> \| <code>&#x27;Blue&#x27;</code> \| <code>&#x27;Cyan&#x27;</code> \| <code>&#x27;Magenta&#x27;</code> \| <code>&#x27;Yellow&#x27;</code> \| <code>&#x27;Amber&#x27;</code> \| <code>&#x27;White&#x27;</code> \| <code>&#x27;Warm White&#x27;</code> \| <code>&#
Download .txt
gitextract_hf70e5ny/

├── .devcontainer/
│   └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── new-plugin.md
│   ├── aw/
│   │   └── actions-lock.json
│   ├── dependabot.yaml
│   ├── lighthouserc.json
│   └── workflows/
│       ├── external-links-checker.yaml
│       ├── fixture-metadata-triage.lock.yml
│       ├── fixture-metadata-triage.md
│       ├── lighthouse-production.yaml
│       ├── lighthouse-review.yaml
│       └── test.yaml
├── .gitignore
├── .stylelintrc.yaml
├── LICENSE
├── README.md
├── cli/
│   ├── build-plugin-data.js
│   ├── build-register.js
│   ├── build-test-fixtures.js
│   ├── debug-env-variables.js
│   ├── diff-plugin-outputs.js
│   ├── export-fixture.js
│   ├── import-fixture.js
│   └── run-export-test.js
├── docs/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── README.md
│   ├── capability-types.md
│   ├── environment-variables.md
│   ├── fixture-features.md
│   ├── fixture-format.md
│   ├── fixture-model.md
│   ├── model-api.md
│   ├── plugins.md
│   ├── rest-api.md
│   ├── testing.md
│   └── ui.md
├── eslint.config.js
├── fixtures/
│   ├── 5star-systems/
│   │   └── spica-250m.json
│   ├── abstract/
│   │   └── twister-4.json
│   ├── acoustic-control/
│   │   └── par-180-cob-3in1.json
│   ├── adb/
│   │   ├── alc4.json
│   │   ├── europe-105.json
│   │   └── warp-m.json
│   ├── afx/
│   │   └── lmh460z.json
│   ├── alien-pro/
│   │   └── alien-s.json
│   ├── american-dj/
│   │   ├── 12p-hex-ip.json
│   │   ├── 18p-hex-ip.json
│   │   ├── 7p-hex-ip.json
│   │   ├── auto-spot-150.json
│   │   ├── boom-box-fx2.json
│   │   ├── cob-cannon-wash.json
│   │   ├── crazy-pocket-8.json
│   │   ├── dekker-led.json
│   │   ├── dotz-par.json
│   │   ├── encore-lp12z-ip.json
│   │   ├── encore-profile-1000-ww.json
│   │   ├── flat-par-qa12.json
│   │   ├── flat-par-qa12xs.json
│   │   ├── fog-fury-jett-pro.json
│   │   ├── galaxian-3d.json
│   │   ├── illusion-dotz-4-4.json
│   │   ├── inno-pocket-beam-q4.json
│   │   ├── inno-pocket-fusion.json
│   │   ├── inno-pocket-spot.json
│   │   ├── inno-spot-pro.json
│   │   ├── mega-bar-50rgb-rc.json
│   │   ├── mega-bar-50rgb.json
│   │   ├── mega-bar-rgba.json
│   │   ├── mega-hex-par.json
│   │   ├── mega-par-profile-plus.json
│   │   ├── mega-tripar-profile-plus.json
│   │   ├── mega-tripar-profile.json
│   │   ├── mod-hex100.json
│   │   ├── pocket-pro.json
│   │   ├── quad-phase-hp.json
│   │   ├── revo-4-ir.json
│   │   ├── revo-burst.json
│   │   ├── revo-sweep.json
│   │   ├── saber-spot-rgbw.json
│   │   ├── starburst.json
│   │   ├── stinger-ii.json
│   │   ├── stinger-spot.json
│   │   ├── ultra-hex-bar-12.json
│   │   ├── uv-eco-bar.json
│   │   ├── vbar-pak.json
│   │   ├── vizi-q-wash7.json
│   │   ├── vizi-spot-led-pro.json
│   │   └── xs-400.json
│   ├── ape-labs/
│   │   └── lightcan.json
│   ├── aputure/
│   │   ├── ls-1200d-pro.json
│   │   ├── ls-300x.json
│   │   ├── ls-600d-pro.json
│   │   ├── ls-600d.json
│   │   ├── ls-600x-pro.json
│   │   └── nova-p300c.json
│   ├── arri/
│   │   ├── broadcaster-2-plus.json
│   │   ├── l10-c.json
│   │   ├── l5-c.json
│   │   ├── l7-c.json
│   │   ├── skypanel-s120c.json
│   │   ├── skypanel-s30c.json
│   │   ├── skypanel-s360c.json
│   │   └── skypanel-s60c.json
│   ├── astera/
│   │   ├── ax3-lightdrop.json
│   │   ├── fp1-titan-tube.json
│   │   ├── fp2-helios-tube.json
│   │   ├── fp3-hyperion-tube.json
│   │   └── fp5-nyx-bulb.json
│   ├── audibax/
│   │   └── boston-60.json
│   ├── ayra/
│   │   ├── compar-20.json
│   │   └── tdc-triple-burst.json
│   ├── ayrton/
│   │   ├── diablo-s.json
│   │   ├── diablo-tc.json
│   │   └── magicblade-fx.json
│   ├── beamz/
│   │   ├── h2000-faze-machine.json
│   │   ├── panther-7r.json
│   │   ├── pls25-par.json
│   │   └── triple-flex-centre-pro-led.json
│   ├── big-dipper/
│   │   ├── lp001.json
│   │   └── ls90.json
│   ├── bitfocus/
│   │   └── companion-v2.json
│   ├── blizzard/
│   │   ├── hotbox-exa.json
│   │   ├── hotbox-rgbw.json
│   │   ├── puck-rgbaw.json
│   │   └── rokbox-rgbw.json
│   ├── boomtonedj/
│   │   ├── crazy-spot-30.json
│   │   ├── silentpar-12x10w-5in1.json
│   │   ├── silentpar-12x10w-6in1.json
│   │   ├── silentpar-12x3w-3in1.json
│   │   ├── silentpar-5x10w-5in1.json
│   │   ├── silentpar-5x10w-6in1.json
│   │   ├── silentpar-5x3w-3in1.json
│   │   ├── silentpar-7x10w-5in1.json
│   │   ├── silentpar-7x10w-6in1.json
│   │   ├── silentpar-7x3w-3in1.json
│   │   └── xtrem-led.json
│   ├── briteq/
│   │   ├── beam-fury-1.json
│   │   ├── beamspot1-dmx-fc.json
│   │   ├── bt-coloray-120r.json
│   │   ├── bt-coloray-18fcr.json
│   │   ├── bt-coloray-60r.json
│   │   ├── bt-ledrotor.json
│   │   ├── bt-stagepar-6in1.json
│   │   ├── btx-cirrus-ii.json
│   │   ├── btx-titan.json
│   │   ├── cob-slim-100-rgb.json
│   │   ├── pro-beamer-zoom-indoor.json
│   │   └── pro-beamer-zoom-outdoor.json
│   ├── cameo/
│   │   ├── auro-beam-150.json
│   │   ├── auro-spot-100.json
│   │   ├── auro-spot-200.json
│   │   ├── auro-spot-300.json
│   │   ├── auro-spot-400.json
│   │   ├── auro-spot-z300.json
│   │   ├── flash-matrix-250.json
│   │   ├── flat-par-can-rgb-10-ir.json
│   │   ├── flat-par-can-tri-5x-3w-ir.json
│   │   ├── flat-par-can-tri-7x-3w-ir.json
│   │   ├── flat-pro-18.json
│   │   ├── flat-pro-flood-600-ip65.json
│   │   ├── flat-pro-flood-ip65-tri.json
│   │   ├── gobo-scanner-80.json
│   │   ├── hydrabeam-100.json
│   │   ├── hydrabeam-300-rgbw.json
│   │   ├── hydrabeam-400-rgbw.json
│   │   ├── instant-air-1000-pro.json
│   │   ├── instant-air-2000-pro.json
│   │   ├── instant-hazer-1500-t-pro.json
│   │   ├── ioda-1000-rgb.json
│   │   ├── ioda-400-rgy.json
│   │   ├── ioda-600-rgb.json
│   │   ├── multi-fx-bar.json
│   │   ├── multi-par-cob-1.json
│   │   ├── nanospot-120.json
│   │   ├── outdoor-par-tri-12.json
│   │   ├── q-spot-40-cw.json
│   │   ├── q-spot-40-rgbw.json
│   │   ├── root-par-6.json
│   │   ├── steam-wizard-1000.json
│   │   ├── steam-wizard-2000.json
│   │   ├── storm.json
│   │   ├── thunder-wash-100-rgb.json
│   │   ├── thunder-wash-100-w.json
│   │   ├── thunder-wash-600-rgb.json
│   │   ├── thunder-wash-600-rgbw.json
│   │   ├── thunder-wash-600-w.json
│   │   ├── ts-100-ww.json
│   │   ├── ts-200-fc.json
│   │   ├── ts-60-rgbw.json
│   │   ├── ts60.json
│   │   ├── zenit-w600.json
│   │   └── zenit-z120.json
│   ├── chauvet-dj/
│   │   ├── colorband-pix-ip.json
│   │   ├── colorband-pix.json
│   │   ├── corepar-uv-usb.json
│   │   ├── dmx-4.json
│   │   ├── eve-p-100-ww.json
│   │   ├── eve-p-130-rgb.json
│   │   ├── freedom-h1.json
│   │   ├── geyser-rgb.json
│   │   ├── gigbar-2.json
│   │   ├── hurricane-1600.json
│   │   ├── hurricane-haze-1dx.json
│   │   ├── hurricane-haze-2d.json
│   │   ├── intimidator-spot-110.json
│   │   ├── intimidator-spot-160.json
│   │   ├── intimidator-spot-260.json
│   │   ├── kinta-x.json
│   │   ├── led-par-64-tri-b.json
│   │   ├── megastrobe-fx12.json
│   │   ├── motiondrape-led.json
│   │   ├── slimpar-pro-h-usb.json
│   │   ├── slimpar-pro-qz12.json
│   │   ├── slimpar-pro-rgba.json
│   │   ├── slimpar-pro-w.json
│   │   ├── slimpar-q12-bt.json
│   │   ├── slimpar-t12-bt.json
│   │   ├── slimpar-t12-usb.json
│   │   └── washfx.json
│   ├── chauvet-professional/
│   │   ├── colorado-1-solo.json
│   │   ├── colordash-batten-quad-6.json
│   │   ├── colordash-s-par-1.json
│   │   ├── ovation-f-915vw.json
│   │   ├── rogue-r1-wash.json
│   │   ├── rogue-r2-wash.json
│   │   └── vesuvio-rgba.json
│   ├── chroma-q/
│   │   ├── color-force-ii-12.json
│   │   ├── color-force-ii-48.json
│   │   └── color-force-ii-72.json
│   ├── cinetec/
│   │   └── par-18x15w-rgbwa.json
│   ├── clay-paky/
│   │   ├── a-leda-b-eye-k10.json
│   │   ├── a-leda-b-eye-k20.json
│   │   ├── alpha-spot-qwo-800.json
│   │   ├── sharpy.json
│   │   ├── show-batten-100.json
│   │   └── spheriscan.json
│   ├── clf/
│   │   └── hera.json
│   ├── coemar/
│   │   └── prospot-250-lx.json
│   ├── contest/
│   │   ├── irled64-18x12six.json
│   │   └── irledflat-5x12SIXb.json
│   ├── dedolight/
│   │   ├── dled4-bi.json
│   │   └── dled7-bi.json
│   ├── desisti/
│   │   ├── softled-4-vw.json
│   │   └── softled-8-vw.json
│   ├── dmg-lumiere/
│   │   ├── maxi-mix.json
│   │   ├── mini-mix.json
│   │   └── sl1-mix.json
│   ├── dts/
│   │   ├── scena-led-150.json
│   │   ├── xr1200-wash.json
│   │   └── xr4-spot.json
│   ├── elation/
│   │   ├── acl-360-roller.json
│   │   ├── cuepix-blinder-ww2.json
│   │   ├── cuepix-blinder-ww4.json
│   │   ├── design-led-par-zoom.json
│   │   ├── fuze-par-z60ip.json
│   │   ├── platinum-hfx.json
│   │   ├── platinum-seven.json
│   │   ├── platinum-spot-15r-pro.json
│   │   ├── proteus-hybrid.json
│   │   ├── sixpar-100-ip.json
│   │   ├── sixpar-100.json
│   │   ├── sixpar-200-ip.json
│   │   ├── sixpar-200-wmg.json
│   │   ├── sixpar-200.json
│   │   ├── sixpar-300-ip.json
│   │   ├── sixpar-300-wmg.json
│   │   ├── sixpar-300.json
│   │   ├── uni-bar.json
│   │   └── zw19.json
│   ├── eliminator/
│   │   ├── stealth-beam.json
│   │   └── stealth-wash-zoom.json
│   ├── empire-lighting/
│   │   └── 8x-3w-led-spider-effect.json
│   ├── epsilon/
│   │   └── duo-q-beam-bar.json
│   ├── equinox/
│   │   ├── gigabar.json
│   │   └── rgb-power-batten.json
│   ├── etc/
│   │   ├── colorsource-par-deep-blue.json
│   │   ├── colorsource-par.json
│   │   ├── colorsource-spot-deep-blue.json
│   │   ├── colorsource-spot.json
│   │   ├── fos4PD16.json
│   │   ├── fos4PD24.json
│   │   ├── fos4PD8.json
│   │   ├── fos4PL16.json
│   │   ├── fos4PL24.json
│   │   ├── fos4PL8.json
│   │   ├── source-4wrd-color-ii.json
│   │   ├── source-four-led-series-2-daylight-hd.json
│   │   ├── source-four-led-series-2-lustr.json
│   │   ├── source-four-led-series-2-tungsten-hd.json
│   │   ├── source-four-led-series-3-daylight-hdr.json
│   │   └── source-four-led-series-3-lustr-x8.json
│   ├── eurolite/
│   │   ├── edx-4.json
│   │   ├── led-7c-7-silent-slim.json
│   │   ├── led-b-40.json
│   │   ├── led-bar-12-qcl-rgba-bar.json
│   │   ├── led-bar-3-hcl-bar.json
│   │   ├── led-bar-6-qcl-rgbw.json
│   │   ├── led-big-party-spot.json
│   │   ├── led-big-party-tcl-spot.json
│   │   ├── led-dmx-pixel-tube-16-rgb-ip20.json
│   │   ├── led-fe-1500.json
│   │   ├── led-h2o.json
│   │   ├── led-kls-801.json
│   │   ├── led-ml-56-rgbw.json
│   │   ├── led-par-56-tcl.json
│   │   ├── led-party-spot.json
│   │   ├── led-party-tcl-spot.json
│   │   ├── led-pix-12-hcl.json
│   │   ├── led-pix-144.json
│   │   ├── led-ps-4-hcl.json
│   │   ├── led-sls-12-bcl.json
│   │   ├── led-sls-5-bcl.json
│   │   ├── led-sls-6-uv-floor.json
│   │   ├── led-svf-1.json
│   │   ├── led-tha-100f-mk2.json
│   │   ├── led-tha-100f.json
│   │   ├── led-theatre-cob-200-rgb-ww.json
│   │   ├── led-tl-3-rgb-uv.json
│   │   ├── led-tl-4-qcl.json
│   │   ├── led-tmh-14.json
│   │   ├── led-tmh-17.json
│   │   ├── led-tmh-18.json
│   │   ├── led-tmh-7.json
│   │   ├── led-tmh-8.json
│   │   ├── led-tmh-9.json
│   │   ├── led-tmh-x12.json
│   │   ├── led-tmh-x25.json
│   │   ├── led-z-200-tcl.json
│   │   ├── md-2030.json
│   │   ├── multiflood-pro-ip-smd-rgbw.json
│   │   ├── n-150.json
│   │   ├── tmh-xb-130.json
│   │   └── ts-2.json
│   ├── event-lighting/
│   │   ├── par12x12.json
│   │   └── par5x12.json
│   ├── evolight/
│   │   └── colours-archspot-54-rgb.json
│   ├── explo/
│   │   ├── gasprojector-gx2.json
│   │   └── x2-wave-flamer.json
│   ├── eyourlife/
│   │   └── led-rgbw-54x3-par64.json
│   ├── fiilex/
│   │   └── p3-color.json
│   ├── flash-professional/
│   │   ├── led-moving-head-150w.json
│   │   ├── led-par-64-cob-300w-rgbwauv.json
│   │   └── led-par-64-slim-7x10w-rgbw-mk2.json
│   ├── fovitec/
│   │   └── 600xb.json
│   ├── fractal-lights/
│   │   ├── par-led-7x10w.json
│   │   ├── par-led-7x12w.json
│   │   └── par-led-7x9w.json
│   ├── fun-generation/
│   │   ├── led-pot-12-1w-rgbw.json
│   │   ├── led-pot-12x1w-qcl-rgb-ww-15.json
│   │   ├── led-pot-12x1w-qcl-rgb-ww-40.json
│   │   ├── picobeam-30-quad-led.json
│   │   ├── picobeam-60-cob-rgbw.json
│   │   ├── picoblade-fx-4x10w-rgbw.json
│   │   ├── picospot-20-led.json
│   │   ├── picospot-45-led.json
│   │   ├── picowash-40-pixel-quad-led.json
│   │   ├── separ-hex-led-rgbaw-uv.json
│   │   ├── separ-quad-led-rgb-uv.json
│   │   └── separ-quad-led-rgbw.json
│   ├── futurelight/
│   │   ├── dj-scan-250.json
│   │   ├── dmh-75-i-led-moving-head.json
│   │   ├── pro-slim-par-7-hcl.json
│   │   ├── sc-250-scanner.json
│   │   └── stb-648-led-strobe-smd-5050.json
│   ├── galaxis/
│   │   └── g-flame.json
│   ├── gantom/
│   │   └── precision-dmx.json
│   ├── generic/
│   │   ├── 4-channel-dimmer-pack.json
│   │   ├── cmy-fader.json
│   │   ├── color-temperature-fader.json
│   │   ├── cw-ww-fader.json
│   │   ├── desk-channel.json
│   │   ├── drgb-fader.json
│   │   ├── drgbw-fader.json
│   │   ├── grbw-fader.json
│   │   ├── pan-tilt.json
│   │   ├── rgb-fader.json
│   │   ├── rgba-fader.json
│   │   ├── rgbd-fader.json
│   │   ├── rgbw-fader.json
│   │   ├── rgbwauv-fader.json
│   │   ├── rgbww-fader.json
│   │   └── strobe.json
│   ├── ghost/
│   │   ├── ip-spot-bat.json
│   │   └── ip-spot-pro.json
│   ├── glp/
│   │   ├── force-120.json
│   │   ├── impression-fr1.json
│   │   ├── impression-laser.json
│   │   ├── impression-spot-one.json
│   │   ├── impression-x4-bar-10.json
│   │   ├── jdc1.json
│   │   ├── knv-arc.json
│   │   └── knv-cube.json
│   ├── glx/
│   │   └── gls-4-led-stage-4.json
│   ├── griven/
│   │   └── kolorado-4000.json
│   ├── gruft/
│   │   ├── pixel-tube.json
│   │   └── ventilator.json
│   ├── hazebase/
│   │   └── base-hazer-pro.json
│   ├── hive/
│   │   ├── bee-50-c.json
│   │   ├── bumble-bee-25-cx.json
│   │   ├── hornet-200-c.json
│   │   ├── hornet-200-cx.json
│   │   ├── super-hornet-575-c.json
│   │   ├── wasp-100-c.json
│   │   └── wasp-100-cx.json
│   ├── hong-yi/
│   │   └── hy-g60.json
│   ├── hsl/
│   │   └── 40w-beam-spot-light-rgbw.json
│   ├── ibiza-light/
│   │   ├── lp64-led-promo.json
│   │   ├── ls-005led.json
│   │   └── par-mini-rgb3.json
│   ├── ignition/
│   │   ├── 2bright-par-18-ip.json
│   │   ├── led-accu-par.json
│   │   ├── strip-blinder-x.json
│   │   ├── teatro-led-spot-100-fr.json
│   │   └── teatro-led-spot-100-pc.json
│   ├── ikan/
│   │   └── stryder-sfb150.json
│   ├── infinity/
│   │   ├── iw-340-rdm.json
│   │   └── iw-720-rdm.json
│   ├── jb-lighting/
│   │   ├── jbled-a7.json
│   │   └── varyscan-p7.json
│   ├── jb-systems/
│   │   ├── imove-5s.json
│   │   ├── irock-5c.json
│   │   └── twin-effect-laser.json
│   ├── kam/
│   │   └── gobotracer.json
│   ├── kino-flo/
│   │   ├── celeb-250-led-dmx.json
│   │   ├── celeb-450-led-dmx.json
│   │   ├── celeb-led-201-dmx.json
│   │   ├── celeb-led-250-dmx.json
│   │   └── celeb-led-450-dmx.json
│   ├── lalucenatz/
│   │   ├── 18leds-par-light.json
│   │   └── dj-lights.json
│   ├── laserworld/
│   │   ├── cs-1000rgb.json
│   │   ├── ds-1000rgb.json
│   │   ├── el-400rgb-mk2.json
│   │   └── shownet.json
│   ├── ledj/
│   │   ├── slimline-12q5-rgba.json
│   │   └── slimline-12q5-rgbw.json
│   ├── lep-laser/
│   │   └── diamond-pro-2-8.json
│   ├── light-sky/
│   │   └── aurora.json
│   ├── light4me/
│   │   └── led-par-18x3w-uv.json
│   ├── lightmaxx/
│   │   ├── cls-nano-cob.json
│   │   ├── dj-scan-led.json
│   │   ├── easy-wash-quad-led.json
│   │   ├── led-blinder-4.json
│   │   ├── led-nano-par.json
│   │   ├── led-par-56.json
│   │   ├── led-par-64-cob-100w-rgb.json
│   │   ├── platinum-mini-tri-par.json
│   │   ├── vector-haze-1-0.json
│   │   ├── vector-pixel-bar-18x-15w-rgbwa.json
│   │   ├── vega-bat-1.json
│   │   └── vega-zoom-wash.json
│   ├── lite-tek/
│   │   └── beam-230.json
│   ├── litecraft/
│   │   ├── washx-21.json
│   │   └── washx-432-sw.json
│   ├── litegear/
│   │   ├── litemat-plus-1.json
│   │   ├── litemat-plus-2.json
│   │   ├── litemat-plus-2l.json
│   │   ├── litemat-plus-3.json
│   │   ├── litemat-plus-4.json
│   │   ├── litemat-plus-8.json
│   │   ├── litetile-plus-4.json
│   │   ├── litetile-plus-8.json
│   │   ├── s2-litemat-1.json
│   │   ├── s2-litemat-2.json
│   │   ├── s2-litemat-2l.json
│   │   ├── s2-litemat-3.json
│   │   └── s2-litemat-4.json
│   ├── lixada/
│   │   ├── mini-beam-rgbw.json
│   │   ├── mini-gobo-moving-head-light.json
│   │   └── mini-moving-head-rgbw.json
│   ├── look/
│   │   ├── cryofog.json
│   │   └── viper-nt.json
│   ├── lupo/
│   │   ├── actionpanel-dual-color.json
│   │   ├── actionpanel-full-color.json
│   │   ├── superpanel-dual-color-60.json
│   │   ├── superpanel-full-color-60.json
│   │   ├── superpanelpro-dual-color-30.json
│   │   ├── superpanelpro-full-color-30.json
│   │   ├── ultrapanel-dual-color-60.json
│   │   ├── ultrapanel-full-color-60.json
│   │   ├── ultrapanelpro-dual-color-30.json
│   │   └── ultrapanelpro-full-color-30.json
│   ├── magicfx/
│   │   ├── psyco2jet.json
│   │   ├── smokejet.json
│   │   └── stage-flame.json
│   ├── manufacturers.json
│   ├── mark/
│   │   ├── mbar-381-ip.json
│   │   └── superbat-led-72.json
│   ├── martin/
│   │   ├── atomic-3000.json
│   │   ├── mac-250-beam.json
│   │   ├── mac-250-krypton.json
│   │   ├── mac-250-wash.json
│   │   ├── mac-600.json
│   │   ├── mac-700-wash.json
│   │   ├── mac-aura.json
│   │   ├── mac-axiom-hybrid.json
│   │   ├── mac-encore-performance.json
│   │   ├── mac-viper-airfx.json
│   │   ├── mac-viper-performance.json
│   │   ├── mac-viper-wash.json
│   │   ├── magnum-2500-hz.json
│   │   ├── mania-scx500.json
│   │   ├── mx-10-extreme.json
│   │   ├── roboscan-812.json
│   │   ├── rush-mh-2-wash.json
│   │   ├── rush-mh-3-beam.json
│   │   ├── rush-mh-5-profile.json
│   │   ├── rush-mh-7-hybrid.json
│   │   ├── rush-par-2-rgbw-zoom.json
│   │   ├── rush-scanner-1-led.json
│   │   ├── stagebar-54l.json
│   │   └── stagebar-54s.json
│   ├── mdg/
│   │   ├── atme.json
│   │   ├── hazer-atmosphere-aps.json
│   │   └── theone-atmospheric-generator.json
│   ├── mega-led-lighting/
│   │   ├── led-par-light-372.json
│   │   └── zoom-360.json
│   ├── mega-lite/
│   │   ├── framebot-600.json
│   │   ├── mw1.json
│   │   ├── spotbot-led-cmy-300.json
│   │   └── washbot-led-cymk-300.json
│   ├── minuit-une/
│   │   ├── ivl-carre.json
│   │   └── m-carre.json
│   ├── nicols/
│   │   ├── led-bar-123-fc-ip.json
│   │   └── pat-252.json
│   ├── orion/
│   │   └── orcan2.json
│   ├── panasonic/
│   │   ├── pt-rz120.json
│   │   └── pt-rz120l.json
│   ├── phocea-light/
│   │   └── box-leds-batterie-6x15w.json
│   ├── powerlighting/
│   │   └── wash-84w.json
│   ├── pr-lighting/
│   │   └── xs-250-spot.json
│   ├── prolights/
│   │   ├── diamond19.json
│   │   ├── pixpan16.json
│   │   ├── polar3000.json
│   │   ├── smartbat.json
│   │   └── v700spot.json
│   ├── qtx/
│   │   ├── lux-ld01.json
│   │   └── lux-ld30w.json
│   ├── renkforce/
│   │   └── gm107.json
│   ├── robe/
│   │   ├── colorspot-2500e-at.json
│   │   ├── dj-scan-250-xt.json
│   │   ├── robin-300e-wash.json
│   │   ├── robin-600e-spot.json
│   │   ├── robin-ledbeam-100.json
│   │   ├── robin-ledbeam-150.json
│   │   ├── robin-ledwash-600.json
│   │   ├── robin-parfect-150.json
│   │   ├── robin-t1-profile.json
│   │   ├── robin-viva-cmy.json
│   │   └── spot-160-xt.json
│   ├── robert-juliat/
│   │   └── 613sx.json
│   ├── rockville/
│   │   └── rockpar50.json
│   ├── sgm/
│   │   └── p-5.json
│   ├── shehds/
│   │   ├── led-flat-par-12x3w-rgbw.json
│   │   ├── led-flat-par-18x18w.json
│   │   ├── led-flat-par-54x3w.json
│   │   ├── led-flat-par-7x18w-rgbwa-uv-light.json
│   │   ├── led-par-18x18w.json
│   │   └── led-spot-60w.json
│   ├── showline/
│   │   └── sl-nitro-510c.json
│   ├── showlite/
│   │   └── lb-4390.json
│   ├── showpro/
│   │   └── litebar-h9.json
│   ├── showtec/
│   │   ├── accent-spot-q4-rgbw.json
│   │   ├── archi-painter-24-8-q4.json
│   │   ├── atmos-2000.json
│   │   ├── club-par-12-4-rgbw.json
│   │   ├── club-par-12-6-rgbwauv.json
│   │   ├── compact-par-18.json
│   │   ├── compact-par-7-tri.json
│   │   ├── dim-4lc.json
│   │   ├── dominator.json
│   │   ├── explorer-250-wash-pro.json
│   │   ├── horizon-8.json
│   │   ├── kanjo-spot-60.json
│   │   ├── kanjo-wash-rgb.json
│   │   ├── led-blinder-2-cob.json
│   │   ├── led-light-bar-rgb-v3.json
│   │   ├── performer-2500.json
│   │   ├── phantom-140-led-spot.json
│   │   ├── phantom-25-led-wash.json
│   │   ├── phantom-3r-beam.json
│   │   ├── phantom-50-led-spot.json
│   │   ├── phantom-matrix-fx.json
│   │   ├── pixel-bar-12-mkii.json
│   │   ├── shark-the-meg-hybrid-one.json
│   │   ├── sunraise-led.json
│   │   ├── sunstrip-active-mkii.json
│   │   └── xs-1-rgbw.json
│   ├── showven/
│   │   ├── sparkular-fall.json
│   │   └── sparkular.json
│   ├── silver-star/
│   │   └── mx-indigo-6000xe.json
│   ├── skypix/
│   │   └── ribalta-beam.json
│   ├── smoke-factory/
│   │   ├── data-ii.json
│   │   └── tour-hazer-ii.json
│   ├── solaris/
│   │   └── smart-36.json
│   ├── solena/
│   │   ├── max-par-20.json
│   │   └── mini-par-12.json
│   ├── soundlight/
│   │   └── 3204r-h.json
│   ├── stage-right/
│   │   ├── mini-beam-rgbw.json
│   │   └── stage-wash-7x10w-led-moving-head.json
│   ├── stairville/
│   │   ├── af-180-led-fogger.json
│   │   ├── af-250.json
│   │   ├── afh-600.json
│   │   ├── bel6-ip-bar-hex.json
│   │   ├── clb5-2p-rgb-ww-compact-led-par.json
│   │   ├── cx60-hex.json
│   │   ├── hz-200-compact-hazer.json
│   │   ├── led-bar-240-8.json
│   │   ├── led-flood-panel-150.json
│   │   ├── led-par-56.json
│   │   ├── led-par-64.json
│   │   ├── matrixx-sc-100.json
│   │   ├── mh-100.json
│   │   ├── mh-x20.json
│   │   ├── mh-x25.json
│   │   ├── mh-x30-led-spot.json
│   │   ├── mh-x30.json
│   │   ├── mh-x50.json
│   │   ├── mh-x60.json
│   │   ├── octagon-theater-20x6w-cw-ww-a.json
│   │   ├── par-56.json
│   │   ├── remus-hexspot-515.json
│   │   ├── revueled-120-cob-rgbww.json
│   │   ├── revueled-120-cob-true-white.json
│   │   ├── sonicpulse-led-bar-05.json
│   │   ├── sonicpulse-led-bar-10.json
│   │   ├── stage-tri-led.json
│   │   ├── vf-1200-dmx-vertifog-co2-fx.json
│   │   ├── wild-wash-132-led-rgb-dmx.json
│   │   ├── wild-wash-648-led-white-dmx.json
│   │   ├── xbrick-full-colour.json
│   │   ├── xbrick-quad-16x8w-rgbw.json
│   │   └── z120m-par-64-led-rgbw-120w.json
│   ├── starway/
│   │   ├── servo-color-4k.json
│   │   └── stickolor-1210uhd.json
│   ├── studio-due/
│   │   └── light-deflector.json
│   ├── sun-star/
│   │   └── g-2011-nova.json
│   ├── tecshow/
│   │   ├── nebula-18.json
│   │   └── nebula-6.json
│   ├── tiptop-stage-light/
│   │   └── 3-10w-battery-led-wedge-par.json
│   ├── tmb/
│   │   └── solaris-flare.json
│   ├── tomshine/
│   │   ├── 3-led-par-light-rgbuv.json
│   │   └── 80w-mini-gobo-moving-head.json
│   ├── uking/
│   │   ├── b117-par-can-4in1-rgbw-18-leds.json
│   │   ├── mini-led-spot-25w.json
│   │   ├── par-light-b262.json
│   │   └── zq-b20-mini-spider-light.json
│   ├── ultratec/
│   │   └── radiance-hazer.json
│   ├── varytec/
│   │   ├── bat-par-6-rgbuv.json
│   │   ├── bat-par-6-rgbwa.json
│   │   ├── easy-move-xs-hp-wash-7x8w-rgbw.json
│   │   ├── giga-bar-frost-pix-8-rgb.json
│   │   ├── giga-bar-hex-3.json
│   │   ├── hero-spot-230.json
│   │   ├── hero-wash-340fx-rgbw-zoom.json
│   │   ├── hero-wash-640fx.json
│   │   ├── led-hellball-3-rgb.json
│   │   └── led-theater-spot-100.json
│   ├── velleman/
│   │   └── aeron-250-ii.json
│   ├── venue/
│   │   ├── thintri64.json
│   │   └── tristrip3z.json
│   └── vrsl/
│       ├── disco-ball.json
│       └── flasher.json
├── jsconfig.json
├── lib/
│   ├── ajv-validator.js
│   ├── cache-result.js
│   ├── create-github-issue.js
│   ├── create-github-pr.js
│   ├── diff-plugin-outputs.js
│   ├── esm-shim.cjs
│   ├── fixture-features/
│   │   ├── 16bit-dmx-value-resolution.js
│   │   ├── capability-types.js
│   │   ├── duplicate-channel-names.js
│   │   ├── fine-channels.js
│   │   ├── fine-positions.js
│   │   ├── floating-point-physicals.js
│   │   ├── many-modes.js
│   │   ├── matrices.js
│   │   ├── multiple-categories.js
│   │   ├── multiple-focuses.js
│   │   ├── no-physical-data.js
│   │   ├── null-channels.js
│   │   ├── physical-override.js
│   │   ├── rdm.js
│   │   ├── redirect-reasons.js
│   │   ├── reused-channels.js
│   │   ├── switching-channels.js
│   │   └── wheels.js
│   ├── fixture-json-stringify.js
│   ├── get-ajv-error-messages.js
│   ├── import-json.js
│   ├── load-env-file.js
│   ├── model/
│   │   ├── AbstractChannel.js
│   │   ├── Capability.js
│   │   ├── CoarseChannel.js
│   │   ├── Entity.js
│   │   ├── FineChannel.js
│   │   ├── Fixture.js
│   │   ├── Manufacturer.js
│   │   ├── Matrix.js
│   │   ├── Meta.js
│   │   ├── Mode.js
│   │   ├── NullChannel.js
│   │   ├── Physical.js
│   │   ├── Range.js
│   │   ├── Resource.js
│   │   ├── SwitchingChannel.js
│   │   ├── TemplateChannel.js
│   │   ├── Wheel.js
│   │   └── WheelSlot.js
│   ├── model.js
│   ├── register.js
│   ├── scale-dmx-values.js
│   ├── schema-properties.js
│   ├── server-response-helpers.js
│   ├── site-crawler.js
│   └── types.js
├── nuxt.config.js
├── package.json
├── plugins/
│   ├── aglight/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── color-chief/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── colorsource/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── d-light/
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── attributes-correctness.js
│   │   └── plugin.json
│   ├── dmxcontrol3/
│   │   ├── ddf3-function-groups.js
│   │   ├── ddf3-functions.js
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── channel-numbers.js
│   │   └── plugin.json
│   ├── dragonframe/
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── json-schema-conformity.js
│   │   └── plugin.json
│   ├── ecue/
│   │   ├── export.js
│   │   ├── import.js
│   │   └── plugin.json
│   ├── gdtf/
│   │   ├── deprecated-gdtf-attributes.js
│   │   ├── gdtf-attributes.js
│   │   ├── gdtf-helpers.js
│   │   ├── import.js
│   │   └── plugin.json
│   ├── millumin/
│   │   ├── export.js
│   │   ├── exportTests/
│   │   │   └── json-schema-conformity.js
│   │   └── plugin.json
│   ├── ofl/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── op-z/
│   │   ├── export.js
│   │   └── plugin.json
│   ├── plugins.json
│   └── qlcplus_4.12.2/
│       ├── export.js
│       ├── exportTests/
│       │   ├── fixture-tool-validation.js
│       │   └── xsd-schema-conformity.js
│       ├── import.js
│       ├── plugin.json
│       └── presets.js
├── resources/
│   └── gobos/
│       ├── 1-vertical-bar.json
│       ├── 10-circles.json
│       ├── 3-fold-spiral.json
│       ├── 3-fold-swirl.json
│       ├── 32-circles.json
│       ├── 4-line-arranged-dots.json
│       ├── 4-vertical-bars.json
│       ├── 5-fold-swirl-inverted.json
│       ├── 5-fold-swirl.json
│       ├── 5-pointed-compass-rose-star.json
│       ├── 5-pointed-star.json
│       ├── 6-mouse-heads.json
│       ├── aliases/
│       │   └── qlcplus.json
│       ├── biohazard.json
│       ├── bubbles.json
│       ├── caro-flower.json
│       ├── circle-outline.json
│       ├── circled-clock-face.json
│       ├── circular-beams.json
│       ├── circular-droplets.json
│       ├── circular-drops.json
│       ├── clockwork.json
│       ├── comets.json
│       ├── crescent.json
│       ├── daisy-flower.json
│       ├── dot-spiral.json
│       ├── double-lines-star.json
│       ├── edge-touching-circles.json
│       ├── fragmented-star.json
│       ├── glass-raindrops-on-window.json
│       ├── glass-red-10-circles.json
│       ├── hypnotic-swirl.json
│       ├── pointed-grid.json
│       ├── quarter-mark.json
│       ├── rays.json
│       ├── rose-petal-abstract.json
│       ├── rose-petal.json
│       ├── shards.json
│       ├── square-arrows.json
│       ├── square-outline.json
│       ├── stars.json
│       ├── stones.json
│       ├── striped-iris.json
│       ├── tiny-stars.json
│       ├── triangle-hexagon-pattern.json
│       ├── triangle-outline.json
│       └── vibrating-triangle.json
├── schemas/
│   ├── capability.json
│   ├── channel.json
│   ├── definitions.json
│   ├── fixture-redirect.json
│   ├── fixture.json
│   ├── gobo.json
│   ├── manufacturers.json
│   ├── matrix.json
│   ├── plugin.json
│   └── wheel-slot.json
├── server/
│   ├── ecosystem.config.js
│   ├── nginx-site-available
│   ├── redeploy.sh
│   └── webhook.js
├── tests/
│   ├── built-files-committed.js
│   ├── dmx-value-scaling.test.js
│   ├── external-links.js
│   ├── fixture-valid.js
│   ├── fixtures-valid.js
│   ├── github/
│   │   ├── export-diff.js
│   │   ├── exports-valid.js
│   │   ├── pull-request.js
│   │   └── schema-version-reminder.js
│   ├── http-status.test.js
│   ├── test-fixtures.json
│   └── test-fixtures.md
└── ui/
    ├── api/
    │   ├── download.js
    │   ├── index.js
    │   ├── openapi.json
    │   ├── routes/
    │   │   ├── fixtures/
    │   │   │   ├── from-editor.js
    │   │   │   ├── from-editor.json
    │   │   │   ├── import.js
    │   │   │   ├── import.json
    │   │   │   ├── submit.js
    │   │   │   └── submit.json
    │   │   ├── get-search-results.js
    │   │   ├── get-search-results.json
    │   │   ├── manufacturers/
    │   │   │   ├── _manufacturerKey.js
    │   │   │   ├── _manufacturerKey.json
    │   │   │   ├── index.js
    │   │   │   └── index.json
    │   │   ├── plugins/
    │   │   │   ├── _pluginKey.js
    │   │   │   ├── _pluginKey.json
    │   │   │   ├── index.js
    │   │   │   └── index.json
    │   │   ├── submit-feedback.js
    │   │   └── submit-feedback.json
    │   └── routes.js
    ├── assets/
    │   ├── icons/
    │   │   └── icons.js
    │   ├── scripts/
    │   │   ├── editor-utilities.js
    │   │   └── fixture-link-types.js
    │   └── styles/
    │       ├── fonts.scss
    │       ├── global.scss
    │       ├── mixins.scss
    │       ├── style.scss
    │       ├── theming.scss
    │       └── vars.scss
    ├── components/
    │   ├── A11yDialog.vue
    │   ├── CapabilityTypeIcon.vue
    │   ├── CategoryBadge.vue
    │   ├── ChannelTypeIcon.vue
    │   ├── ClimateStrikeBanner.vue
    │   ├── ConditionalDetails.vue
    │   ├── DownloadButton.vue
    │   ├── FixtureHeader.vue
    │   ├── HeaderBar.vue
    │   ├── HelpWantedDialog.vue
    │   ├── HelpWantedMessage.vue
    │   ├── LabeledInput.vue
    │   ├── LabeledValue.vue
    │   ├── PropertyInputBoolean.vue
    │   ├── PropertyInputDimensions.vue
    │   ├── PropertyInputEntity.vue
    │   ├── PropertyInputNumber.vue
    │   ├── PropertyInputRange.vue
    │   ├── PropertyInputSelect.vue
    │   ├── PropertyInputText.vue
    │   ├── PropertyInputTextarea.vue
    │   ├── ThemeSwitcher.vue
    │   ├── editor/
    │   │   ├── EditorCapability.vue
    │   │   ├── EditorCapabilityTypeData.vue
    │   │   ├── EditorCapabilityWizard.vue
    │   │   ├── EditorCategoryChooser.vue
    │   │   ├── EditorChannelDialog.vue
    │   │   ├── EditorChooseChannelEditModeDialog.vue
    │   │   ├── EditorFileUpload.vue
    │   │   ├── EditorFixtureInformation.vue
    │   │   ├── EditorLink.vue
    │   │   ├── EditorLinks.vue
    │   │   ├── EditorManufacturer.vue
    │   │   ├── EditorMode.vue
    │   │   ├── EditorPhysical.vue
    │   │   ├── EditorProportionalPropertySwitcher.vue
    │   │   ├── EditorRestoreDialog.vue
    │   │   ├── EditorSubmitDialog.vue
    │   │   ├── EditorWheelSlot.vue
    │   │   ├── EditorWheelSlots.vue
    │   │   ├── capabilities/
    │   │   │   ├── CapabilityBeamAngle.vue
    │   │   │   ├── CapabilityBeamPosition.vue
    │   │   │   ├── CapabilityBladeInsertion.vue
    │   │   │   ├── CapabilityBladeRotation.vue
    │   │   │   ├── CapabilityBladeSystemRotation.vue
    │   │   │   ├── CapabilityColorIntensity.vue
    │   │   │   ├── CapabilityColorPreset.vue
    │   │   │   ├── CapabilityColorTemperature.vue
    │   │   │   ├── CapabilityEffect.vue
    │   │   │   ├── CapabilityEffectDuration.vue
    │   │   │   ├── CapabilityEffectParameter.vue
    │   │   │   ├── CapabilityEffectSpeed.vue
    │   │   │   ├── CapabilityFocus.vue
    │   │   │   ├── CapabilityFog.vue
    │   │   │   ├── CapabilityFogOutput.vue
    │   │   │   ├── CapabilityFogType.vue
    │   │   │   ├── CapabilityFrost.vue
    │   │   │   ├── CapabilityFrostEffect.vue
    │   │   │   ├── CapabilityGeneric.vue
    │   │   │   ├── CapabilityIntensity.vue
    │   │   │   ├── CapabilityIris.vue
    │   │   │   ├── CapabilityIrisEffect.vue
    │   │   │   ├── CapabilityMaintenance.vue
    │   │   │   ├── CapabilityNoFunction.vue
    │   │   │   ├── CapabilityPan.vue
    │   │   │   ├── CapabilityPanContinuous.vue
    │   │   │   ├── CapabilityPanTiltSpeed.vue
    │   │   │   ├── CapabilityPrism.vue
    │   │   │   ├── CapabilityPrismRotation.vue
    │   │   │   ├── CapabilityRotation.vue
    │   │   │   ├── CapabilityShutterStrobe.vue
    │   │   │   ├── CapabilitySoundSensitivity.vue
    │   │   │   ├── CapabilitySpeed.vue
    │   │   │   ├── CapabilityStrobeDuration.vue
    │   │   │   ├── CapabilityStrobeSpeed.vue
    │   │   │   ├── CapabilityTilt.vue
    │   │   │   ├── CapabilityTiltContinuous.vue
    │   │   │   ├── CapabilityTime.vue
    │   │   │   ├── CapabilityWheelRotation.vue
    │   │   │   ├── CapabilityWheelShake.vue
    │   │   │   ├── CapabilityWheelSlot.vue
    │   │   │   ├── CapabilityWheelSlotRotation.vue
    │   │   │   └── CapabilityZoom.vue
    │   │   └── wheel-slots/
    │   │       ├── WheelSlotAnimationGoboEnd.vue
    │   │       ├── WheelSlotAnimationGoboStart.vue
    │   │       ├── WheelSlotClosed.vue
    │   │       ├── WheelSlotColor.vue
    │   │       ├── WheelSlotFrost.vue
    │   │       ├── WheelSlotGobo.vue
    │   │       ├── WheelSlotIris.vue
    │   │       ├── WheelSlotOpen.vue
    │   │       └── WheelSlotPrism.vue
    │   ├── fixture-page/
    │   │   ├── FixturePage.vue
    │   │   ├── FixturePageCapabilityTable.vue
    │   │   ├── FixturePageChannel.vue
    │   │   ├── FixturePageMatrix.vue
    │   │   ├── FixturePageMode.vue
    │   │   ├── FixturePagePhysical.vue
    │   │   └── FixturePageWheel.vue
    │   └── global/
    │       ├── OflSvg.vue
    │       └── OflTime.vue
    ├── layouts/
    │   ├── default.vue
    │   └── error.vue
    ├── pages/
    │   ├── _manufacturerKey/
    │   │   ├── _fixtureKey.vue
    │   │   └── index.vue
    │   ├── about/
    │   │   ├── index.vue
    │   │   └── plugins/
    │   │       ├── _plugin.vue
    │   │       └── index.vue
    │   ├── categories/
    │   │   ├── _category.vue
    │   │   └── index.vue
    │   ├── fixture-editor.vue
    │   ├── import-fixture-file.vue
    │   ├── index.vue
    │   ├── manufacturers.vue
    │   ├── rdm.vue
    │   └── search.vue
    ├── plugins/
    │   ├── global-components.js
    │   └── vue-form.js
    └── static/
        ├── BingSiteAuth.xml
        ├── browserconfig.xml
        ├── fonts/
        │   ├── Inconsolata/
        │   │   └── OFL.txt
        │   └── LatoLatin/
        │       └── OFL.txt
        ├── google02fa8e96cb305d78.html
        └── manifest.json
Download .txt
SYMBOL INDEX (720 symbols across 86 files)

FILE: cli/build-plugin-data.js
  function readPluginJson (line 61) | async function readPluginJson(pluginKey) {
  function readPluginImport (line 86) | async function readPluginImport(pluginKey) {
  function readPluginExport (line 107) | async function readPluginExport(pluginKey) {
  function readPluginExportTests (line 129) | async function readPluginExportTests(pluginKey) {

FILE: cli/build-register.js
  function addFixturesToRegister (line 42) | async function addFixturesToRegister() {

FILE: cli/build-test-fixtures.js
  function getFixtureFeatures (line 114) | async function getFixtureFeatures() {
  function getMarkdownCode (line 155) | async function getMarkdownCode(fixtures, fixtureFeatures) {

FILE: cli/debug-env-variables.js
  function printVariables (line 35) | function printVariables() {

FILE: cli/export-fixture.js
  function checkCliArguments (line 70) | async function checkCliArguments(cliArguments) {

FILE: cli/import-fixture.js
  function checkCliArguments (line 71) | async function checkCliArguments() {

FILE: lib/ajv-validator.js
  function getAjvValidator (line 37) | async function getAjvValidator(schemaName) {

FILE: lib/cache-result.js
  function cacheResult (line 20) | function cacheResult(classInstance, propertyName, value) {

FILE: lib/create-github-issue.js
  function createIssue (line 11) | async function createIssue(title, body, labels = []) {

FILE: lib/create-github-pr.js
  function createPullRequest (line 28) | async function createPullRequest(fixtureCreateResult, githubUsername = n...
  function getGithubUserInfo (line 147) | async function getGithubUserInfo(username) {
  function getPrDescriptionMarkdown (line 171) | function getPrDescriptionMarkdown(submitterNameMarkdown, fixtureWarnings...
  function getFixtureWarningsErrorsMarkdownList (line 200) | function getFixtureWarningsErrorsMarkdownList(fixtures, errors, warnings) {
  function getSubmitterNameMarkdown (line 226) | function getSubmitterNameMarkdown(user, githubUsername, fixtures) {
  function addOrUpdateFile (line 256) | async function addOrUpdateFile(filename, displayName, newContentFunction...
  function encodeBase64 (line 323) | function encodeBase64(string) {
  function prettyJsonStringify (line 332) | function prettyJsonStringify(object) {

FILE: lib/diff-plugin-outputs.js
  function diffPluginOutputs (line 26) | async function diffPluginOutputs(currentPluginKey, comparePluginKey, ref...
  function findDifferences (line 169) | async function findDifferences(currentOut, compareOut) {
  function getRelativePath (line 227) | function getRelativePath(relativePath, name, type) {

FILE: lib/fixture-features/floating-point-physicals.js
  function isFloatInPhysical (line 53) | function isFloatInPhysical(physical, properties) {

FILE: lib/fixture-json-stringify.js
  function fixtureJsonStringify (line 7) | function fixtureJsonStringify(object) {

FILE: lib/get-ajv-error-messages.js
  method propertyNames (line 4) | propertyNames(error, allErrors) {
  method additionalProperties (line 16) | additionalProperties(error) {
  method enum (line 19) | enum(error) {
  method oneOf (line 23) | oneOf(error) {
  method const (line 38) | const(error) {
  function getAjvErrorMessages (line 48) | function getAjvErrorMessages(ajvErrors, rootName = 'root') {
  function getDataDescription (line 71) | function getDataDescription(data) {
  function getShortenedString (line 88) | function getShortenedString(string) {

FILE: lib/import-json.js
  function importJson (line 9) | async function importJson(jsonPath, basePath) {

FILE: lib/model.js
  function fixtureFromFile (line 13) | async function fixtureFromFile(absolutePath) {
  function fixtureFromRepository (line 36) | async function fixtureFromRepository(manufacturerKey, fixtureKey) {
  function manufacturerFromRepository (line 59) | async function manufacturerFromRepository(manufacturerKey) {
  function embedResourcesIntoFixtureJson (line 67) | async function embedResourcesIntoFixtureJson(fixtureJson) {
  function getResourceFromString (line 83) | async function getResourceFromString(resourceName) {
  function resolveResourceName (line 121) | async function resolveResourceName(resourceName) {
  function getImageForResource (line 180) | async function getImageForResource(type, baseUrl, key) {

FILE: lib/model/AbstractChannel.js
  class AbstractChannel (line 7) | class AbstractChannel {
    method constructor (line 13) | constructor(key) {
    method fixture (line 27) | get fixture() {
    method key (line 34) | get key() {
    method name (line 42) | get name() {
    method uniqueName (line 50) | get uniqueName() {
    method pixelKey (line 57) | get pixelKey() {
    method pixelKey (line 64) | set pixelKey(pixelKey) {

FILE: lib/model/Capability.js
  constant START_END_ENTITIES (line 10) | const START_END_ENTITIES = ['speed', 'duration', 'time', 'brightness', '...
  function getSlotCapabilityName (line 256) | function getSlotCapabilityName(capability) {
  class Capability (line 270) | class Capability {
    method START_END_ENTITIES (line 274) | static get START_END_ENTITIES() {
    method constructor (line 284) | constructor(jsonObject, resolution, channel) {
    method jsonObject (line 294) | get jsonObject() {
    method dmxRange (line 301) | get dmxRange() {
    method rawDmxRange (line 308) | get rawDmxRange() {
    method getDmxRangeWithResolution (line 316) | getDmxRangeWithResolution(desiredResolution) {
    method type (line 331) | get type() {
    method name (line 338) | get name() {
    method hasComment (line 349) | get hasComment() {
    method comment (line 356) | get comment() {
    method isStep (line 363) | get isStep() {
    method isInverted (line 375) | get isInverted() {
    method usedStartEndEntities (line 393) | get usedStartEndEntities() {
    method canCrossfadeTo (line 403) | canCrossfadeTo(nextCapability) {
    method helpWanted (line 429) | get helpWanted() {
    method menuClick (line 436) | get menuClick() {
    method menuClickDmxValue (line 443) | get menuClickDmxValue() {
    method getMenuClickDmxValueWithResolution (line 451) | getMenuClickDmxValueWithResolution(desiredResolution) {
    method switchChannels (line 476) | get switchChannels() {
    method shutterEffect (line 487) | get shutterEffect() {
    method color (line 494) | get color() {
    method colors (line 501) | get colors() {
    method wheels (line 534) | get wheels() {
    method isSlotType (line 557) | isSlotType(slotType) {
    method isShaking (line 572) | get isShaking() {
    method effectName (line 579) | get effectName() {
    method effectPreset (line 599) | get effectPreset() {
    method isSoundControlled (line 606) | get isSoundControlled() {
    method randomTiming (line 613) | get randomTiming() {
    method blade (line 620) | get blade() {
    method fogType (line 627) | get fogType() {
    method hold (line 634) | get hold() {
    method speed (line 649) | get speed() {
    method duration (line 656) | get duration() {
    method time (line 663) | get time() {
    method brightness (line 670) | get brightness() {
    method slotNumber (line 684) | get slotNumber() {
    method wheelSlot (line 691) | get wheelSlot() {
    method angle (line 712) | get angle() {
    method horizontalAngle (line 719) | get horizontalAngle() {
    method verticalAngle (line 726) | get verticalAngle() {
    method colorTemperature (line 733) | get colorTemperature() {
    method soundSensitivity (line 740) | get soundSensitivity() {
    method shakeAngle (line 747) | get shakeAngle() {
    method shakeSpeed (line 754) | get shakeSpeed() {
    method distance (line 761) | get distance() {
    method openPercent (line 768) | get openPercent() {
    method frostIntensity (line 775) | get frostIntensity() {
    method insertion (line 782) | get insertion() {
    method fogOutput (line 789) | get fogOutput() {
    method parameter (line 796) | get parameter() {
    method _getStartEndArray (line 806) | _getStartEndArray(property) {
  function getSimpleCapabilityName (line 834) | function getSimpleCapabilityName(capability, name, property, propertyNam...
  function appendInBrackets (line 845) | function appendInBrackets(string, ...inBrackets) {
  function colorTemperaturesToString (line 863) | function colorTemperaturesToString([start, end]) {
  function startEndToString (line 912) | function startEndToString([start, end], propertyName = null, propertyNam...

FILE: lib/model/CoarseChannel.js
  class CoarseChannel (line 53) | class CoarseChannel extends AbstractChannel {
    method RESOLUTION_8BIT (line 62) | static get RESOLUTION_8BIT() {
    method RESOLUTION_16BIT (line 69) | static get RESOLUTION_16BIT() {
    method RESOLUTION_24BIT (line 76) | static get RESOLUTION_24BIT() {
    method RESOLUTION_32BIT (line 83) | static get RESOLUTION_32BIT() {
    method constructor (line 93) | constructor(key, jsonObject, fixture) {
    method jsonObject (line 102) | get jsonObject() {
    method fixture (line 110) | get fixture() {
    method name (line 118) | get name() {
    method type (line 125) | get type() {
    method color (line 150) | get color() {
    method fineChannelAliases (line 158) | get fineChannelAliases() {
    method fineChannels (line 165) | get fineChannels() {
    method maxResolution (line 174) | get maxResolution() {
    method ensureProperResolution (line 183) | ensureProperResolution(uncheckedResolution) {
    method dmxValueResolution (line 192) | get dmxValueResolution() {
    method getResolutionInMode (line 211) | getResolutionInMode(mode, switchingChannelBehavior) {
    method maxDmxBound (line 223) | get maxDmxBound() {
    method hasDefaultValue (line 230) | get hasDefaultValue() {
    method defaultValue (line 237) | get defaultValue() {
    method _defaultValuePerResolution (line 245) | get _defaultValuePerResolution() {
    method getDefaultValueWithResolution (line 265) | getDefaultValueWithResolution(desiredResolution) {
    method hasHighlightValue (line 274) | get hasHighlightValue() {
    method highlightValue (line 281) | get highlightValue() {
    method _highlightValuePerResolution (line 289) | get _highlightValuePerResolution() {
    method getHighlightValueWithResolution (line 316) | getHighlightValueWithResolution(desiredResolution) {
    method isInverted (line 325) | get isInverted() {
    method isConstant (line 334) | get isConstant() {
    method canCrossfade (line 341) | get canCrossfade() {
    method precedence (line 356) | get precedence() {
    method switchingChannelAliases (line 363) | get switchingChannelAliases() {
    method switchingChannels (line 370) | get switchingChannels() {
    method switchToChannelKeys (line 379) | get switchToChannelKeys() {
    method capabilities (line 388) | get capabilities() {
    method isHelpWanted (line 408) | get isHelpWanted() {

FILE: lib/model/Entity.js
  constant KEYWORDS (line 3) | const KEYWORDS = {
  class Entity (line 63) | class Entity {
    method constructor (line 70) | constructor(number, unit, keyword = null) {
    method number (line 79) | get number() {
    method unit (line 86) | get unit() {
    method keyword (line 93) | get keyword() {
    method baseUnitEntity (line 100) | get baseUnitEntity() {
    method valueOf (line 113) | valueOf() {
    method toString (line 120) | toString() {
    method equals (line 128) | equals(anotherEntity) {
    method createFromEntityString (line 141) | static createFromEntityString(entityString) {

FILE: lib/model/FineChannel.js
  class FineChannel (line 10) | class FineChannel extends AbstractChannel {
    method constructor (line 16) | constructor(key, coarseChannel) {
    method coarseChannel (line 24) | get coarseChannel() {
    method coarserChannel (line 31) | get coarserChannel() {
    method name (line 39) | get name() {
    method fixture (line 48) | get fixture() {
    method resolution (line 55) | get resolution() {
    method defaultValue (line 62) | get defaultValue() {

FILE: lib/model/Fixture.js
  class Fixture (line 40) | class Fixture {
    method constructor (line 47) | constructor(manufacturer, key, jsonObject) {
    method manufacturer (line 56) | get manufacturer() {
    method key (line 63) | get key() {
    method jsonObject (line 70) | get jsonObject() {
    method url (line 77) | get url() {
    method name (line 85) | get name() {
    method hasShortName (line 92) | get hasShortName() {
    method shortName (line 99) | get shortName() {
    method categories (line 106) | get categories() {
    method mainCategory (line 113) | get mainCategory() {
    method meta (line 120) | get meta() {
    method hasComment (line 127) | get hasComment() {
    method comment (line 134) | get comment() {
    method helpWanted (line 141) | get helpWanted() {
    method isHelpWanted (line 148) | get isHelpWanted() {
    method isCapabilityHelpWanted (line 155) | get isCapabilityHelpWanted() {
    method links (line 164) | get links() {
    method getLinksOfType (line 172) | getLinksOfType(type) {
    method rdm (line 185) | get rdm() {
    method physical (line 192) | get physical() {
    method matrix (line 203) | get matrix() {
    method wheels (line 214) | get wheels() {
    method _wheelByName (line 225) | get _wheelByName() {
    method getWheelByName (line 235) | getWheelByName(wheelName) {
    method uniqueChannelNames (line 242) | get uniqueChannelNames() {
    method availableChannelKeys (line 267) | get availableChannelKeys() {
    method availableChannels (line 274) | get availableChannels() {
    method coarseChannelKeys (line 283) | get coarseChannelKeys() {
    method coarseChannels (line 292) | get coarseChannels() {
    method fineChannelAliases (line 301) | get fineChannelAliases() {
    method fineChannels (line 310) | get fineChannels() {
    method switchingChannelAliases (line 319) | get switchingChannelAliases() {
    method switchingChannels (line 328) | get switchingChannels() {
    method templateChannelKeys (line 338) | get templateChannelKeys() {
    method templateChannels (line 346) | get templateChannels() {
    method _templateChannelByKey (line 356) | get _templateChannelByKey() {
    method getTemplateChannelByKey (line 367) | getTemplateChannelByKey(channelKey) {
    method matrixChannelKeys (line 374) | get matrixChannelKeys() {
    method matrixChannels (line 383) | get matrixChannels() {
    method nullChannelKeys (line 396) | get nullChannelKeys() {
    method nullChannels (line 403) | get nullChannels() {
    method allChannelKeys (line 415) | get allChannelKeys() {
    method allChannels (line 422) | get allChannels() {
    method allChannelsByKey (line 429) | get allChannelsByKey() {
    method getChannelByKey (line 487) | getChannelByKey(key) {
    method capabilities (line 494) | get capabilities() {
    method modes (line 503) | get modes() {

FILE: lib/model/Manufacturer.js
  class Manufacturer (line 4) | class Manufacturer {
    method constructor (line 10) | constructor(key, jsonObject) {
    method name (line 18) | get name() {
    method comment (line 25) | get comment() {
    method hasComment (line 32) | get hasComment() {
    method website (line 39) | get website() {
    method rdmId (line 46) | get rdmId() {

FILE: lib/model/Matrix.js
  class Matrix (line 6) | class Matrix {
    method constructor (line 10) | constructor(jsonObject) {
    method jsonObject (line 17) | get jsonObject() {
    method pixelCount (line 25) | get pixelCount() {
    method pixelCountX (line 51) | get pixelCountX() {
    method pixelCountY (line 58) | get pixelCountY() {
    method pixelCountZ (line 65) | get pixelCountZ() {
    method definedAxes (line 72) | get definedAxes() {
    method pixelKeyStructure (line 92) | get pixelKeyStructure() {
    method _getPixelDefaultKeys (line 110) | _getPixelDefaultKeys() {
    method _getPixelDefaultKey (line 148) | _getPixelDefaultKey(x, y, z) {
    method pixelKeys (line 170) | get pixelKeys() {
    method getPixelKeysByOrder (line 185) | getPixelKeysByOrder(firstAxis, secondAxis, thirdAxis) {
    method pixelKeyPositions (line 207) | get pixelKeyPositions() {
    method pixelGroupKeys (line 226) | get pixelGroupKeys() {
    method pixelGroups (line 233) | get pixelGroups() {
    method _pixelKeyFulfillsConstraints (line 265) | _pixelKeyFulfillsConstraints(pixelKey, constraints) {
  function convertConstraintsToFunctions (line 287) | function convertConstraintsToFunctions(constraints) {
  function convertNumberConstraintToFunction (line 309) | function convertNumberConstraintToFunction(constraint) {

FILE: lib/model/Meta.js
  class Meta (line 4) | class Meta {
    method constructor (line 9) | constructor(jsonObject) {
    method authors (line 16) | get authors() {
    method createDate (line 23) | get createDate() {
    method lastModifyDate (line 30) | get lastModifyDate() {
    method importPlugin (line 37) | get importPlugin() {
    method importDate (line 44) | get importDate() {
    method importComment (line 51) | get importComment() {
    method hasImportComment (line 58) | get hasImportComment() {

FILE: lib/model/Mode.js
  class Mode (line 11) | class Mode {
    method constructor (line 17) | constructor(jsonObject, fixture) {
    method jsonObject (line 25) | get jsonObject() {
    method fixture (line 32) | get fixture() {
    method name (line 39) | get name() {
    method shortName (line 46) | get shortName() {
    method hasShortName (line 53) | get hasShortName() {
    method rdmPersonalityIndex (line 60) | get rdmPersonalityIndex() {
    method physicalOverride (line 67) | get physicalOverride() {
    method physical (line 78) | get physical() {
    method channelKeys (line 106) | get channelKeys() {
    method nullChannelCount (line 122) | get nullChannelCount() {
    method _getMatrixChannelKeysFromInsertBlock (line 132) | _getMatrixChannelKeysFromInsertBlock(channelInsert) {
    method _getRepeatForPixelKeys (line 164) | _getRepeatForPixelKeys(repeatFor) {
    method channels (line 188) | get channels() {
    method getChannelIndex (line 206) | getChannelIndex(channelKey, switchingChannelBehavior = 'all') {

FILE: lib/model/NullChannel.js
  class NullChannel (line 9) | class NullChannel extends CoarseChannel {
    method constructor (line 15) | constructor(fixture) {

FILE: lib/model/Physical.js
  class Physical (line 4) | class Physical {
    method constructor (line 9) | constructor(jsonObject) {
    method jsonObject (line 16) | get jsonObject() {
    method dimensions (line 23) | get dimensions() {
    method width (line 30) | get width() {
    method height (line 37) | get height() {
    method depth (line 44) | get depth() {
    method weight (line 51) | get weight() {
    method power (line 58) | get power() {
    method powerConnectors (line 65) | get powerConnectors() {
    method DMXconnector (line 72) | get DMXconnector() {
    method hasBulb (line 79) | get hasBulb() {
    method bulbType (line 86) | get bulbType() {
    method bulbColorTemperature (line 93) | get bulbColorTemperature() {
    method bulbLumens (line 100) | get bulbLumens() {
    method hasLens (line 107) | get hasLens() {
    method lensName (line 114) | get lensName() {
    method lensDegreesMin (line 121) | get lensDegreesMin() {
    method lensDegreesMax (line 128) | get lensDegreesMax() {
    method hasMatrixPixels (line 135) | get hasMatrixPixels() {
    method matrixPixelsDimensions (line 142) | get matrixPixelsDimensions() {
    method matrixPixelsSpacing (line 149) | get matrixPixelsSpacing() {

FILE: lib/model/Range.js
  class Range (line 4) | class Range {
    method constructor (line 9) | constructor(rangeArray) {
    method start (line 16) | get start() {
    method end (line 23) | get end() {
    method center (line 30) | get center() {
    method contains (line 38) | contains(value) {
    method overlapsWith (line 46) | overlapsWith(range) {
    method overlapsWithOneOf (line 54) | overlapsWithOneOf(ranges) {
    method isAdjacentTo (line 62) | isAdjacentTo(range) {
    method getRangeMergedWith (line 70) | getRangeMergedWith(range) {
    method toString (line 77) | toString() {
    method getMergedRanges (line 86) | static getMergedRanges(ranges) {

FILE: lib/model/Resource.js
  class Resource (line 6) | class Resource {
    method constructor (line 11) | constructor(jsonObject) {
    method name (line 20) | get name() {
    method keywords (line 27) | get keywords() {
    method source (line 34) | get source() {
    method key (line 43) | get key() {
    method type (line 50) | get type() {
    method alias (line 57) | get alias() {
    method hasImage (line 64) | get hasImage() {
    method imageExtension (line 71) | get imageExtension() {
    method imageMimeType (line 78) | get imageMimeType() {
    method imageData (line 85) | get imageData() {
    method imageEncoding (line 92) | get imageEncoding() {
    method imageDataUrl (line 99) | get imageDataUrl() {

FILE: lib/model/SwitchingChannel.js
  class SwitchingChannel (line 11) | class SwitchingChannel extends AbstractChannel {
    method constructor (line 17) | constructor(alias, triggerChannel) {
    method triggerChannel (line 25) | get triggerChannel() {
    method fixture (line 33) | get fixture() {
    method triggerCapabilities (line 46) | get triggerCapabilities() {
    method triggerRanges (line 58) | get triggerRanges() {
    method defaultChannelKey (line 80) | get defaultChannelKey() {
    method defaultChannel (line 89) | get defaultChannel() {
    method switchToChannelKeys (line 96) | get switchToChannelKeys() {
    method switchToChannels (line 107) | get switchToChannels() {
    method usesChannelKey (line 122) | usesChannelKey(channelKey, switchingChannelBehavior = 'all') {
    method isHelpWanted (line 141) | get isHelpWanted() {

FILE: lib/model/TemplateChannel.js
  class TemplateChannel (line 10) | class TemplateChannel extends CoarseChannel {
    method constructor (line 17) | constructor(key, jsonObject, fixture) {
    method allTemplateKeys (line 24) | get allTemplateKeys() {
    method possibleMatrixChannelKeys (line 31) | get possibleMatrixChannelKeys() {
    method createMatrixChannels (line 48) | createMatrixChannels() {
    method resolveTemplateObject (line 76) | static resolveTemplateObject(object, variables) {
    method resolveTemplateString (line 86) | static resolveTemplateString(string, variables) {
  function stringReplaceAll (line 103) | function stringReplaceAll(string, search, replacement) {

FILE: lib/model/Wheel.js
  class Wheel (line 7) | class Wheel {
    method constructor (line 13) | constructor(wheelName, jsonObject) {
    method name (line 23) | get name() {
    method direction (line 30) | get direction() {
    method type (line 37) | get type() {
    method slots (line 61) | get slots() {
    method getSlot (line 71) | getSlot(slotNumber) {
    method getAbsoluteSlotIndex (line 95) | getAbsoluteSlotIndex(slotNumber) {
    method getSlotsOfType (line 103) | getSlotsOfType(type) {

FILE: lib/model/WheelSlot.js
  class WheelSlot (line 82) | class WheelSlot {
    method constructor (line 90) | constructor(jsonObject, wheel, floorSlot = null, ceilSlot = null) {
    method isSplitSlot (line 100) | get isSplitSlot() {
    method type (line 107) | get type() {
    method nthOfType (line 122) | get nthOfType() {
    method resource (line 129) | get resource() {
    method name (line 144) | get name() {
    method colors (line 159) | get colors() {
    method colorTemperature (line 184) | get colorTemperature() {
    method facets (line 195) | get facets() {
    method openPercent (line 202) | get openPercent() {
    method frostIntensity (line 213) | get frostIntensity() {
    method floorSlot (line 224) | get floorSlot() {
    method ceilSlot (line 231) | get ceilSlot() {

FILE: lib/register.js
  class Register (line 14) | class Register {
    method constructor (line 20) | constructor(manufacturers, register) {
    method addManufacturer (line 46) | addManufacturer(manufacturerKey, manufacturer) {
    method addFixtureRedirect (line 68) | addFixtureRedirect(manufacturerKey, fixtureKey, redirectData, redirect...
    method addFixture (line 92) | addFixture(manufacturerKey, fixtureKey, fixtureData) {
    method _addFixtureToManufacturer (line 128) | _addFixtureToManufacturer(manufacturerKey, fixtureKey) {
    method _addFixtureToCategory (line 141) | _addFixtureToCategory(manufacturerKey, fixtureKey, category) {
    method _addFixtureToContributor (line 155) | _addFixtureToContributor(manufacturerKey, fixtureKey, contributor, las...
    method getAsSortedObject (line 174) | getAsSortedObject() {
  function getObjectSortedByKeys (line 247) | function getObjectSortedByKeys(object, itemMapFunction) {
  function localeSort (line 264) | function localeSort(a, b) {
  function getFixtureLastAction (line 274) | function getFixtureLastAction(fixtureMeta) {

FILE: lib/scale-dmx-values.js
  function scaleDmxValue (line 7) | function scaleDmxValue(dmxValue, currentResolution, desiredResolution) {
  function scaleDmxRange (line 30) | function scaleDmxRange(dmxRangeStart, dmxRangeEnd, currentResolution, de...
  function scaleDmxRangeIndividually (line 42) | function scaleDmxRangeIndividually(dmxRangeStart, startResolution, dmxRa...
  function bytesToDmxValue (line 78) | function bytesToDmxValue(bytes) {
  function getBytes (line 93) | function getBytes(dmxValue, resolution) {

FILE: lib/server-response-helpers.js
  function sendJson (line 7) | function sendJson(response, jsonObject) {
  function sendAttachment (line 19) | function sendAttachment(response, { name, mimetype, content }) {

FILE: lib/site-crawler.js
  constant BASE_URL (line 6) | const BASE_URL = `http://localhost:${process.env.PORT || 3000}`;
  class SiteCrawler (line 13) | class SiteCrawler extends EventTarget {
    method startServer (line 19) | async startServer() {
    method emit (line 56) | emit(eventName, url, error) {
    method crawl (line 67) | async crawl() {
    method stopServer (line 135) | async stopServer() {

FILE: nuxt.config.js
  method extend (line 78) | extend(config, context) {
  method head (line 120) | head() {
  method robots (line 267) | robots() {
  method routes (line 285) | async routes() {

FILE: plugins/aglight/export.js
  function exportFixtures (line 25) | async function exportFixtures(fixtures, options) {
  function exportFixture (line 60) | function exportFixture(fixture, manufacturers, namedColors) {
  function downgradePhysical (line 86) | function downgradePhysical(physicalJsonData) {
  function transformMatrixChannels (line 98) | function transformMatrixChannels(fixtureJson, fixture) {
  function transformSingleCapabilityToArray (line 130) | function transformSingleCapabilityToArray(fixtureJson) {
  function transformNonNumericValues (line 146) | function transformNonNumericValues(fixtureJson, namedColors) {
  function processColor (line 165) | function processColor(capability, namedColors) {
  function getEntityNumber (line 180) | function getEntityNumber(entityString) {

FILE: plugins/color-chief/export.js
  function exportFixtures (line 38) | async function exportFixtures(fixtures, options) {
  function getFilesForMode (line 55) | function getFilesForMode(mode) {
  function getModeChannels (line 103) | function getModeChannels(mode) {
  function groupModeChannelsByPixelKey (line 144) | function groupModeChannelsByPixelKey(mode) {
  function mergeDisjunctSegments (line 161) | function mergeDisjunctSegments(segments) {
  function isColorChannel (line 191) | function isColorChannel(channel, color) {
  function isChannelOfType (line 204) | function isChannelOfType(channel, type) {
  function getFileName (line 219) | function getFileName(mode, totalFiles, fileIndex, extension) {
  function getChannelsForFile (line 229) | function getChannelsForFile(modeChannels, fileIndex) {

FILE: plugins/colorsource/export.js
  constant EDITOR_VERSION (line 9) | const EDITOR_VERSION = '1.1.1.9.0.4';
  constant CHANNEL_TYPE_NO_FUNCTION (line 11) | const CHANNEL_TYPE_NO_FUNCTION = 0;
  constant CHANNEL_TYPE_INTENSITY (line 12) | const CHANNEL_TYPE_INTENSITY = 1;
  constant CHANNEL_TYPE_POSITION (line 13) | const CHANNEL_TYPE_POSITION = 2;
  constant CHANNEL_TYPE_MULTI_COLOR (line 14) | const CHANNEL_TYPE_MULTI_COLOR = 3;
  constant CHANNEL_TYPE_BEAM (line 15) | const CHANNEL_TYPE_BEAM = 4;
  constant CHANNEL_TYPE_COLOR (line 16) | const CHANNEL_TYPE_COLOR = 5;
  constant UUID_NAMESPACE (line 18) | const UUID_NAMESPACE = '0de81b51-02b2-45e3-b53c-578f9eb31b77';
  function exportFixtures (line 27) | async function exportFixtures(fixtures, options) {
  function getColorSourcePersonality (line 56) | function getColorSourcePersonality(fixture, fixtureUuidNamespace, mode) {
  function getColorTable (line 89) | function getColorTable(colorSourceChannels) {
  function getCommands (line 135) | function getCommands(mode) {
  function getColorSourceChannels (line 176) | function getColorSourceChannels(mode, hasIntensity) {
  function getColorSourceChannelType (line 300) | function getColorSourceChannelType(channel) {
  function removeEmptyProperties (line 340) | function removeEmptyProperties(object) {

FILE: plugins/d-light/export.js
  function exportFixtures (line 21) | async function exportFixtures(fixtures, options) {
  function exportFixtureMode (line 50) | function exportFixtureMode(fixture, mode, options) {
  function addAttribute (line 96) | function addAttribute(xml, mode, attribute, channels) {
  function addCapability (line 149) | function addCapability(capability, xmlCapabilities) {
  function getParameterName (line 181) | function getParameterName(channel, mode, attribute, indexInAttribute) {
  function getDefaultValue (line 212) | function getDefaultValue(channel) {
  function getUsableChannel (line 224) | function getUsableChannel(channel) {
  function getChannelsByAttribute (line 236) | function getChannelsByAttribute(channels) {

FILE: plugins/d-light/exportTests/attributes-correctness.js
  function testAttributesCorrectness (line 17) | async function testAttributesCorrectness(exportFile, allExportFiles) {

FILE: plugins/dmxcontrol3/ddf3-function-groups.js
  function mergeIntoNew (line 129) | function mergeIntoNew(tagName) {
  function mergeIntoFirst (line 144) | function mergeIntoFirst(firstElement, ...xmlElements) {
  function rename (line 155) | function rename(tagName) {

FILE: plugins/dmxcontrol3/ddf3-functions.js
  function getStrobeType (line 78) | function getStrobeType(capability) {
  function getSplittedCapabilities (line 274) | function getSplittedCapabilities(capability) {
  function getMergedCapability (line 341) | function getMergedCapability(capability1, capability2) {
  function getColor (line 381) | function getColor(capability) {
  function getSingleUnitCapabilities (line 812) | function getSingleUnitCapabilities(capabilities, property, allowedUnit, ...
  function getBaseXmlCapability (line 863) | function getBaseXmlCapability(capability, startValue = null, endValue = ...
  function getRotationSpeedXmlCapability (line 893) | function getRotationSpeedXmlCapability(capability) {
  function arraysEqual (line 910) | function arraysEqual(array1, array2) {

FILE: plugins/dmxcontrol3/export.js
  function exportFixtures (line 19) | async function exportFixtures(fixtures, options) {
  function exportFixtureMode (line 48) | function exportFixtureMode(fixture, mode, options) {
  function addInformation (line 81) | function addInformation(xml, mode) {
  function addFunctions (line 106) | function addFunctions(xml, mode) {
  function getChannelsPerPixel (line 214) | function getChannelsPerPixel(mode) {
  function addProcedures (line 242) | function addProcedures(xml, mode) {
  function addMatrix (line 294) | function addMatrix(mode, xmlFunctionsPerPixel) {
  function addChannelAttributes (line 335) | function addChannelAttributes(xmlElement, mode, channel) {

FILE: plugins/dmxcontrol3/exportTests/channel-numbers.js
  function testChannelNumbers (line 15) | async function testChannelNumbers(exportFile) {

FILE: plugins/dragonframe/export.js
  function exportFixtures (line 18) | async function exportFixtures(fixtures, options) {
  function getFixtureFile (line 59) | function getFixtureFile(fixture) {
  function downgradePhysical (line 85) | function downgradePhysical(physicalJsonData) {

FILE: plugins/dragonframe/exportTests/json-schema-conformity.js
  constant REPO_BASE_URL (line 7) | const REPO_BASE_URL = 'https://raw.githubusercontent.com/OpenLightingPro...
  constant SCHEMA_BASE_URL (line 8) | const SCHEMA_BASE_URL = `${REPO_BASE_URL}/schema-${SUPPORTED_OFL_VERSION...
  constant SCHEMA_FILES (line 9) | const SCHEMA_FILES = [
  function testJsonSchemaConformity (line 37) | async function testJsonSchemaConformity(exportFile, allExportFiles) {
  function getSchemas (line 58) | async function getSchemas() {
  function downloadSchema (line 88) | function downloadSchema(url) {

FILE: plugins/ecue/export.js
  function exportFixtures (line 20) | async function exportFixtures(fixtures, options) {
  function addFixture (line 91) | function addFixture(xmlManufacturer, fixture) {
  function handleMode (line 127) | function handleMode(xmlFixture, mode) {
  function getFixtureComment (line 194) | function getFixtureComment(fixture) {
  function getChannelType (line 206) | function getChannelType(channel) {
  function addCapabilities (line 243) | function addCapabilities(xmlChannel, channel, resolution) {
  function dateToString (line 260) | function dateToString(date) {

FILE: plugins/ecue/import.js
  function importFixtures (line 15) | async function importFixtures(buffer, filename, authorName) {
  function getPhysical (line 134) | function getPhysical(ecueFixture) {
  function getCombinedEcueChannels (line 160) | function getCombinedEcueChannels(ecueFixture) {
  function getDirectionSuffix (line 184) | function getDirectionSuffix(direction) {
  function addChannelToFixture (line 199) | function addChannelToFixture(ecueChannel, fixture, warningsArray, colors) {
  function slugify (line 528) | function slugify(string) {

FILE: plugins/gdtf/deprecated-gdtf-attributes.js
  method beforePhysicalPropertyHook (line 81) | beforePhysicalPropertyHook(capability, gdtfCapability, attributeName) {
  method beforePhysicalPropertyHook (line 459) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  function guessColorComponentName (line 510) | function guessColorComponentName(gdtfCapability, primaryColor, secondary...

FILE: plugins/gdtf/gdtf-attributes.js
  method None (line 10) | None(value) {
  method Percent (line 13) | Percent(value) {
  method Length (line 16) | Length(value) {
  method Mass (line 19) | Mass(value) {
  method Time (line 22) | Time(value, otherValue) {
  method Temperature (line 29) | Temperature(value) {
  method LuminousIntensity (line 32) | LuminousIntensity(value) {
  method Angle (line 35) | Angle(value) {
  method Force (line 38) | Force(value) {
  method Frequency (line 41) | Frequency(value) {
  method Current (line 44) | Current(value) {
  method Voltage (line 47) | Voltage(value) {
  method Power (line 50) | Power(value) {
  method Energy (line 53) | Energy(value) {
  method Area (line 56) | Area(value) {
  method Volume (line 59) | Volume(value) {
  method Speed (line 62) | Speed(value) {
  method Acceleration (line 65) | Acceleration(value) {
  method AngularSpeed (line 68) | AngularSpeed(value, otherValue) {
  method AngularAcc (line 77) | AngularAcc(value) {
  method Wavelength (line 80) | Wavelength(value) {
  method ColorComponent (line 85) | ColorComponent(value) {
  function physicalValuesFulfillCondition (line 99) | function physicalValuesFulfillCondition(value1, value2, predicate) {
  method beforePhysicalPropertyHook (line 175) | beforePhysicalPropertyHook(capability, gdtfCapability, attributeName) {
  method beforePhysicalPropertyHook (line 202) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 222) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 246) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 250) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 259) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 272) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 282) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 289) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 292) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 299) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 302) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 309) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 316) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 323) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 330) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 333) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 340) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 347) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 354) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 361) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 364) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 371) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 378) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 385) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 392) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 399) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 426) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 590) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 618) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 627) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 640) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 684) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 726) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 735) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 746) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 771) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 775) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 792) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 803) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 815) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 832) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 869) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 878) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 891) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 898) | afterPhysicalPropertyHook(capability) {
  method beforePhysicalPropertyHook (line 995) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1023) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1043) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1056) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1065) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1074) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1083) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 1086) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1095) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 1098) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1107) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 1110) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  method beforePhysicalPropertyHook (line 1119) | beforePhysicalPropertyHook(capability, gdtfCapability) {
  method afterPhysicalPropertyHook (line 1122) | afterPhysicalPropertyHook(capability, gdtfCapability) {
  function guessSpeedOrDuration (line 1201) | function guessSpeedOrDuration(gdtfCapability) {

FILE: plugins/gdtf/gdtf-helpers.js
  function followXmlNodeReference (line 6) | function followXmlNodeReference(startNode, nodeReference) {
  function getRgbColorFromGdtfColor (line 46) | function getRgbColorFromGdtfColor(gdtfColorString) {
  function normalizeAngularSpeedDirection (line 117) | function normalizeAngularSpeedDirection(gdtfCapability) {

FILE: plugins/gdtf/import.js
  function importFixtures (line 27) | async function importFixtures(buffer, filename, authorName) {
  function getGdtfXml (line 1175) | async function getGdtfXml(buffer, filename) {
  function cleanUpChannelWrappers (line 1198) | function cleanUpChannelWrappers(channelWrappers) {
  function transformRelations (line 1248) | function transformRelations(fixture, switchingChannelRelations) {
  function replaceSwitchingChannelsInModes (line 1323) | function replaceSwitchingChannelsInModes(fixture, modeChannelReplacement...
  function cleanUpFixture (line 1344) | function cleanUpFixture(fixture, warnings) {
  function xmlNodeHasNotNoneAttribute (line 1374) | function xmlNodeHasNotNoneAttribute(xmlNode, attribute) {
  function getIsoDateFromGdtfDate (line 1387) | function getIsoDateFromGdtfDate(dateString, fallbackDateString) {
  function getDmxValueWithResolutionFromGdtfDmxValue (line 1417) | function getDmxValueWithResolutionFromGdtfDmxValue(dmxValueString, fallb...
  function parseFloatWithFallback (line 1436) | function parseFloatWithFallback(value, fallback) {
  function slugify (line 1445) | function slugify(string) {

FILE: plugins/millumin/export.js
  function exportFixtures (line 18) | async function exportFixtures(fixtures, options) {
  function getFixtureFile (line 36) | function getFixtureFile(fixture) {
  function getDowngradedCategories (line 89) | function getDowngradedCategories(categories) {
  function getDowngradedFixturePhysical (line 126) | function getDowngradedFixturePhysical(jsonPhysical, fixture) {
  function getDowngradedMatrix (line 195) | function getDowngradedMatrix(jsonMatrix, fixture) {
  function getDowngradedChannel (line 211) | function getDowngradedChannel(channelKey, jsonChannel, fixture) {
  function addIfValidData (line 270) | function addIfValidData(object, property, data, value) {

FILE: plugins/millumin/exportTests/json-schema-conformity.js
  constant SCHEMA_BASE_URL (line 7) | const SCHEMA_BASE_URL = `https://raw.githubusercontent.com/OpenLightingP...
  constant SCHEMA_FILES (line 8) | const SCHEMA_FILES = ['capability.json', 'channel.json', 'definitions.js...
  function testSchemaConformity (line 26) | async function testSchemaConformity(exportFile, allExportFiles) {
  function getSchemas (line 46) | async function getSchemas() {
  function downloadSchema (line 74) | function downloadSchema(url) {

FILE: plugins/ofl/export.js
  function exportFixtures (line 16) | async function exportFixtures(fixtures, options) {
  function getFixtureFile (line 60) | function getFixtureFile(fixture, displayedPluginVersion) {

FILE: plugins/op-z/export.js
  constant MAX_KNOBS (line 10) | const MAX_KNOBS = 8;
  constant MAX_OPZ_FIXTURES (line 11) | const MAX_OPZ_FIXTURES = 16;
  function exportFixtures (line 23) | async function exportFixtures(fixtures, options) {
  function getOpZChannelType (line 66) | function getOpZChannelType(channel, fixtureKey) {
  function getKnobType (line 116) | function getKnobType(channel, fixtureKey) {

FILE: plugins/qlcplus_4.12.2/export.js
  function exportFixtures (line 28) | async function exportFixtures(fixtures, options) {
  function getFixtureFile (line 67) | async function getFixtureFile(fixture, options, customGobos) {
  function addChannel (line 128) | async function addChannel(xml, channel, customGobos) {
  function addFineChannel (line 174) | async function addFineChannel(xml, fineChannel, customGobos) {
  function addCapability (line 241) | async function addCapability(xmlChannel, capability, customGobos) {
  function addCapabilityLegacyAttributes (line 285) | async function addCapabilityLegacyAttributes(xmlCapability, capability, ...
  function addCapabilityAliases (line 309) | function addCapabilityAliases(xmlCapability, capability) {
  function addMode (line 346) | function addMode(xml, mode, createPhysical) {
  function addPhysical (line 381) | function addPhysical(xmlParentNode, physical, fixture, mode) {
  function getPanTiltMax (line 481) | function getPanTiltMax(panOrTilt, channels) {
  function addHeads (line 506) | function addHeads(xmlMode, mode) {
  function getFixtureType (line 552) | function getFixtureType(fixture) {
  function getChannelType (line 584) | function getChannelType(type) {

FILE: plugins/qlcplus_4.12.2/exportTests/fixture-tool-validation.js
  constant GITHUB_BASE_URL (line 11) | const GITHUB_BASE_URL = 'https://raw.githubusercontent.com/mcallegari/ql...
  constant FIXTURE_TOOL_DIR_PREFIX (line 12) | const FIXTURE_TOOL_DIR_PREFIX = path.join(os.tmpdir(), 'ofl-qlcplus5-fix...
  constant FIXTURE_TOOL_PATH (line 13) | const FIXTURE_TOOL_PATH = 'resources/fixtures/scripts/fixtures-tool.py';
  constant COLOR_FILTERS_PATH (line 14) | const COLOR_FILTERS_PATH = 'resources/colorfilters/namedrgb.qxcf';
  constant EXPORTED_FIXTURE_PATH (line 15) | const EXPORTED_FIXTURE_PATH = 'resources/fixtures/manufacturer/fixture.q...
  function testFixtureToolValidation (line 31) | async function testFixtureToolValidation(exportFile, allExportFiles) {
  function downloadFile (line 96) | function downloadFile(url) {

FILE: plugins/qlcplus_4.12.2/exportTests/xsd-schema-conformity.js
  constant SCHEMA_URL (line 4) | const SCHEMA_URL = 'https://raw.githubusercontent.com/mcallegari/qlcplus...
  function testSchemaConformity (line 20) | async function testSchemaConformity(exportFile, allExportFiles) {

FILE: plugins/qlcplus_4.12.2/import.js
  function importFixtures (line 20) | async function importFixtures(buffer, filename, authorName) {
  function getOflCategories (line 106) | function getOflCategories(qlcPlusFixture) {
  function addOflFixturePhysical (line 121) | function addOflFixturePhysical(fixture, qlcPlusFixture) {
  function getOflMatrix (line 142) | function getOflMatrix(qlcPlusFixture) {
  function getOflWheels (line 232) | async function getOflWheels(qlcPlusFixture) {
  function addOflChannel (line 506) | function addOflChannel(fixture, qlcPlusChannel, qlcPlusFixture) {
  function getOflPhysical (line 620) | function getOflPhysical(qlcPlusPhysical, oflFixturePhysical = {}) {
  function getOflMode (line 755) | function getOflMode(qlcPlusMode, oflFixturePhysical, warningsArray) {
  function addHeadWarnings (line 790) | function addHeadWarnings(qlcPlusMode, mode, warningsArray) {
  function mergeFineChannels (line 809) | function mergeFineChannels(fixture, qlcPlusFixture, warningsArray) {
  function addSwitchingChannels (line 899) | function addSwitchingChannels(fixture, qlcPlusFixture) {
  function hasAliases (line 934) | function hasAliases(qlcPlusChannel) {
  function mergeSimilarSwitchChannels (line 941) | function mergeSimilarSwitchChannels(switchChannels) {
  function replaceSwitchingChannelsInModes (line 978) | function replaceSwitchingChannelsInModes(switchChannels, oflModes) {
  function addSwitchChannelsToCapabilities (line 1002) | function addSwitchChannelsToCapabilities(switchChannels, oflTriggerChann...
  function cleanUpFixture (line 1014) | function cleanUpFixture(fixture, qlcPlusFixture) {
  function slugify (line 1046) | function slugify(string) {

FILE: plugins/qlcplus_4.12.2/presets.js
  method getBeamAngleCap (line 146) | getBeamAngleCap({ channelName, channelType }, isAscending) {
  method getDirectionSuffix (line 170) | getDirectionSuffix(direction) {
  method getSpeedGuessedComment (line 185) | getSpeedGuessedComment(capabilityName, capability) {
  function getChannelPreset (line 566) | function getChannelPreset(channel) {
  function getCapabilityFromChannelPreset (line 583) | function getCapabilityFromChannelPreset(preset, channelName, panMax, til...
  function getFineChannelPreset (line 693) | function getFineChannelPreset(fineChannel) {
  function getCapabilityPreset (line 1134) | async function getCapabilityPreset(capability) {
  function getCapabilityFromCapabilityPreset (line 1156) | function getCapabilityFromCapabilityPreset(preset, capabilityData) {

FILE: server/webhook.js
  function startServer (line 28) | function startServer() {
  function processRequest (line 67) | function processRequest(url, body, headers) {
  function redeploy (line 113) | function redeploy(webhookPayload) {

FILE: tests/dmx-value-scaling.test.js
  function getScaledDownRanges (line 194) | function getScaledDownRanges(resolution) {
  function getRandomCapabilityRanges (line 204) | function getRandomCapabilityRanges(resolution) {

FILE: tests/external-links.js
  constant USER_AGENT (line 11) | const USER_AGENT = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gec...
  constant GITHUB_COMMENT_HEADING (line 12) | const GITHUB_COMMENT_HEADING = '## Broken links update';
  constant TIMEOUT (line 13) | const TIMEOUT = 30_000;
  function fetchExternalUrls (line 96) | async function fetchExternalUrls(externalUrls) {
  function testExternalLink (line 143) | async function testExternalLink(url) {
  function updateGithubIssue (line 201) | async function updateGithubIssue(urlResults) {
  function getFailedEmoji (line 493) | function getFailedEmoji(message) {

FILE: tests/fixture-valid.js
  function checkFixture (line 32) | async function checkFixture(manufacturerKey, fixtureKey, fixtureJson, un...
  function checkUniqueness (line 1309) | function checkUniqueness(set, value, result, messageIfNotUnique) {
  function getErrorString (line 1321) | function getErrorString(description, error) {
  function arraysEqual (line 1334) | function arraysEqual(a, b) {

FILE: tests/fixtures-valid.js
  function runTests (line 98) | async function runTests() {
  function checkFixtureFile (line 145) | async function checkFixtureFile(manufacturerKey, fixtureKey) {
  function checkManufacturers (line 167) | async function checkManufacturers() {
  function printFileResult (line 219) | function printFileResult(result) {

FILE: tests/github/export-diff.js
  function getDiffTasks (line 69) | async function getDiffTasks(changedComponents) {
  function performTask (line 191) | async function performTask(task) {
  function getChangeFlags (line 259) | function getChangeFlags(diffOutput) {
  function getEmoji (line 276) | function getEmoji(changeFlags) {

FILE: tests/github/exports-valid.js
  function mapExportTestsToTasks (line 131) | function mapExportTestsToTasks(tests, manufacturerKey, fixtureKey) {
  function getTasksForModel (line 144) | function getTasksForModel(changedComponents) {
  function getTasksForPlugins (line 164) | function getTasksForPlugins(changedComponents) {
  function getTasksForExportTests (line 189) | function getTasksForExportTests(changedComponents) {
  function getTasksForFixtures (line 205) | function getTasksForFixtures(changedComponents) {
  function getTaskPromise (line 221) | async function getTaskPromise(task) {

FILE: tests/github/pull-request.js
  function checkEnv (line 27) | async function checkEnv() {
  function init (line 39) | async function init() {
  function fetchChangedComponents (line 65) | async function fetchChangedComponents() {
  function updateComment (line 189) | async function updateComment(test) {

FILE: ui/api/download.js
  function downloadFixtures (line 23) | async function downloadFixtures(response, pluginKey, fixtures, zipName, ...

FILE: ui/api/index.js
  method origin (line 28) | origin(origin, callback) {
  method validationFail (line 50) | validationFail({ validation, request, operation }) {
  method notFound (line 67) | notFound(context) {
  method methodNotAllowed (line 73) | methodNotAllowed(context) {
  method notImplemented (line 79) | notImplemented(context) {
  method postResponseHandler (line 85) | postResponseHandler({ request, response, operation }) {

FILE: ui/api/routes/fixtures/from-editor.js
  function createFixtureFromEditor (line 22) | async function createFixtureFromEditor({ request }) {
  function getFixtureCreateResult (line 44) | async function getFixtureCreateResult(fixtures) {
  function isEmptyObject (line 458) | function isEmptyObject(object) {
  function propertyExistsIn (line 467) | function propertyExistsIn(property, object) {
  function getComboboxInput (line 477) | function getComboboxInput(property, from) {
  function slugify (line 485) | function slugify(string) {

FILE: ui/api/routes/fixtures/import.js
  function importFixtureFile (line 20) | async function importFixtureFile({ request }) {
  function importFixture (line 42) | async function importFixture(body) {

FILE: ui/api/routes/fixtures/submit.js
  function submitFixtures (line 19) | async function submitFixtures({ request }) {

FILE: ui/api/routes/get-search-results.js
  function getSearchResults (line 14) | async function getSearchResults({ request }) {
  function queryMatch (line 34) | function queryMatch(searchQuery, fixtureKey) {
  function manufacturerMatch (line 47) | function manufacturerMatch(manufacturersQuery, fixtureKey) {
  function categoryMatch (line 63) | function categoryMatch(categoriesQuery, fixtureKey) {

FILE: ui/api/routes/manufacturers/_manufacturerKey.js
  function getManufacturerByKey (line 10) | async function getManufacturerByKey({ request }) {

FILE: ui/api/routes/manufacturers/index.js
  function getManufacturers (line 10) | async function getManufacturers(context) {

FILE: ui/api/routes/plugins/_pluginKey.js
  function getPluginByKey (line 10) | async function getPluginByKey({ request }) {

FILE: ui/api/routes/plugins/index.js
  function getPlugins (line 10) | async function getPlugins(context) {

FILE: ui/api/routes/submit-feedback.js
  function createFeedbackIssue (line 11) | async function createFeedbackIssue({ request }) {

FILE: ui/assets/scripts/editor-utilities.js
  function getEmptyFormState (line 13) | function getEmptyFormState() {
  function getEmptyFixture (line 20) | function getEmptyFixture() {
  function getEmptyLink (line 51) | function getEmptyLink(linkType = 'manual') {
  function getEmptyPhysical (line 62) | function getEmptyPhysical() {
  function getEmptyMode (line 84) | function getEmptyMode() {
  function getEmptyChannel (line 99) | function getEmptyChannel() {
  function getEmptyFineChannel (line 131) | function getEmptyFineChannel(coarseChannelId, resolution) {
  function getEmptyCapability (line 142) | function getEmptyCapability() {
  function getEmptyWheelSlot (line 155) | function getEmptyWheelSlot() {
  function isChannelChanged (line 167) | function isChannelChanged(channel) {
  function isCapabilityChanged (line 195) | function isCapabilityChanged(capability) {
  function colorsHexStringToArray (line 211) | function colorsHexStringToArray(hexString) {
  function getSanitizedChannel (line 231) | function getSanitizedChannel(channel) {

FILE: ui/plugins/vue-form.js
  method 'step' (line 6) | 'step'(value, stepValue) {
  method 'data-exclusive-minimum' (line 9) | 'data-exclusive-minimum'(value, exclusiveMinimum) {
  method 'data-exclusive-maximum' (line 12) | 'data-exclusive-maximum'(value, exclusiveMaximum) {
  method 'complete-range' (line 15) | 'complete-range'(range) {
  method 'valid-range' (line 18) | 'valid-range'(range) {
  method 'categories-not-empty' (line 36) | 'categories-not-empty'(categories) {
  method 'complete-dimensions' (line 39) | 'complete-dimensions'(dimensions) {
  method 'start-with-uppercase-or-number' (line 42) | 'start-with-uppercase-or-number'(value) {
  method 'no-mode-name' (line 45) | 'no-mode-name'(value) {
  method 'no-fine-channel-name' (line 48) | 'no-fine-channel-name'(value) {
  method 'entity-complete' (line 55) | 'entity-complete'(value, attributeValue, vnode) {
  method 'entities-have-same-units' (line 64) | 'entities-have-same-units'(value, attributeValue, vnode) {
  method 'valid-color-hex-list' (line 67) | 'valid-color-hex-list'(value) {
  method 'max-file-size' (line 70) | 'max-file-size'(file, attributeValue) {
Condensed preview — 988 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (9,986K chars).
[
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 510,
    "preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.co"
  },
  {
    "path": ".editorconfig",
    "chars": 268,
    "preview": "# see https://editorconfig.org/\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\n"
  },
  {
    "path": ".gitattributes",
    "chars": 902,
    "preview": "# make JSON and Markdown files appear in GitHub language bar\n# see https://github.com/github/linguist#detectable\nfixture"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/new-plugin.md",
    "chars": 831,
    "preview": "---\nname: New plugin\nabout: Open Fixture Library should have a plugin for this fixture format.\ntitle: Add [software / co"
  },
  {
    "path": ".github/aw/actions-lock.json",
    "chars": 201,
    "preview": "{\n  \"entries\": {\n    \"github/gh-aw-actions/setup@v0.68.3\": {\n      \"repo\": \"github/gh-aw-actions/setup\",\n      \"version\""
  },
  {
    "path": ".github/dependabot.yaml",
    "chars": 645,
    "preview": "# See https://help.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically\n\nvers"
  },
  {
    "path": ".github/lighthouserc.json",
    "chars": 179,
    "preview": "{\n  \"ci\": {\n    \"collect\": {\n      \"startServerCommand\": \"npm run start\",\n      \"startServerReadyPattern\": \"Nuxt.js is r"
  },
  {
    "path": ".github/workflows/external-links-checker.yaml",
    "chars": 979,
    "preview": "name: External Links Checker\non:\n  schedule:\n    - cron: '30 18 * * *' # everyday 18:30 UTC\n  workflow_dispatch:\njobs:\n "
  },
  {
    "path": ".github/workflows/fixture-metadata-triage.lock.yml",
    "chars": 62335,
    "preview": "# gh-aw-metadata: {\"schema_version\":\"v3\",\"frontmatter_hash\":\"e3344764c91da961e68d011784f9ac52e21c60848ac0eef547ff7c18e04"
  },
  {
    "path": ".github/workflows/fixture-metadata-triage.md",
    "chars": 2382,
    "preview": "---\nname: Fixture Metadata Triage\ndescription: Daily triage of PRs with new-fixture/via-editor labels to validate fixtur"
  },
  {
    "path": ".github/workflows/lighthouse-production.yaml",
    "chars": 709,
    "preview": "name: Lighthouse\non:\n  schedule:\n    - cron: '30 18 * * *' # everyday 18:30 UTC\n  workflow_dispatch:\njobs:\n  production:"
  },
  {
    "path": ".github/workflows/lighthouse-review.yaml",
    "chars": 1314,
    "preview": "name: Lighthouse\non:\n  pull_request:\n    types: [opened, synchronize, reopened, labeled]\nconcurrency:\n  group: lighthous"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 2436,
    "preview": "name: Test\non: pull_request\nconcurrency:\n  group: test-${{ github.ref }}\n  cancel-in-progress: true\njobs:\n  required:\n  "
  },
  {
    "path": ".gitignore",
    "chars": 303,
    "preview": "# Node build artifacts\nnode_modules/\nnpm-debug.log\n\n# Local development\n*.env\n*.dev.*\n.DS_Store\n*.sublime-*\n.vscode/\n.id"
  },
  {
    "path": ".stylelintrc.yaml",
    "chars": 1550,
    "preview": "reportNeedlessDisables: true\nreportInvalidScopeDisables: true\nreportDescriptionlessDisables: true\nextends:\n  - '@stylist"
  },
  {
    "path": "LICENSE",
    "chars": 1081,
    "preview": "MIT License\n\nCopyright (c) 2017 Florian & Felix Edelmann\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "README.md",
    "chars": 2295,
    "preview": "# Open Fixture Library<br><a href=\"https://open-fixture-library.org/\">open-fixture-library.org</a>\n\n[![Mozilla HTTP Obse"
  },
  {
    "path": "cli/build-plugin-data.js",
    "chars": 4295,
    "preview": "#!/usr/bin/env node\n\nimport { readdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { fileURLToPath }"
  },
  {
    "path": "cli/build-register.js",
    "chars": 2382,
    "preview": "#!/usr/bin/env node\n\nimport { readdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { fileURLToPath }"
  },
  {
    "path": "cli/build-test-fixtures.js",
    "chars": 7199,
    "preview": "#!/usr/bin/env node\n\n/**\n * @fileoverview This script generates a set of test fixtures that cover all defined fixture fe"
  },
  {
    "path": "cli/debug-env-variables.js",
    "chars": 1127,
    "preview": "#!/usr/bin/env node\n\nimport { styleText } from 'util';\n\nconst usedVariables = [\n  'ALLOW_SEARCH_INDEXING',\n  'GITHUB_USE"
  },
  {
    "path": "cli/diff-plugin-outputs.js",
    "chars": 2702,
    "preview": "#!/usr/bin/env node\n\nimport { styleText } from 'util';\nimport minimist from 'minimist';\nimport diffPluginOutputs from '."
  },
  {
    "path": "cli/export-fixture.js",
    "chars": 3674,
    "preview": "#!/usr/bin/env node\nimport { mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport { fileURLToPath } fr"
  },
  {
    "path": "cli/import-fixture.js",
    "chars": 2377,
    "preview": "#!/usr/bin/env node\n\nimport { readFile } from 'fs/promises';\nimport minimist from 'minimist';\nimport createPullRequest f"
  },
  {
    "path": "cli/run-export-test.js",
    "chars": 3229,
    "preview": "#!/usr/bin/env node\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { styleText } from 'util';\nimpo"
  },
  {
    "path": "docs/CODE_OF_CONDUCT.md",
    "chars": 3228,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "chars": 7452,
    "preview": "# Contributing Guidelines\n\nWe believe in the power of open source development and want to encourage everyone to contribu"
  },
  {
    "path": "docs/README.md",
    "chars": 3537,
    "preview": "# Documentation Overview\n\nThis is the developer documentation for the *Open Fixture Library*. Please follow links in the"
  },
  {
    "path": "docs/capability-types.md",
    "chars": 26800,
    "preview": "This is a full list of capability types with their properties, entities and units. See the [general information about ca"
  },
  {
    "path": "docs/environment-variables.md",
    "chars": 3296,
    "preview": "# Environment Variables\n\nWe use environment variables for configuration and storing secret credentials. They are set by "
  },
  {
    "path": "docs/fixture-features.md",
    "chars": 1954,
    "preview": "# Fixture features\n\nFixture features are specific [fixture](fixture-format.md) characteristics (like \"uses RDM\" or \"uses"
  },
  {
    "path": "docs/fixture-format.md",
    "chars": 21592,
    "preview": "# JSON Fixture Format\n\nThis document gives a high-level overview of the concepts used in the JSON format of the fixture "
  },
  {
    "path": "docs/fixture-model.md",
    "chars": 3577,
    "preview": "# Fixture model\n\nInstead of parsing [fixtures' JSON data](fixture-format.md) directly, it is recommended to use the mode"
  },
  {
    "path": "docs/model-api.md",
    "chars": 153158,
    "preview": "## Classes\n\n<dl>\n<dt><a href=\"#AbstractChannel\">AbstractChannel</a></dt>\n<dd><p>Base class for channels.</p>\n</dd>\n<dt><"
  },
  {
    "path": "docs/plugins.md",
    "chars": 8106,
    "preview": "# Plugins\n\nThe aim of the *Open Fixture Library* is to import and export our fixture definitions from / to fixture forma"
  },
  {
    "path": "docs/rest-api.md",
    "chars": 15862,
    "preview": "<!-- Generator: Widdershins v4.0.1 -->\n\n<h1 id=\"open-fixture-library-api\">Open Fixture Library API v1.0</h1>\n\n> Scroll d"
  },
  {
    "path": "docs/testing.md",
    "chars": 2554,
    "preview": "# Testing\n\nWe try to develop unit tests wherever possible. They test specific components of our project by respecting th"
  },
  {
    "path": "docs/ui.md",
    "chars": 2022,
    "preview": "# User Interface (UI) / Website\n\nOur website [open-fixture-library.org/](https://open-fixture-library.org/) is a [Nuxt]("
  },
  {
    "path": "eslint.config.js",
    "chars": 16282,
    "preview": "import { fixupPluginRules } from '@eslint/compat';\nimport eslintJs from '@eslint/js';\nimport eslintMarkdown from '@eslin"
  },
  {
    "path": "fixtures/5star-systems/spica-250m.json",
    "chars": 12440,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/abstract/twister-4.json",
    "chars": 5426,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/acoustic-control/par-180-cob-3in1.json",
    "chars": 2692,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/adb/alc4.json",
    "chars": 5340,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/adb/europe-105.json",
    "chars": 4607,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/adb/warp-m.json",
    "chars": 13079,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/afx/lmh460z.json",
    "chars": 5709,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/alien-pro/alien-s.json",
    "chars": 11041,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/12p-hex-ip.json",
    "chars": 16483,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/18p-hex-ip.json",
    "chars": 16946,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/7p-hex-ip.json",
    "chars": 17096,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/auto-spot-150.json",
    "chars": 10573,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/boom-box-fx2.json",
    "chars": 8966,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/cob-cannon-wash.json",
    "chars": 25002,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/crazy-pocket-8.json",
    "chars": 10424,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/dekker-led.json",
    "chars": 4516,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/dotz-par.json",
    "chars": 11124,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/encore-lp12z-ip.json",
    "chars": 24971,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/encore-profile-1000-ww.json",
    "chars": 2098,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/flat-par-qa12.json",
    "chars": 15161,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/flat-par-qa12xs.json",
    "chars": 15244,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/fog-fury-jett-pro.json",
    "chars": 9061,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/galaxian-3d.json",
    "chars": 4790,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/illusion-dotz-4-4.json",
    "chars": 11514,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/inno-pocket-beam-q4.json",
    "chars": 6457,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/inno-pocket-fusion.json",
    "chars": 13131,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/inno-pocket-spot.json",
    "chars": 11719,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/inno-spot-pro.json",
    "chars": 13274,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-bar-50rgb-rc.json",
    "chars": 8558,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-bar-50rgb.json",
    "chars": 8560,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-bar-rgba.json",
    "chars": 26748,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-hex-par.json",
    "chars": 23817,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-par-profile-plus.json",
    "chars": 25131,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-tripar-profile-plus.json",
    "chars": 25265,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mega-tripar-profile.json",
    "chars": 11904,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/mod-hex100.json",
    "chars": 24282,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/pocket-pro.json",
    "chars": 16309,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/quad-phase-hp.json",
    "chars": 5387,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/revo-4-ir.json",
    "chars": 8525,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/revo-burst.json",
    "chars": 5423,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/revo-sweep.json",
    "chars": 8591,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/saber-spot-rgbw.json",
    "chars": 19048,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/starburst.json",
    "chars": 6225,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/stinger-ii.json",
    "chars": 7321,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/stinger-spot.json",
    "chars": 11085,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/ultra-hex-bar-12.json",
    "chars": 20579,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/uv-eco-bar.json",
    "chars": 2355,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/vbar-pak.json",
    "chars": 15660,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/vizi-q-wash7.json",
    "chars": 20920,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/vizi-spot-led-pro.json",
    "chars": 15592,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/american-dj/xs-400.json",
    "chars": 10327,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/ape-labs/lightcan.json",
    "chars": 8533,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/aputure/ls-1200d-pro.json",
    "chars": 5576,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/aputure/ls-300x.json",
    "chars": 4690,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/aputure/ls-600d-pro.json",
    "chars": 4552,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/aputure/ls-600d.json",
    "chars": 4881,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/aputure/ls-600x-pro.json",
    "chars": 5106,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/aputure/nova-p300c.json",
    "chars": 14629,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/broadcaster-2-plus.json",
    "chars": 1749,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/l10-c.json",
    "chars": 8978,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/l5-c.json",
    "chars": 8976,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/l7-c.json",
    "chars": 8976,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/skypanel-s120c.json",
    "chars": 15099,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/skypanel-s30c.json",
    "chars": 15417,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/skypanel-s360c.json",
    "chars": 15098,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/arri/skypanel-s60c.json",
    "chars": 15143,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/astera/ax3-lightdrop.json",
    "chars": 94212,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/astera/fp1-titan-tube.json",
    "chars": 65993,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/astera/fp2-helios-tube.json",
    "chars": 64108,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/astera/fp3-hyperion-tube.json",
    "chars": 69685,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/astera/fp5-nyx-bulb.json",
    "chars": 42413,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/audibax/boston-60.json",
    "chars": 5649,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/ayra/compar-20.json",
    "chars": 4198,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/ayra/tdc-triple-burst.json",
    "chars": 7884,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/ayrton/diablo-s.json",
    "chars": 24923,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/ayrton/diablo-tc.json",
    "chars": 24925,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/ayrton/magicblade-fx.json",
    "chars": 20039,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/beamz/h2000-faze-machine.json",
    "chars": 1401,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/beamz/panther-7r.json",
    "chars": 15984,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/beamz/pls25-par.json",
    "chars": 2070,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/beamz/triple-flex-centre-pro-led.json",
    "chars": 6074,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/big-dipper/lp001.json",
    "chars": 3136,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/big-dipper/ls90.json",
    "chars": 11122,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/bitfocus/companion-v2.json",
    "chars": 16986,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/blizzard/hotbox-exa.json",
    "chars": 5317,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/blizzard/hotbox-rgbw.json",
    "chars": 6240,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/blizzard/puck-rgbaw.json",
    "chars": 9175,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/blizzard/rokbox-rgbw.json",
    "chars": 5345,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/crazy-spot-30.json",
    "chars": 9463,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-12x10w-5in1.json",
    "chars": 3199,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-12x10w-6in1.json",
    "chars": 3336,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-12x3w-3in1.json",
    "chars": 2901,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-5x10w-5in1.json",
    "chars": 3194,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-5x10w-6in1.json",
    "chars": 3330,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-5x3w-3in1.json",
    "chars": 2896,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-7x10w-5in1.json",
    "chars": 3188,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-7x10w-6in1.json",
    "chars": 3330,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/silentpar-7x3w-3in1.json",
    "chars": 2896,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/boomtonedj/xtrem-led.json",
    "chars": 9912,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/beam-fury-1.json",
    "chars": 7509,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/beamspot1-dmx-fc.json",
    "chars": 5858,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/bt-coloray-120r.json",
    "chars": 3064,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/bt-coloray-18fcr.json",
    "chars": 3063,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/bt-coloray-60r.json",
    "chars": 3060,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/bt-ledrotor.json",
    "chars": 16427,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/bt-stagepar-6in1.json",
    "chars": 6551,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/btx-cirrus-ii.json",
    "chars": 28734,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/btx-titan.json",
    "chars": 52068,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/cob-slim-100-rgb.json",
    "chars": 3932,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/pro-beamer-zoom-indoor.json",
    "chars": 17781,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/briteq/pro-beamer-zoom-outdoor.json",
    "chars": 17802,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/auro-beam-150.json",
    "chars": 19761,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/auro-spot-100.json",
    "chars": 18959,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/auro-spot-200.json",
    "chars": 19091,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/auro-spot-300.json",
    "chars": 22271,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/auro-spot-400.json",
    "chars": 28435,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/auro-spot-z300.json",
    "chars": 22381,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flash-matrix-250.json",
    "chars": 17579,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flat-par-can-rgb-10-ir.json",
    "chars": 5640,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flat-par-can-tri-5x-3w-ir.json",
    "chars": 5396,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flat-par-can-tri-7x-3w-ir.json",
    "chars": 5572,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flat-pro-18.json",
    "chars": 8159,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flat-pro-flood-600-ip65.json",
    "chars": 6596,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/flat-pro-flood-ip65-tri.json",
    "chars": 5064,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/gobo-scanner-80.json",
    "chars": 11228,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/hydrabeam-100.json",
    "chars": 8772,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/hydrabeam-300-rgbw.json",
    "chars": 20462,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/hydrabeam-400-rgbw.json",
    "chars": 17465,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/instant-air-1000-pro.json",
    "chars": 1264,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/instant-air-2000-pro.json",
    "chars": 1243,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/instant-hazer-1500-t-pro.json",
    "chars": 1346,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ioda-1000-rgb.json",
    "chars": 25872,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ioda-400-rgy.json",
    "chars": 25735,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ioda-600-rgb.json",
    "chars": 25753,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/multi-fx-bar.json",
    "chars": 15493,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/multi-par-cob-1.json",
    "chars": 13695,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/nanospot-120.json",
    "chars": 9023,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/outdoor-par-tri-12.json",
    "chars": 4948,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/q-spot-40-cw.json",
    "chars": 2980,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/q-spot-40-rgbw.json",
    "chars": 7325,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/root-par-6.json",
    "chars": 19484,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/steam-wizard-1000.json",
    "chars": 5321,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/steam-wizard-2000.json",
    "chars": 4984,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/storm.json",
    "chars": 14889,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/thunder-wash-100-rgb.json",
    "chars": 9707,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/thunder-wash-100-w.json",
    "chars": 5102,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/thunder-wash-600-rgb.json",
    "chars": 9710,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/thunder-wash-600-rgbw.json",
    "chars": 9915,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/thunder-wash-600-w.json",
    "chars": 6273,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ts-100-ww.json",
    "chars": 3811,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ts-200-fc.json",
    "chars": 13764,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ts-60-rgbw.json",
    "chars": 8228,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/ts60.json",
    "chars": 215,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture-redire"
  },
  {
    "path": "fixtures/cameo/zenit-w600.json",
    "chars": 11574,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/cameo/zenit-z120.json",
    "chars": 9995,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/chauvet-dj/colorband-pix-ip.json",
    "chars": 7186,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/chauvet-dj/colorband-pix.json",
    "chars": 7115,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/chauvet-dj/corepar-uv-usb.json",
    "chars": 4788,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/chauvet-dj/dmx-4.json",
    "chars": 1372,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/chauvet-dj/eve-p-100-ww.json",
    "chars": 4492,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  },
  {
    "path": "fixtures/chauvet-dj/eve-p-130-rgb.json",
    "chars": 6019,
    "preview": "{\n  \"$schema\": \"https://raw.githubusercontent.com/OpenLightingProject/open-fixture-library/master/schemas/fixture.json\","
  }
]

// ... and 788 more files (download for full content)

About this extraction

This page contains the full source code of the OpenLightingProject/open-fixture-library GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 988 files (8.5 MB), approximately 2.3M tokens, and a symbol index with 720 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!