mb-run 1.1.4-dev-20261026-e0e9bae → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -22,12 +22,22 @@ If you like this project and find it useful, please consider giving it a star on
22
22
 
23
23
  <a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/assets/bmc-button.svg" alt="Buy me a coffee" width="120"></a>
24
24
 
25
- ## [1.1.4] - Dev branch
25
+ ## [1.1.4] - 2026-10-28
26
26
 
27
27
  ### Changed
28
28
 
29
+ - [upgrade]: Install jsdom in root devDependencies when `automator.frontend` is true.
30
+ - [upgrade]: Align frontend package scripts with the frontend template when `automator.frontend` is true.
31
+ - [upgrade]: Copy frontend TypeScript and Vitest configuration from the template when `automator.frontend` is true.
29
32
  - [template]: Update `.vscode/tasks.json` to 1.0.8: add `local` after `--pack` in the `Pack`, `Pack Minify` and `Pack Obfuscate` tasks.
30
33
  - [template]: Update the plugin frontend guide to v.1.0.1: build API URLs from Vite's base URL to preserve Home Assistant Ingress paths.
34
+ - [package]: Bump `rollup` to v.4.65.1.
35
+ - [package]: Bump `oxfmt` to v.0.75.0.
36
+ - [package]: Bump `oxlint` to v.1.90.0.
37
+
38
+ ### Fixed
39
+
40
+ - [scripts]: Update `downloads.mjs` to v.2.1.0 in the package and the template: query an explicit 30-day date range instead of the npm `last-month` period, which returns stale data.
31
41
 
32
42
  <a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/assets/bmc-button.svg" alt="Buy me a coffee" width="80"></a>
33
43
 
package/dist/upgrade.js CHANGED
@@ -595,6 +595,36 @@ export async function runPackageJsonUpgrade(opts, pkgPath, pkgJson, isMonorepo =
595
595
  : undefined,
596
596
  };
597
597
  }
598
+ if (!isWorkspace && automator?.frontend === true) {
599
+ copyRecursive('apps/frontend/tsconfig.json', 'apps/frontend/tsconfig.json');
600
+ copyRecursive('apps/frontend/vitest.config.ts', 'apps/frontend/vitest.config.ts');
601
+ }
602
+ if (!isWorkspace && automator?.frontend === true) {
603
+ const frontendPkgPath = path.join(path.dirname(pkgPath), 'apps', 'frontend', 'package.json');
604
+ const frontendPackageJson = JSON.parse(readFileSync(frontendPkgPath, 'utf8'));
605
+ frontendPackageJson.scripts = {
606
+ 'dev': 'vite',
607
+ 'build': 'vite build',
608
+ 'preview': 'vite preview',
609
+ 'typecheck': 'tsc --project tsconfig.json --noEmit',
610
+ 'test': 'vitest run',
611
+ 'test:watch': 'vitest watch',
612
+ 'test:verbose': 'vitest run --reporter verbose',
613
+ 'test:coverage': 'vitest run --coverage --coverage.thresholds.statements=100 --coverage.thresholds.branches=100 --coverage.thresholds.lines=100 --coverage.thresholds.functions=100',
614
+ 'lint': 'oxlint',
615
+ 'lint:fix': 'oxlint --fix',
616
+ 'format': 'oxfmt',
617
+ 'format:check': 'oxfmt --check',
618
+ 'clean': 'node ../../scripts/clean.mjs',
619
+ 'deepClean': 'node ../../scripts/deep-clean.mjs',
620
+ 'reset': 'npm run deepClean && npm run softReset',
621
+ 'softReset': 'npm install --no-fund --no-audit && npm prune --no-fund --no-audit && npm run build && npm run typecheck',
622
+ 'checkDependencies': 'npx --yes --package npm-check-updates@latest ncu',
623
+ 'updateDependencies': 'npx --yes --package npm-check-updates@latest ncu -u && npm run softReset',
624
+ 'runMeBeforePublish': 'npm run format && npm run lint && npm run build && npm run typecheck',
625
+ };
626
+ writeFileSync(frontendPkgPath, JSON.stringify(frontendPackageJson, null, 2) + '\n', 'utf8');
627
+ }
598
628
  const devDeps = pkgJson.devDependencies;
599
629
  delete devDeps?.['npm-check-updates'];
600
630
  delete devDeps?.['shx'];
@@ -644,6 +674,7 @@ export async function runPackageJsonUpgrade(opts, pkgPath, pkgJson, isMonorepo =
644
674
  : `npm install --no-fund --no-audit --save-dev --save-exact ${opts.useNode ? `@types/node` : ''} ${opts.useBun ? '@types/bun' : ''} ${automator?.jestTypes ? '@types/jest' : ''} ${automator?.vitestTypes ? 'vitest' : ''} typescript oxlint oxlint-tsgolint oxfmt`,
645
675
  opts.enableJest ? `npm install --no-fund --no-audit --save-dev --save-exact jest ts-jest @types/jest @jest/globals cross-env` : null,
646
676
  opts.enableVitest ? `npm install --no-fund --no-audit --save-dev --save-exact vitest @vitest/coverage-v8` : null,
677
+ !isWorkspace && automator?.frontend === true ? `npm install --no-fund --no-audit --save-dev --save-exact jsdom` : null,
647
678
  `npm prune --no-fund --no-audit`,
648
679
  isPlugin && !isWorkspace ? `npm link --no-fund --no-audit matterbridge` : null,
649
680
  isWorkspace ? null : `npm run format`,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mb-run",
3
- "version": "1.1.4-dev-20261026-e0e9bae",
3
+ "version": "1.1.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "mb-run",
9
- "version": "1.1.4-dev-20261026-e0e9bae",
9
+ "version": "1.1.4",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@typescript/typescript6": "6.0.2",
@@ -14,7 +14,7 @@
14
14
  "esbuild": "0.28.2",
15
15
  "javascript-obfuscator": "5.9.0",
16
16
  "npm-check-updates": "23.1.1",
17
- "rollup": "4.65.0",
17
+ "rollup": "4.65.1",
18
18
  "rollup-plugin-dts": "6.5.1"
19
19
  },
20
20
  "bin": {
@@ -620,9 +620,9 @@
620
620
  }
621
621
  },
622
622
  "node_modules/@rollup/rollup-android-arm-eabi": {
623
- "version": "4.65.0",
624
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.65.0.tgz",
625
- "integrity": "sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg==",
623
+ "version": "4.65.1",
624
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.65.1.tgz",
625
+ "integrity": "sha512-5coxNSjqOI2J3NkVhlp3u+zGcyjnowQFsU9sg7MMz4UPXY4Dv63kFGLt7ConXz9CNq2i0yuOJZAF/2RMrgI2OA==",
626
626
  "cpu": [
627
627
  "arm"
628
628
  ],
@@ -633,9 +633,9 @@
633
633
  ]
634
634
  },
635
635
  "node_modules/@rollup/rollup-android-arm64": {
636
- "version": "4.65.0",
637
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.65.0.tgz",
638
- "integrity": "sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ==",
636
+ "version": "4.65.1",
637
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.65.1.tgz",
638
+ "integrity": "sha512-K0HFf6XMB3IYs+jDZ1/RRhZaVOwxNXrl0n4MDEyGDjuK7+B6K/nDJp8DCBitO3S1sK/fzNxAf9QZAny4AIUPtg==",
639
639
  "cpu": [
640
640
  "arm64"
641
641
  ],
@@ -646,9 +646,9 @@
646
646
  ]
647
647
  },
648
648
  "node_modules/@rollup/rollup-darwin-arm64": {
649
- "version": "4.65.0",
650
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.65.0.tgz",
651
- "integrity": "sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g==",
649
+ "version": "4.65.1",
650
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.65.1.tgz",
651
+ "integrity": "sha512-zkZrXYwNlIcSUmcwY2DpsaDHr0XjVUeQHCuSunG3eQD7/5Wn/DvI7OMUE8NRRweK87yG8ebJ1xGOzmwpMsbZQA==",
652
652
  "cpu": [
653
653
  "arm64"
654
654
  ],
@@ -659,9 +659,9 @@
659
659
  ]
660
660
  },
661
661
  "node_modules/@rollup/rollup-darwin-x64": {
662
- "version": "4.65.0",
663
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.65.0.tgz",
664
- "integrity": "sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw==",
662
+ "version": "4.65.1",
663
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.65.1.tgz",
664
+ "integrity": "sha512-Zaed+R3cpkkA3aiNd7NO0GV5KFh/LVf8j6bGcoRdpoXkSm/mWH8M0oX2/3u/nyGcx/bHEovD9CzX+w9/gcpI4A==",
665
665
  "cpu": [
666
666
  "x64"
667
667
  ],
@@ -672,9 +672,9 @@
672
672
  ]
673
673
  },
674
674
  "node_modules/@rollup/rollup-freebsd-arm64": {
675
- "version": "4.65.0",
676
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.65.0.tgz",
677
- "integrity": "sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA==",
675
+ "version": "4.65.1",
676
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.65.1.tgz",
677
+ "integrity": "sha512-h12p3WJWhyJ9kir0zqAPr0rKNmBzOWIjoaOMT9OULs9KZQZCeOMKd+mGSvERjd9VxJYShd3zIxtrVxkYz6BEVA==",
678
678
  "cpu": [
679
679
  "arm64"
680
680
  ],
@@ -685,9 +685,9 @@
685
685
  ]
686
686
  },
687
687
  "node_modules/@rollup/rollup-freebsd-x64": {
688
- "version": "4.65.0",
689
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.65.0.tgz",
690
- "integrity": "sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ==",
688
+ "version": "4.65.1",
689
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.65.1.tgz",
690
+ "integrity": "sha512-yRBwh/5imKY4DydDulVtrjy8Jf7h7NQJUqZbgmZ3zFsCD9QqlnWvZ7RtEF+ecO1b+IukKjXlu5Bb9fehV3tIbg==",
691
691
  "cpu": [
692
692
  "x64"
693
693
  ],
@@ -698,9 +698,9 @@
698
698
  ]
699
699
  },
700
700
  "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
701
- "version": "4.65.0",
702
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.65.0.tgz",
703
- "integrity": "sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA==",
701
+ "version": "4.65.1",
702
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.65.1.tgz",
703
+ "integrity": "sha512-1XuPiWYYaLE1UDOqhbKVOfsqxvfmFS/lYPSk92U1hEX1qRfSYH0hNeAlbxFIgjp4d6+YQmL95EPRgdvjbZEddA==",
704
704
  "cpu": [
705
705
  "arm"
706
706
  ],
@@ -714,9 +714,9 @@
714
714
  ]
715
715
  },
716
716
  "node_modules/@rollup/rollup-linux-arm-musleabihf": {
717
- "version": "4.65.0",
718
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.65.0.tgz",
719
- "integrity": "sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q==",
717
+ "version": "4.65.1",
718
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.65.1.tgz",
719
+ "integrity": "sha512-Y3C5e55Yrf6p2r5rBcc4P9hFWVNi6oqWy/53I/AyCJJGlp3KRSpTP8eoUmVntchTbEnhgBgAgLO9flj9AvOdUw==",
720
720
  "cpu": [
721
721
  "arm"
722
722
  ],
@@ -730,9 +730,9 @@
730
730
  ]
731
731
  },
732
732
  "node_modules/@rollup/rollup-linux-arm64-gnu": {
733
- "version": "4.65.0",
734
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.65.0.tgz",
735
- "integrity": "sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA==",
733
+ "version": "4.65.1",
734
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.65.1.tgz",
735
+ "integrity": "sha512-Itj6coSswkYPIcVLczObXmwdplHNQQxyfnK1ppBg4czDaX8RiEzLKqU7EMc64BiBuiDZEUS+M93ktM18rv5llw==",
736
736
  "cpu": [
737
737
  "arm64"
738
738
  ],
@@ -746,9 +746,9 @@
746
746
  ]
747
747
  },
748
748
  "node_modules/@rollup/rollup-linux-arm64-musl": {
749
- "version": "4.65.0",
750
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.65.0.tgz",
751
- "integrity": "sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A==",
749
+ "version": "4.65.1",
750
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.65.1.tgz",
751
+ "integrity": "sha512-KeO8jTEqdrnhvcahufbimyMoI44vedRUp1bVz2QUnRX2vDm/hxzCPt5F8fERVD5fsHyTW/vXmCElT054VOSVOA==",
752
752
  "cpu": [
753
753
  "arm64"
754
754
  ],
@@ -762,9 +762,9 @@
762
762
  ]
763
763
  },
764
764
  "node_modules/@rollup/rollup-linux-loong64-gnu": {
765
- "version": "4.65.0",
766
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.65.0.tgz",
767
- "integrity": "sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw==",
765
+ "version": "4.65.1",
766
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.65.1.tgz",
767
+ "integrity": "sha512-dDOz5VZZ+ZGnglpmET6VJefamWXFsgaVtuhSClf2/URZBp048jprD6z1hddsIMN6n1w+oUzGZEBp9u6Mlszsfg==",
768
768
  "cpu": [
769
769
  "loong64"
770
770
  ],
@@ -778,9 +778,9 @@
778
778
  ]
779
779
  },
780
780
  "node_modules/@rollup/rollup-linux-loong64-musl": {
781
- "version": "4.65.0",
782
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.65.0.tgz",
783
- "integrity": "sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ==",
781
+ "version": "4.65.1",
782
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.65.1.tgz",
783
+ "integrity": "sha512-JbfRKz8nJCuo8gn1Dqx8lQWEHw4qhZ3uRaHphw5vrSjnSptJTKTRC/bLKHh8QQMsEnhL4uq+bdg3tkiXbJ2q/A==",
784
784
  "cpu": [
785
785
  "loong64"
786
786
  ],
@@ -794,9 +794,9 @@
794
794
  ]
795
795
  },
796
796
  "node_modules/@rollup/rollup-linux-ppc64-gnu": {
797
- "version": "4.65.0",
798
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.65.0.tgz",
799
- "integrity": "sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A==",
797
+ "version": "4.65.1",
798
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.65.1.tgz",
799
+ "integrity": "sha512-3oQpe1q0emSeCAssVHMQdOz5fz3PBH5my/Fr0ZekV2UMNL4ZiRa1ciikYlGQXyDkeqseYYv1InwOY8YPqY7RIQ==",
800
800
  "cpu": [
801
801
  "ppc64"
802
802
  ],
@@ -810,9 +810,9 @@
810
810
  ]
811
811
  },
812
812
  "node_modules/@rollup/rollup-linux-ppc64-musl": {
813
- "version": "4.65.0",
814
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.65.0.tgz",
815
- "integrity": "sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg==",
813
+ "version": "4.65.1",
814
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.65.1.tgz",
815
+ "integrity": "sha512-nNV3HHi23o1LtpGGwH+Bav4hqnc4+klQtC96tSvCfvql8bLlNFTnTFiGl/jai2nve3eEr/vT2ocuyf+h+eMnYQ==",
816
816
  "cpu": [
817
817
  "ppc64"
818
818
  ],
@@ -826,9 +826,9 @@
826
826
  ]
827
827
  },
828
828
  "node_modules/@rollup/rollup-linux-riscv64-gnu": {
829
- "version": "4.65.0",
830
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.65.0.tgz",
831
- "integrity": "sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w==",
829
+ "version": "4.65.1",
830
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.65.1.tgz",
831
+ "integrity": "sha512-b5zab0FryBxuocuDeqOsYGAg2EzjKw4mKA6LjQXCyewXjGv/BXqACBsY2cO45xM4hV2weYtdjGThP2VB2ByAYg==",
832
832
  "cpu": [
833
833
  "riscv64"
834
834
  ],
@@ -842,9 +842,9 @@
842
842
  ]
843
843
  },
844
844
  "node_modules/@rollup/rollup-linux-riscv64-musl": {
845
- "version": "4.65.0",
846
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.65.0.tgz",
847
- "integrity": "sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ==",
845
+ "version": "4.65.1",
846
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.65.1.tgz",
847
+ "integrity": "sha512-Rh08xZakiUxKNgI3hbgH9mivlx478lSqckS9s+Oc3/TX1VrAH3W8z/5wpJXR/v5VjqcJ/jJNVBg64PU1+iQsTQ==",
848
848
  "cpu": [
849
849
  "riscv64"
850
850
  ],
@@ -858,9 +858,9 @@
858
858
  ]
859
859
  },
860
860
  "node_modules/@rollup/rollup-linux-s390x-gnu": {
861
- "version": "4.65.0",
862
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.65.0.tgz",
863
- "integrity": "sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w==",
861
+ "version": "4.65.1",
862
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.65.1.tgz",
863
+ "integrity": "sha512-qDgtS6juN3/k97y4kcD6XLFpXEo4f6CnG8lTOGYwlhrUYTrMyhN0WHdZCPAW5rqS0MqQG7qI7dxnjugt6QB39Q==",
864
864
  "cpu": [
865
865
  "s390x"
866
866
  ],
@@ -874,9 +874,9 @@
874
874
  ]
875
875
  },
876
876
  "node_modules/@rollup/rollup-linux-x64-gnu": {
877
- "version": "4.65.0",
878
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.65.0.tgz",
879
- "integrity": "sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ==",
877
+ "version": "4.65.1",
878
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.65.1.tgz",
879
+ "integrity": "sha512-44IKzBPqXXVm0kt1O+F5eW4juR5FyIxGNlFkpP8qwIDzucBsjM1HCbdYUQrQw8rU/g6HRl/tfBm0+Qq4H4Ro3w==",
880
880
  "cpu": [
881
881
  "x64"
882
882
  ],
@@ -890,9 +890,9 @@
890
890
  ]
891
891
  },
892
892
  "node_modules/@rollup/rollup-linux-x64-musl": {
893
- "version": "4.65.0",
894
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.65.0.tgz",
895
- "integrity": "sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg==",
893
+ "version": "4.65.1",
894
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.65.1.tgz",
895
+ "integrity": "sha512-tvYWZ/h2PCHs558Xu7B6LptuRCV8lXV2xI75ONMcldII1m2zlFwuI4BJZi9M6rSMkYqcPLLtPUPrjyqfbzLOdg==",
896
896
  "cpu": [
897
897
  "x64"
898
898
  ],
@@ -906,9 +906,9 @@
906
906
  ]
907
907
  },
908
908
  "node_modules/@rollup/rollup-openbsd-x64": {
909
- "version": "4.65.0",
910
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.65.0.tgz",
911
- "integrity": "sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w==",
909
+ "version": "4.65.1",
910
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.65.1.tgz",
911
+ "integrity": "sha512-gdyeucgZCnei3pXwadKVTEAOgRx0B+c847DUPR/PdOMmp3olhfkYo6r0J4PzRFZB5ZICZQTxntyvgmWH5xT7rg==",
912
912
  "cpu": [
913
913
  "x64"
914
914
  ],
@@ -919,9 +919,9 @@
919
919
  ]
920
920
  },
921
921
  "node_modules/@rollup/rollup-openharmony-arm64": {
922
- "version": "4.65.0",
923
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.65.0.tgz",
924
- "integrity": "sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw==",
922
+ "version": "4.65.1",
923
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.65.1.tgz",
924
+ "integrity": "sha512-Wf/NEKdn2SAL4Nvt8HyDtdkseWTwiLbZEOoNYnEaHkbso0B9eHO42vKnZh+OYsn+IMhAMcWcuIwPlBOgOXsUzg==",
925
925
  "cpu": [
926
926
  "arm64"
927
927
  ],
@@ -932,9 +932,9 @@
932
932
  ]
933
933
  },
934
934
  "node_modules/@rollup/rollup-win32-arm64-msvc": {
935
- "version": "4.65.0",
936
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.65.0.tgz",
937
- "integrity": "sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ==",
935
+ "version": "4.65.1",
936
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.65.1.tgz",
937
+ "integrity": "sha512-x9VSsDmsMsEWy4cr/mFeaEsXnVJ6Kc4NCAU/lCnwml3s7LQbZoa9+/qwc9etWHbuuDT5mYxdyyKUwo76njtjZw==",
938
938
  "cpu": [
939
939
  "arm64"
940
940
  ],
@@ -945,9 +945,9 @@
945
945
  ]
946
946
  },
947
947
  "node_modules/@rollup/rollup-win32-ia32-msvc": {
948
- "version": "4.65.0",
949
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.65.0.tgz",
950
- "integrity": "sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ==",
948
+ "version": "4.65.1",
949
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.65.1.tgz",
950
+ "integrity": "sha512-RIDbiVvvLkN45mOFpbo6eYMgnuiBEB62BGR/0s4QJgGg2AvVxucDDNn67kpAcUoYyfmY8iX8/RifMcmxmmnejA==",
951
951
  "cpu": [
952
952
  "ia32"
953
953
  ],
@@ -958,9 +958,9 @@
958
958
  ]
959
959
  },
960
960
  "node_modules/@rollup/rollup-win32-x64-gnu": {
961
- "version": "4.65.0",
962
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.65.0.tgz",
963
- "integrity": "sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w==",
961
+ "version": "4.65.1",
962
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.65.1.tgz",
963
+ "integrity": "sha512-+KN8HIr88vN59JNxlrM2fMimzY43Y0lQB7+0xdVsFbvFCAA74CLKCdfzIHzzvndJryBc94Pm+MvwtukKjaS2ug==",
964
964
  "cpu": [
965
965
  "x64"
966
966
  ],
@@ -971,9 +971,9 @@
971
971
  ]
972
972
  },
973
973
  "node_modules/@rollup/rollup-win32-x64-msvc": {
974
- "version": "4.65.0",
975
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.65.0.tgz",
976
- "integrity": "sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ==",
974
+ "version": "4.65.1",
975
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.65.1.tgz",
976
+ "integrity": "sha512-+Y1Sd49cSDq60UOl8/U2HI0ebUvnSl/hDhMHQn+ouw17yb8fYwMWIazzJ4JFpSkjPE7sj5NTNSX9IoyEqcFfyQ==",
977
977
  "cpu": [
978
978
  "x64"
979
979
  ],
@@ -1496,24 +1496,6 @@
1496
1496
  "util": "^0.12.5"
1497
1497
  }
1498
1498
  },
1499
- "node_modules/async-function": {
1500
- "version": "1.0.0",
1501
- "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
1502
- "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
1503
- "license": "MIT",
1504
- "engines": {
1505
- "node": ">= 0.4"
1506
- }
1507
- },
1508
- "node_modules/async-generator-function": {
1509
- "version": "1.0.0",
1510
- "resolved": "https://registry.npmjs.org/async-generator-function/-/async-generator-function-1.0.0.tgz",
1511
- "integrity": "sha512-+NAXNqgCrB95ya4Sr66i1CL2hqLVckAk7xwRYWdcm39/ELQ6YNn1aw5r0bdQtqNZgQpEWzc5yc/igXc7aL5SLA==",
1512
- "license": "MIT",
1513
- "engines": {
1514
- "node": ">= 0.4"
1515
- }
1516
- },
1517
1499
  "node_modules/async-retry": {
1518
1500
  "version": "1.3.3",
1519
1501
  "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
@@ -2008,19 +1990,16 @@
2008
1990
  }
2009
1991
  },
2010
1992
  "node_modules/get-intrinsic": {
2011
- "version": "1.3.1",
2012
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.1.tgz",
2013
- "integrity": "sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==",
1993
+ "version": "1.3.0",
1994
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1995
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
2014
1996
  "license": "MIT",
2015
1997
  "dependencies": {
2016
- "async-function": "^1.0.0",
2017
- "async-generator-function": "^1.0.0",
2018
1998
  "call-bind-apply-helpers": "^1.0.2",
2019
1999
  "es-define-property": "^1.0.1",
2020
2000
  "es-errors": "^1.3.0",
2021
2001
  "es-object-atoms": "^1.1.1",
2022
2002
  "function-bind": "^1.1.2",
2023
- "generator-function": "^2.0.0",
2024
2003
  "get-proto": "^1.0.1",
2025
2004
  "gopd": "^1.2.0",
2026
2005
  "has-symbols": "^1.1.0",
@@ -2637,9 +2616,9 @@
2637
2616
  }
2638
2617
  },
2639
2618
  "node_modules/rollup": {
2640
- "version": "4.65.0",
2641
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.65.0.tgz",
2642
- "integrity": "sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw==",
2619
+ "version": "4.65.1",
2620
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.65.1.tgz",
2621
+ "integrity": "sha512-HzD4IdphaPMvuAacnL/vtoq5B8FwqewKvxTtqYibWvP6VDyZ+UsTtlw5F8HIOIIpLQAkN2NOP/0sM3puo9xN/A==",
2643
2622
  "license": "MIT",
2644
2623
  "dependencies": {
2645
2624
  "@types/estree": "1.0.9"
@@ -2653,31 +2632,31 @@
2653
2632
  },
2654
2633
  "optionalDependencies": {
2655
2634
  "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
2656
- "@rollup/rollup-android-arm-eabi": "4.65.0",
2657
- "@rollup/rollup-android-arm64": "4.65.0",
2658
- "@rollup/rollup-darwin-arm64": "4.65.0",
2659
- "@rollup/rollup-darwin-x64": "4.65.0",
2660
- "@rollup/rollup-freebsd-arm64": "4.65.0",
2661
- "@rollup/rollup-freebsd-x64": "4.65.0",
2662
- "@rollup/rollup-linux-arm-gnueabihf": "4.65.0",
2663
- "@rollup/rollup-linux-arm-musleabihf": "4.65.0",
2664
- "@rollup/rollup-linux-arm64-gnu": "4.65.0",
2665
- "@rollup/rollup-linux-arm64-musl": "4.65.0",
2666
- "@rollup/rollup-linux-loong64-gnu": "4.65.0",
2667
- "@rollup/rollup-linux-loong64-musl": "4.65.0",
2668
- "@rollup/rollup-linux-ppc64-gnu": "4.65.0",
2669
- "@rollup/rollup-linux-ppc64-musl": "4.65.0",
2670
- "@rollup/rollup-linux-riscv64-gnu": "4.65.0",
2671
- "@rollup/rollup-linux-riscv64-musl": "4.65.0",
2672
- "@rollup/rollup-linux-s390x-gnu": "4.65.0",
2673
- "@rollup/rollup-linux-x64-gnu": "4.65.0",
2674
- "@rollup/rollup-linux-x64-musl": "4.65.0",
2675
- "@rollup/rollup-openbsd-x64": "4.65.0",
2676
- "@rollup/rollup-openharmony-arm64": "4.65.0",
2677
- "@rollup/rollup-win32-arm64-msvc": "4.65.0",
2678
- "@rollup/rollup-win32-ia32-msvc": "4.65.0",
2679
- "@rollup/rollup-win32-x64-gnu": "4.65.0",
2680
- "@rollup/rollup-win32-x64-msvc": "4.65.0",
2635
+ "@rollup/rollup-android-arm-eabi": "4.65.1",
2636
+ "@rollup/rollup-android-arm64": "4.65.1",
2637
+ "@rollup/rollup-darwin-arm64": "4.65.1",
2638
+ "@rollup/rollup-darwin-x64": "4.65.1",
2639
+ "@rollup/rollup-freebsd-arm64": "4.65.1",
2640
+ "@rollup/rollup-freebsd-x64": "4.65.1",
2641
+ "@rollup/rollup-linux-arm-gnueabihf": "4.65.1",
2642
+ "@rollup/rollup-linux-arm-musleabihf": "4.65.1",
2643
+ "@rollup/rollup-linux-arm64-gnu": "4.65.1",
2644
+ "@rollup/rollup-linux-arm64-musl": "4.65.1",
2645
+ "@rollup/rollup-linux-loong64-gnu": "4.65.1",
2646
+ "@rollup/rollup-linux-loong64-musl": "4.65.1",
2647
+ "@rollup/rollup-linux-ppc64-gnu": "4.65.1",
2648
+ "@rollup/rollup-linux-ppc64-musl": "4.65.1",
2649
+ "@rollup/rollup-linux-riscv64-gnu": "4.65.1",
2650
+ "@rollup/rollup-linux-riscv64-musl": "4.65.1",
2651
+ "@rollup/rollup-linux-s390x-gnu": "4.65.1",
2652
+ "@rollup/rollup-linux-x64-gnu": "4.65.1",
2653
+ "@rollup/rollup-linux-x64-musl": "4.65.1",
2654
+ "@rollup/rollup-openbsd-x64": "4.65.1",
2655
+ "@rollup/rollup-openharmony-arm64": "4.65.1",
2656
+ "@rollup/rollup-win32-arm64-msvc": "4.65.1",
2657
+ "@rollup/rollup-win32-ia32-msvc": "4.65.1",
2658
+ "@rollup/rollup-win32-x64-gnu": "4.65.1",
2659
+ "@rollup/rollup-win32-x64-msvc": "4.65.1",
2681
2660
  "fsevents": "~2.3.2"
2682
2661
  }
2683
2662
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mb-run",
3
- "version": "1.1.4-dev-20261026-e0e9bae",
3
+ "version": "1.1.4",
4
4
  "description": "Matterbridge command line executor",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",
@@ -71,7 +71,7 @@
71
71
  "esbuild": "0.28.2",
72
72
  "javascript-obfuscator": "5.9.0",
73
73
  "npm-check-updates": "23.1.1",
74
- "rollup": "4.65.0",
74
+ "rollup": "4.65.1",
75
75
  "rollup-plugin-dts": "6.5.1"
76
76
  }
77
77
  }
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: 'How a plugin serves its own frontend SPA and custom REST API via onFetch v.1.0.1'
2
+ description: 'How a plugin serves its own frontend SPA and custom REST API via onFetch v.1.0.2'
3
3
  ---
4
4
 
5
5
  # Matterbridge Plugin Frontend Guide
@@ -16,7 +16,7 @@ When a plugin package contains a built SPA at `apps/frontend/build/index.html`,
16
16
  - `/plugins/<pluginName>/api/:path` exposes the plugin REST namespace backed by `onFetch`, with JSON body parsing included.
17
17
  - `/plugins/<pluginName>/{*splat}` serves the plugin's `index.html` as the SPA fallback for unmatched routes.
18
18
 
19
- A plugin frontend must call its own `/plugins/<pluginName>/api/...` namespace rather than the core `/api/...` endpoints.
19
+ A plugin frontend must call its own plugin API namespace (built from `import.meta.env.BASE_URL`, see below) rather than the core `/api/...` endpoints.
20
20
 
21
21
  ## Preserve the base path through Home Assistant Ingress
22
22
 
@@ -37,7 +37,19 @@ The frontend template's `vite.config.ts` uses `/plugins/<pluginName>/` as its de
37
37
 
38
38
  Do not hardcode `fetch('/plugins/<pluginName>/api/events')`, a core `/api/...` URL or the Matterbridge host/port in frontend code. A root-relative URL discards the Ingress prefix and sends the request outside the addon route. The absolute plugin paths listed above describe server routes, not URLs to hardcode in browser requests.
39
39
 
40
- Open the plugin frontend at its directory URL with a trailing slash, as Matterbridge does. Relative production URLs resolve against the document URL; if adding nested history-based SPA routes, ensure requests still resolve from the plugin root. Verify API requests during Vite development, direct Matterbridge access and Home Assistant Ingress access.
40
+ ## Open the plugin frontend with a trailing slash
41
+
42
+ Matterbridge always opens a plugin frontend at its directory URL with a trailing slash (`<basePath>plugins/<pluginName>/`). This is required, not cosmetic:
43
+
44
+ - **Relative URLs resolve against the document's directory.** The browser drops everything after the last `/` of the page URL. At `/plugins/my-plugin` the directory is `/plugins/`, so `./assets/index.js` becomes `/plugins/assets/index.js` and `./api/events` becomes `/plugins/api/events`. Both miss the plugin's routes, and the SPA does not even load its scripts and styles. At `/plugins/my-plugin/` they resolve to `/plugins/my-plugin/assets/index.js` and `/plugins/my-plugin/api/events`.
45
+ - **The server redirect breaks under Ingress.** When a directory is requested without the slash, `express.static` answers with a 301 to the same path plus `/`. Behind Home Assistant Ingress the proxy has already stripped `/api/hassio_ingress/<token>`. The redirect therefore points to `/plugins/<pluginName>/` at the Home Assistant origin root, outside the add-on. Adding the slash in the client avoids the redirect.
46
+ - **The slash goes on the path, before any query or hash.** The right form is `plugins/my-plugin/?device=1`; `plugins/my-plugin?device=1/` is wrong.
47
+
48
+ A device `configUrl` that starts with `/plugins/<pluginName>` or `./plugins/<pluginName>` opens the plugin frontend in the Matterbridge dialog. Matterbridge prefixes the base path and inserts the trailing slash before any query string. Prefer writing it with the slash anyway (`./plugins/<pluginName>/?device=<id>`) so the URL is also correct when opened directly.
49
+
50
+ Avoid nested history-based routes (for example `/plugins/<pluginName>/devices/42`). They change the document directory, and since the production `BASE_URL` is `./`, assets and API calls would resolve from the nested path. Use hash-based routing (`/plugins/<pluginName>/#/devices/42`) instead, which keeps the document at the plugin root.
51
+
52
+ Verify API requests during Vite development, direct Matterbridge access and Home Assistant Ingress access.
41
53
 
42
54
  ## Use `onFetch` for a plugin's custom API
43
55
 
@@ -65,4 +77,4 @@ The base implementation only logs the request and returns `undefined`, so overri
65
77
  ## Avoid unsupported routing patterns
66
78
 
67
79
  - Do not invent custom WebSocket method names for plugin frontend traffic. The WebSocket dispatch list is fixed; use `onFetch` through `/plugins/<pluginName>/api/...`.
68
- - Do not call core `/api/...` routes from a plugin's custom frontend. Use the plugin's own `/plugins/<pluginName>/api/...` namespace.
80
+ - Do not call core `/api/...` routes from a plugin's custom frontend. Use the plugin's own namespace via `${import.meta.env.BASE_URL}api/...`.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: 'How a plugin serves its own frontend SPA and custom REST API via onFetch v.1.0.1'
2
+ description: 'How a plugin serves its own frontend SPA and custom REST API via onFetch v.1.0.2'
3
3
  paths:
4
4
  - 'apps/frontend/**'
5
5
  - 'src/*.ts'
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: 'Matterbridge Plugin Frontend Guide v.1.0.1'
2
+ name: 'Matterbridge Plugin Frontend Guide v.1.0.2'
3
3
  description: 'How a plugin serves its own frontend SPA and custom REST API via onFetch'
4
4
  applyTo: 'apps/frontend/**, src/*.ts'
5
5
  ---
@@ -11,6 +11,10 @@
11
11
  "build": "vite build",
12
12
  "preview": "vite preview",
13
13
  "typecheck": "tsc --project tsconfig.json --noEmit",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest watch",
16
+ "test:verbose": "vitest run --reporter verbose",
17
+ "test:coverage": "vitest run --coverage --coverage.thresholds.statements=100 --coverage.thresholds.branches=100 --coverage.thresholds.lines=100 --coverage.thresholds.functions=100",
14
18
  "lint": "oxlint",
15
19
  "lint:fix": "oxlint --fix",
16
20
  "format": "oxfmt",
@@ -35,6 +39,6 @@
35
39
  "@types/react": "19.3.0",
36
40
  "@types/react-dom": "19.3.0",
37
41
  "@vitejs/plugin-react": "6.1.2",
38
- "vite": "8.3.4"
42
+ "vite": "8.3.5"
39
43
  }
40
44
  }
@@ -1,27 +1,18 @@
1
- // tsconfig.json 1.0.0
1
+ // apps/frontend/tsconfig.json v.1.0.0
2
2
 
3
- // This TypeScript configuration is designed for a Vite/React/TypeScript frontend using ESM modules.
3
+ // This TypeScript configuration is designed for a Vite / React / TypeScript frontend.
4
4
  {
5
5
  "compilerOptions": {
6
6
  "target": "ESNext",
7
- "lib": ["ESNext", "DOM", "DOM.Iterable"],
8
7
  "module": "ESNext",
9
- "moduleResolution": "bundler",
8
+ "types": ["vite/client", "vitest/globals"],
10
9
  "jsx": "react-jsx",
11
- "useDefineForClassFields": true,
12
- "resolveJsonModule": true,
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "isolatedModules": true,
16
- "noEmit": true,
17
10
  "strict": true,
18
- "noImplicitOverride": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "noFallthroughCasesInSwitch": true,
22
- "forceConsistentCasingInFileNames": true,
11
+ "moduleResolution": "bundler",
12
+ "esModuleInterop": true,
13
+ "allowImportingTsExtensions": true,
23
14
  "skipLibCheck": true,
24
- "types": ["vite/client"]
15
+ "noEmit": true
25
16
  },
26
- "include": ["src"]
17
+ "include": ["src/**/*.ts", "src/**/*.tsx", "vitest/**/*.ts", "vitest/**/*.tsx", "vite.config.ts", "vitest.config.ts"]
27
18
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file apps/frontend/vitest/theme.test.ts
3
+ * @description Tests for the frontend application theme.
4
+ * @author Luligu
5
+ */
6
+
7
+ import { describe, expect, it } from 'vitest';
8
+
9
+ import { createAppTheme } from '../src/theme.js';
10
+
11
+ describe('createAppTheme', () => {
12
+ it('should create a light theme when the mode is light', () => {
13
+ const theme = createAppTheme('light');
14
+
15
+ expect(theme.palette.mode).toBe('light');
16
+ expect(theme.palette.primary.main).toBe('#1976d2');
17
+ expect(theme.palette.background.default).toBe('#fff');
18
+ expect(theme.palette.background.paper).toBe('#fff');
19
+ });
20
+
21
+ it('should create a dark theme when the mode is dark', () => {
22
+ const theme = createAppTheme('dark');
23
+
24
+ expect(theme.palette.mode).toBe('dark');
25
+ expect(theme.palette.primary.main).toBe('#1976d2');
26
+ expect(theme.palette.background.default).toBe('#121212');
27
+ expect(theme.palette.background.paper).toBe('#121212');
28
+ });
29
+ });
@@ -0,0 +1,33 @@
1
+ // apps/frontend/vitest.config.ts v.2.0.8
2
+
3
+ // Vitest configuration file for a Vite / React / TypeScript frontend.
4
+
5
+ import react from '@vitejs/plugin-react';
6
+ import { defineConfig } from 'vitest/config';
7
+
8
+ export default defineConfig({
9
+ envDir: false,
10
+ plugins: react(),
11
+ test: {
12
+ environment: 'jsdom',
13
+ globals: true,
14
+ // Disable Node's experimental built-in webstorage so the jsdom `localStorage` is used.
15
+ // It is a Node bootstrap flag, so it must be passed to the pool worker processes (where
16
+ // the tests run) via execArgv rather than set from within this config at runtime.
17
+ execArgv: ['--no-experimental-webstorage'],
18
+ // Every run also writes a JUnit XML report with the duration of each test (handy to find slow tests).
19
+ // A --reporter passed on the command line replaces this list.
20
+ reporters: ['default', 'junit'],
21
+ outputFile: { junit: '.cache/vitest/junit.xml' },
22
+ coverage: {
23
+ provider: 'v8',
24
+ reporter: ['lcov', 'text', 'json'],
25
+ thresholds: {
26
+ lines: 100,
27
+ functions: 100,
28
+ branches: 100,
29
+ statements: 100,
30
+ },
31
+ },
32
+ },
33
+ });
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * downloads.mjs
3
- * Version: 2.0.0
3
+ * Version: 2.1.0
4
4
  *
5
- * Prints daily npm downloads for the last month for the package in ../package.json.
5
+ * Prints daily npm downloads for the last 30 days and today for the package in ../package.json.
6
+ * It queries an explicit date range because the npm `last-week` / `last-month` periods can return stale data.
6
7
  *
7
8
  * Usage:
8
9
  * node scripts/downloads.mjs --version, -v Show the script version
@@ -23,7 +24,13 @@ import { readFile } from 'node:fs/promises';
23
24
  import path from 'node:path';
24
25
  import { fileURLToPath } from 'node:url';
25
26
 
26
- const scriptVersion = '2.0.0';
27
+ const scriptVersion = '2.1.0';
28
+
29
+ /** Number of days before today to include in the range. */
30
+ const rangeDays = 30;
31
+
32
+ /** Maximum number of trailing zero days treated as not yet published by npm. */
33
+ const maxPendingDays = 2;
27
34
 
28
35
  /**
29
36
  * @typedef {{ day: string, downloads: number }} DownloadRow
@@ -33,10 +40,6 @@ const scriptVersion = '2.0.0';
33
40
  * @typedef {{ start: string, end: string, package: string, downloads: DownloadRow[] }} NpmDownloadsRangeResponse
34
41
  */
35
42
 
36
- /**
37
- * @typedef {{ start: string, end: string, package: string, downloads: number }} NpmDownloadsPointResponse
38
- */
39
-
40
43
  /**
41
44
  * Builds the help text.
42
45
  *
@@ -46,7 +49,7 @@ function usage() {
46
49
  return [
47
50
  'Usage: node scripts/downloads.mjs [--dry-run|-n]',
48
51
  '',
49
- 'Print the daily npm downloads of the last month for the package in ../package.json.',
52
+ 'Print the daily npm downloads of the last 30 days and today for the package in ../package.json.',
50
53
  '',
51
54
  ' --dry-run, -n Print the package name without querying the npm registry',
52
55
  ' --version, -v Show the script version',
@@ -66,38 +69,25 @@ async function readJson(path) {
66
69
  }
67
70
 
68
71
  /**
69
- * Fetch npm daily download stats for the last month.
72
+ * Format a date as YYYY-MM-DD (UTC).
70
73
  *
71
- * @param {string} pkgName - NPM package name.
72
- * @returns {Promise<NpmDownloadsRangeResponse>} Range response.
74
+ * @param {Date} date - Date to format.
75
+ * @returns {string} The day key.
73
76
  */
74
- async function fetchLastMonthDownloads(pkgName) {
75
- const url = `https://api.npmjs.org/downloads/range/last-month/${encodeURIComponent(pkgName)}`;
76
- const res = await fetch(url, {
77
- headers: { accept: 'application/json' },
78
- });
79
-
80
- if (!res.ok) {
81
- const text = await res.text().catch(() => '');
82
- throw new Error(`HTTP ${res.status} fetching ${url}\n${text}`);
83
- }
84
-
85
- const data = /** @type {NpmDownloadsRangeResponse} */ (await res.json());
86
- if (!data || !Array.isArray(data.downloads)) {
87
- throw new Error(`Unexpected response shape from ${url}`);
88
- }
89
- return data;
77
+ function dayKey(date) {
78
+ return date.toISOString().slice(0, 10);
90
79
  }
91
80
 
92
81
  /**
93
- * Fetch npm download stats for the current day.
82
+ * Fetch npm daily download stats for an explicit date range.
94
83
  *
95
84
  * @param {string} pkgName - NPM package name.
96
- * @returns {Promise<NpmDownloadsPointResponse>} Point response.
85
+ * @param {string} start - First day (YYYY-MM-DD).
86
+ * @param {string} end - Last day (YYYY-MM-DD).
87
+ * @returns {Promise<NpmDownloadsRangeResponse>} Range response.
97
88
  */
98
- async function fetchCurrentDayDownloads(pkgName) {
99
- const today = new Date().toISOString().slice(0, 10);
100
- const url = `https://api.npmjs.org/downloads/point/${today}:${today}/${encodeURIComponent(pkgName)}`;
89
+ async function fetchRangeDownloads(pkgName, start, end) {
90
+ const url = `https://api.npmjs.org/downloads/range/${start}:${end}/${encodeURIComponent(pkgName)}`;
101
91
  const res = await fetch(url, {
102
92
  headers: { accept: 'application/json' },
103
93
  });
@@ -107,8 +97,8 @@ async function fetchCurrentDayDownloads(pkgName) {
107
97
  throw new Error(`HTTP ${res.status} fetching ${url}\n${text}`);
108
98
  }
109
99
 
110
- const data = /** @type {NpmDownloadsPointResponse} */ (await res.json());
111
- if (!data || typeof data.downloads !== 'number') {
100
+ const data = /** @type {NpmDownloadsRangeResponse} */ (await res.json());
101
+ if (!data || !Array.isArray(data.downloads)) {
112
102
  throw new Error(`Unexpected response shape from ${url}`);
113
103
  }
114
104
  return data;
@@ -247,17 +237,21 @@ async function printDownloads(pkgPath, dryRun) {
247
237
  return;
248
238
  }
249
239
 
250
- const [lastMonthData, currentDayData] = await Promise.all([fetchLastMonthDownloads(name), fetchCurrentDayDownloads(name)]);
240
+ const today = new Date();
241
+ const end = dayKey(today);
242
+ const start = dayKey(new Date(today.getTime() - rangeDays * 24 * 60 * 60 * 1000));
243
+ const rangeData = await fetchRangeDownloads(name, start, end);
251
244
 
252
- const rows = lastMonthData.downloads.map((r) => ({ day: String(r.day), downloads: Number(r.downloads) || 0 }));
253
- const currentDayRow = { day: String(currentDayData.end), downloads: Number(currentDayData.downloads) || 0 };
254
- const currentDayPending = currentDayRow.day > lastMonthData.end && currentDayRow.downloads === 0;
255
- if (!rows.some((r) => r.day === currentDayRow.day)) {
256
- rows.push(currentDayRow);
257
- }
245
+ const rows = rangeData.downloads.map((r) => ({ day: String(r.day), downloads: Number(r.downloads) || 0 }));
258
246
  rows.sort((a, b) => a.day.localeCompare(b.day));
259
247
 
260
- const summaryRows = currentDayPending ? rows.filter((r) => r.day !== currentDayRow.day) : rows;
248
+ // npm reports 0 for the latest days until it publishes their counts: mark the trailing zero days as pending.
249
+ const pendingDays = new Set();
250
+ for (let i = rows.length - 1; i >= 0 && pendingDays.size < maxPendingDays && rows[i].downloads === 0; i -= 1) {
251
+ pendingDays.add(rows[i].day);
252
+ }
253
+
254
+ const summaryRows = rows.filter((r) => !pendingDays.has(r.day));
261
255
  const total = sumDownloads(summaryRows);
262
256
  const days = summaryRows.length || 1;
263
257
  const avg = Math.round(total / days);
@@ -271,11 +265,11 @@ async function printDownloads(pkgPath, dryRun) {
271
265
  const dayWidth = Math.max(3, ...rows.map((r) => r.day.length));
272
266
  const downloadStrings = rows.map((r) => formatNumber(r.downloads));
273
267
  const downloadsWidth = Math.max('DOWNLOADS'.length, ...downloadStrings.map((s) => s.length));
274
- const rangeStart = rows[0]?.day ?? lastMonthData.start;
275
- const rangeEnd = rows[rows.length - 1]?.day ?? currentDayData.end;
268
+ const rangeStart = rows[0]?.day ?? rangeData.start;
269
+ const rangeEnd = rows[rows.length - 1]?.day ?? rangeData.end;
276
270
 
277
- console.log(style.bold(style.cyan('npm downloads (last-month + today)')));
278
- console.log(`${style.dim('Package:')} ${lastMonthData.package}`);
271
+ console.log(style.bold(style.cyan(`npm downloads (last ${rangeDays} days + today)`)));
272
+ console.log(`${style.dim('Package:')} ${rangeData.package}`);
279
273
  console.log(`${style.dim('Range: ')} ${rangeStart} .. ${rangeEnd}`);
280
274
  console.log('');
281
275
 
@@ -285,14 +279,14 @@ async function printDownloads(pkgPath, dryRun) {
285
279
  for (let i = 0; i < rows.length; i += 1) {
286
280
  const r = rows[i];
287
281
  const downloads = downloadStrings[i];
288
- const renderedDownloads = currentDayPending && r.day === currentDayRow.day ? style.dim(padLeft('pending', downloadsWidth)) : padLeft(downloads, downloadsWidth);
282
+ const renderedDownloads = pendingDays.has(r.day) ? style.dim(padLeft('pending', downloadsWidth)) : padLeft(downloads, downloadsWidth);
289
283
  console.log(`${padRight(r.day, dayWidth)} ${renderedDownloads}`);
290
284
  }
291
285
 
292
286
  console.log('');
293
287
  console.log(style.dim('Summary'));
294
- if (currentDayPending) {
295
- console.log(style.dim("Today: npm has not published today's count yet"));
288
+ if (pendingDays.size > 0) {
289
+ console.log(style.dim(`Pending: npm has not published the count of ${[...pendingDays].sort().join(', ')} yet`));
296
290
  }
297
291
  console.log(`${style.dim('Total: ')} ${style.bold(style.green(formatNumber(total)))}`);
298
292
  console.log(`${style.dim('Avg/day: ')} ${style.yellow(formatNumber(avg))}`);