From 26a7a84669262aaa28d3dc4c35fb1acc0864b3db Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 01:28:23 +0100 Subject: [PATCH 01/10] feat(build): configure Vite for ESM-only build and update dependencies --- examples/simple-marker/index.html | 37 ++-- package-lock.json | 285 +++++++++++++++--------------- package.json | 22 ++- src/worker/worker.js | 55 +++--- vite.config.ts | 29 +++ 5 files changed, 234 insertions(+), 194 deletions(-) create mode 100644 vite.config.ts diff --git a/examples/simple-marker/index.html b/examples/simple-marker/index.html index 51c4f46..16eb344 100644 --- a/examples/simple-marker/index.html +++ b/examples/simple-marker/index.html @@ -31,8 +31,9 @@

Event Log:

+ diff --git a/package-lock.json b/package-lock.json index e781d0c..2b05c46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,9 @@ "@ar-js-org/artoolkit5-js": "^0.3.2" }, "devDependencies": { - "eslint": "^9.39.0", + "eslint": "^9.39.1", "husky": "^9.1.7", + "vite": "^7.1.12", "vitest": "^4.0.6" } }, @@ -37,9 +38,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -54,9 +55,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -71,9 +72,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -88,9 +89,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -105,9 +106,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -122,9 +123,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -139,9 +140,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -156,9 +157,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -173,9 +174,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -190,9 +191,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -207,9 +208,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -224,9 +225,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -241,9 +242,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -258,9 +259,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -275,9 +276,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -292,9 +293,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -309,9 +310,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -326,9 +327,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -343,9 +344,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -360,9 +361,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -377,9 +378,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -394,9 +395,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "cpu": [ "arm64" ], @@ -411,9 +412,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -428,9 +429,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -445,9 +446,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -462,9 +463,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -586,9 +587,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz", - "integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { @@ -1046,33 +1047,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/mocker": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz", - "integrity": "sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.6", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.19" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, "node_modules/@vitest/pretty-format": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.6.tgz", @@ -1452,9 +1426,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1465,32 +1439,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escape-string-regexp": { @@ -1507,9 +1481,9 @@ } }, "node_modules/eslint": { - "version": "9.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", - "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", "dependencies": { @@ -1519,7 +1493,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.0", + "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2718,6 +2692,33 @@ } } }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz", + "integrity": "sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.19" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 41ded10..437d39d 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,20 @@ "name": "@ar-js-org/arjs-plugin-artoolkit", "version": "0.1.0", "description": "ARToolKit detection plugin for AR.js core (ECS plugin)", - "main": "dist/index.js", - "module": "src/index.js", "type": "module", + "main": "dist/arjs-plugin-artoolkit.esm.js", + "module": "dist/arjs-plugin-artoolkit.esm.js", + "exports": { + ".": { + "import": "./dist/arjs-plugin-artoolkit.esm.js", + "require": "./dist/arjs-plugin-artoolkit.esm.js" + } + }, + "files": [ + "dist" + ], "scripts": { - "build": "echo \"build step (none yet)\"", + "build": "vite build", "test": "vitest", "lint": "eslint . --ext .js", "prepare": "husky install" @@ -20,11 +29,12 @@ "author": "AR.js contributors", "license": "MIT", "devDependencies": { - "eslint": "^9.39.0", + "eslint": "^9.39.1", "husky": "^9.1.7", - "vitest": "^4.0.6" + "vitest": "^4.0.6", + "vite": "^7.1.12" }, "dependencies": { "@ar-js-org/artoolkit5-js": "^0.3.2" } -} +} \ No newline at end of file diff --git a/src/worker/worker.js b/src/worker/worker.js index 8ea9f27..44f0927 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -1,9 +1,6 @@ // Cross-platform worker integrating ARToolKit in browser Workers. // - Browser: processes ImageBitmap → OffscreenCanvas → ARToolKit.process(canvas) -// - Node: keeps stub behavior if needed -let isNodeWorker = false; -let parent = null; - +// Note: Node path removed for now to keep browser worker startup robust. let arController = null; let arControllerInitialized = false; let getMarkerForwarderAttached = false; @@ -33,27 +30,16 @@ let INIT_OPTS = { minConfidence: null }; -if (typeof self === 'undefined') { - try { - const wt = await import('node:worker_threads').catch(() => null); - if (wt && wt.parentPort) { - isNodeWorker = true; - parent = wt.parentPort; - } - } catch { - isNodeWorker = false; - parent = null; - } -} +// Announce-ready guard +let hasAnnouncedReady = false; function onMessage(fn) { - if (isNodeWorker) parent.on('message', (msg) => fn(msg)); - else self.addEventListener('message', (ev) => fn(ev.data)); + // Browser worker path + self.addEventListener('message', (ev) => fn(ev.data)); } function sendMessage(msg) { - if (isNodeWorker) parent.postMessage(msg); - else self.postMessage(msg); + self.postMessage(msg); } // Serialize AR.js-style getMarker event into a transferable payload @@ -149,6 +135,15 @@ async function initArtoolkit(width = 640, height = 480) { return await import('@ar-js-org/artoolkit5-js'); })(); + // Safely extract exports (supports both named and default exports) + /*const ARController = + jsartoolkit.ARController ?? jsartoolkit.default?.ARController; + const ARToolkit = + jsartoolkit.ARToolkit ?? jsartoolkit.default?.ARToolkit;*/ + + if (!ARController) { + throw new Error('ARController export not found in ARToolKit module'); + } // Read the constant if available; else keep default 0 if (ARToolkit && typeof ARToolkit.PATTERN_MARKER === 'number') { @@ -169,7 +164,7 @@ async function initArtoolkit(width = 640, height = 480) { || 'https://raw.githack.com/AR-js-org/AR.js/master/data/data/camera_para.dat'; console.log('[Worker] ARToolKit init', { width, height, camUrl, minConfidence: MIN_CONFIDENCE, patternType: PATTERN_MARKER_TYPE }); - arController = await ARToolkit.ARController.initWithDimensions(width, height, camUrl, {}); + arController = await ARController.initWithDimensions(width, height, camUrl, {}); arControllerInitialized = !!arController; console.log('[Worker] ARToolKit initialized:', arControllerInitialized); @@ -234,7 +229,10 @@ onMessage(async (ev) => { MIN_CONFIDENCE = payload.minConfidence; } } - sendMessage({ type: 'ready' }); + if (!hasAnnouncedReady) { + sendMessage({ type: 'ready' }); + hasAnnouncedReady = true; + } return; } @@ -264,7 +262,7 @@ onMessage(async (ev) => { if (type === 'processFrame') { const { imageBitmap, width, height } = payload || {}; - if (!isNodeWorker && imageBitmap) { + if (imageBitmap) { try { const w = width || imageBitmap.width || 640; const h = height || imageBitmap.height || 480; @@ -299,10 +297,19 @@ onMessage(async (ev) => { return; } + // Non-ImageBitmap path: noop await new Promise((r) => setTimeout(r, 5)); return; } } catch (err) { sendMessage({ type: 'error', payload: { message: err?.message || String(err) } }); } -}); \ No newline at end of file +}); + +// Announce ready right after load, in case 'init' is delayed +try { + if (!hasAnnouncedReady) { + sendMessage({ type: 'ready' }); + hasAnnouncedReady = true; + } +} catch {} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6e331b3 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + lib: { + entry: 'src/index.js', + name: 'ARjsPluginARtoolkit', + fileName: (format) => `arjs-plugin-artoolkit.${format}.js`, + formats: ['es'], // ESM-only build + }, + rollupOptions: { + output: { + // Do NOT override entryFileNames so Vite uses lib.fileName for the entry + // Worker and other assets will still be emitted under assets/ + assetFileNames: (assetInfo) => { + if (assetInfo.name && assetInfo.name.includes('worker')) { + return 'assets/[name]-[hash][extname]'; + } + return 'assets/[name]-[hash][extname]'; + }, + }, + }, + sourcemap: true, + target: 'esnext', + }, + worker: { + format: 'es', + }, +}); \ No newline at end of file From a2e3036aaae06f09a79f5996be72abd0d538887a Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 01:31:55 +0100 Subject: [PATCH 02/10] fix(worker): correct ARController export extraction logic --- src/worker/worker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/worker/worker.js b/src/worker/worker.js index 44f0927..8b33e2c 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -136,10 +136,10 @@ async function initArtoolkit(width = 640, height = 480) { })(); // Safely extract exports (supports both named and default exports) - /*const ARController = + const ARController = jsartoolkit.ARController ?? jsartoolkit.default?.ARController; const ARToolkit = - jsartoolkit.ARToolkit ?? jsartoolkit.default?.ARToolkit;*/ + jsartoolkit.ARToolkit ?? jsartoolkit.default?.ARToolkit; if (!ARController) { throw new Error('ARController export not found in ARToolKit module'); From 0a9b8cb180af755c2d226a95cd6c79394363e269 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 13:40:18 +0100 Subject: [PATCH 03/10] feat(plugin): improve worker readiness handling and fallback logic --- examples/simple-marker/index.html | 20 +++++++++++++------- src/plugin.js | 13 ++++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/simple-marker/index.html b/examples/simple-marker/index.html index 16eb344..8f2076e 100644 --- a/examples/simple-marker/index.html +++ b/examples/simple-marker/index.html @@ -72,29 +72,35 @@

Event Log:

log('Creating ArtoolkitPlugin instance (dist build)…'); plugin = new ArtoolkitPlugin({ worker: true, - cameraParametersUrl: '/examples/simple-marker/data/camera_para.dat', - // minConfidence: 0.6, + cameraParametersUrl: '/examples/simple-marker/data/camera_para.dat' }); - await plugin.init(core); - await plugin.enable(); + // Register listeners BEFORE enable to avoid missing early 'ready' eventBus.on('ar:workerReady', () => { log('Worker ready'); setStatus('Worker ready. Start camera and then load marker.', 'success'); loadMarkerBtn.disabled = false; }); - eventBus.on('ar:workerError', e => { log(`workerError: ${JSON.stringify(e)}`); setStatus('Worker error (see console)', 'error'); }); - eventBus.on('ar:getMarker', (e) => console.log('[example] ar:getMarker', e)); eventBus.on('ar:markerFound', d => log(`markerFound: ${JSON.stringify(d)}`)); eventBus.on('ar:markerUpdated', d => log(`markerUpdated: ${JSON.stringify(d)}`)); eventBus.on('ar:markerLost', d => log(`markerLost: ${JSON.stringify(d)}`)); - setStatus('Plugin initialized. Waiting for worker…', 'normal'); + await plugin.init(core); + await plugin.enable(); + + // Fallback: if worker became ready during enable, honor it + if (plugin.workerReady) { + log('Worker was already ready (post-enable).'); + setStatus('Worker ready. Start camera and then load marker.', 'success'); + loadMarkerBtn.disabled = false; + } else { + setStatus('Plugin initialized. Waiting for worker…', 'normal'); + } } catch (err) { log('Init error: ' + (err && err.message ? err.message : err)); setStatus('Initialization error', 'error'); diff --git a/src/plugin.js b/src/plugin.js index 66aeecb..3bc0d97 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -39,6 +39,7 @@ export class ArtoolkitPlugin { // Pending loadMarker requests: Map this._pendingMarkerLoads = new Map(); this._nextLoadRequestId = 0; + this.workerReady = false; } async init(core) { @@ -165,9 +166,13 @@ export class ArtoolkitPlugin { wasmBaseUrl: this.options.wasmBaseUrl || null } }); - } catch (e) { - // ignore - } + // Watchdog: if 'ready' wasn’t received shortly, resend a no-op init once + setTimeout(() => { + if (!this.workerReady) { + try { this._worker?.postMessage?.({ type: 'init', payload: {} }); } catch {} + } + }, 500); + } catch (e) {} } _stopWorker() { @@ -197,6 +202,8 @@ export class ArtoolkitPlugin { const data = ev && ev.data !== undefined ? ev.data : ev; const { type, payload } = data || {}; if (type === 'ready') { + console.log('[Plugin] Worker ready'); + this.workerReady = true; this.core?.eventBus?.emit('ar:workerReady', {}); } else if (type === 'detectionResult') { console.log('[Plugin] Received detectionResult:', payload); From 800e5d2de16e793ccb6107ef1d47e09cd3701c1c Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 13:53:50 +0100 Subject: [PATCH 04/10] feat(build): set relative asset URLs and simplify asset file naming in Vite config --- vite.config.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 6e331b3..e05322c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from 'vite'; export default defineConfig({ + // Ensure asset URLs are relative to the built module (not absolute at site root) + base: './', build: { lib: { entry: 'src/index.js', @@ -10,14 +12,9 @@ export default defineConfig({ }, rollupOptions: { output: { - // Do NOT override entryFileNames so Vite uses lib.fileName for the entry - // Worker and other assets will still be emitted under assets/ - assetFileNames: (assetInfo) => { - if (assetInfo.name && assetInfo.name.includes('worker')) { - return 'assets/[name]-[hash][extname]'; - } - return 'assets/[name]-[hash][extname]'; - }, + // Keep assets under assets/; relative path is enforced by base: './' + assetFileNames: 'assets/[name]-[hash][extname]', + // Let Vite/rollup choose relative paths tied to the lib entry; no need to force chunks dirs }, }, sourcemap: true, From a461a86e1f9859c17f4b1112105a5180a2690ae3 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 14:14:07 +0100 Subject: [PATCH 05/10] feat(tests): add unit tests for ArtoolkitPlugin and configure coverage reporting --- package-lock.json | 576 ++++++++++++++++++++++++++++++++++++++++++- package.json | 10 +- tests/plugin.spec.ts | 103 ++++++++ tests/setupTests.ts | 32 +++ vitest.config.ts | 19 ++ 5 files changed, 735 insertions(+), 5 deletions(-) create mode 100644 tests/plugin.spec.ts create mode 100644 tests/setupTests.ts create mode 100644 vitest.config.ts diff --git a/package-lock.json b/package-lock.json index 2b05c46..e04ff52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,20 @@ "@ar-js-org/artoolkit5-js": "^0.3.2" }, "devDependencies": { - "eslint": "^9.39.1", + "eslint": "^9.39.0", "husky": "^9.1.7", + "jsdom": "^27.1.0", "vite": "^7.1.12", "vitest": "^4.0.6" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.19.tgz", + "integrity": "sha512-Pp2gAQXPZ2o7lt4j0IMwNRXqQ3pagxtDj5wctL5U2Lz4oV0ocDNlkgx4DpxfyKav4S/bePuI+SMqcBSUHLy9kg==", + "dev": true, + "license": "MIT" + }, "node_modules/@ar-js-org/artoolkit5-js": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@ar-js-org/artoolkit5-js/-/artoolkit5-js-0.3.2.tgz", @@ -28,6 +36,41 @@ "axios": "1.13.1" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", + "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -37,6 +80,141 @@ "node": ">=6.9.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz", + "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -1136,6 +1314,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1210,6 +1398,16 @@ "dev": true, "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1325,6 +1523,49 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.2.tgz", + "integrity": "sha512-zDMqXh8Vs1CdRYZQ2M633m/SFgcjlu8RB8b/1h82i+6vpArF507NSYIWJHGlJaTWoS+imcnctmEz43txhbVkOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1343,6 +1584,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1373,6 +1621,19 @@ "node": ">= 0.4" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1928,6 +2189,47 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -1944,6 +2246,19 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2004,6 +2319,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2024,6 +2346,46 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz", + "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.19", + "@asamuzakjp/dom-selector": "^6.7.3", + "cssstyle": "^5.3.2", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2092,6 +2454,16 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2111,6 +2483,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2241,6 +2620,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2343,6 +2735,16 @@ "node": ">=6" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2395,6 +2797,26 @@ "fsevents": "~2.3.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2475,6 +2897,13 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2516,6 +2945,52 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.17" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2719,6 +3194,66 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2762,6 +3297,45 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 437d39d..73312ed 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "scripts": { "build": "vite build", "test": "vitest", + "coverage": "vitest run --coverage", "lint": "eslint . --ext .js", "prepare": "husky install" }, @@ -29,12 +30,13 @@ "author": "AR.js contributors", "license": "MIT", "devDependencies": { - "eslint": "^9.39.1", + "eslint": "^9.39.0", "husky": "^9.1.7", - "vitest": "^4.0.6", - "vite": "^7.1.12" + "jsdom": "^27.1.0", + "vite": "^7.1.12", + "vitest": "^4.0.6" }, "dependencies": { "@ar-js-org/artoolkit5-js": "^0.3.2" } -} \ No newline at end of file +} diff --git a/tests/plugin.spec.ts b/tests/plugin.spec.ts new file mode 100644 index 0000000..fc6616e --- /dev/null +++ b/tests/plugin.spec.ts @@ -0,0 +1,103 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ArtoolkitPlugin } from '../src/plugin.js'; +import { createEventBus } from './setupTests'; + +describe('ArtoolkitPlugin', () => { + let core: { eventBus: ReturnType }; + + beforeEach(() => { + core = { eventBus: createEventBus() }; + }); + + it('initializes and enables without starting a real Worker', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + await plugin.enable(); + expect(plugin.enabled).toBe(true); + + await plugin.disable(); + expect(plugin.enabled).toBe(false); + }); + + it('emits markerFound then markerUpdated on detectionResult payloads', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + await plugin.enable(); + + const found = vi.fn(); + const updated = vi.fn(); + core.eventBus.on('ar:markerFound', found); + core.eventBus.on('ar:markerUpdated', updated); + + // Simulate first detection of id=1 + // @ts-ignore private method used intentionally for test + plugin._onWorkerMessage({ + data: { + type: 'detectionResult', + payload: { + detections: [ + { id: 1, confidence: 0.92, poseMatrix: new Array(16).fill(0), corners: [] } + ] + } + } + }); + + // Simulate an update for the same marker + // @ts-ignore + plugin._onWorkerMessage({ + data: { + type: 'detectionResult', + payload: { + detections: [ + { id: 1, confidence: 0.88, poseMatrix: new Array(16).fill(1), corners: [] } + ] + } + } + }); + + expect(found).toHaveBeenCalledTimes(1); + expect(updated).toHaveBeenCalledTimes(1); + // Optional shape assertions + const first = found.mock.calls[0][0]; + expect(first.id).toBe(1); + expect(first.poseMatrix).toBeInstanceOf(Float32Array); + expect(first.poseMatrix.length).toBe(16); + }); + + it('resolves loadMarker promises when worker replies', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + + // Attach a stub worker so loadMarker can postMessage + const postMessage = vi.fn(); + // @ts-ignore + plugin._worker = { postMessage }; + + const p = plugin.loadMarker('/pattern.patt', 1); + + // Simulate worker response with requestId=0 (first call) + // @ts-ignore + plugin._onWorkerMessage({ + data: { + type: 'loadMarkerResult', + payload: { ok: true, markerId: 42, size: 1, requestId: 0 } + } + }); + + await expect(p).resolves.toEqual({ markerId: 42, size: 1 }); + expect(postMessage).toHaveBeenCalledTimes(1); + }); + + it('emits ar:workerReady on ready message', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + + const ready = vi.fn(); + core.eventBus.on('ar:workerReady', ready); + + // @ts-ignore + plugin._onWorkerMessage({ data: { type: 'ready', payload: {} } }); + + expect(ready).toHaveBeenCalledTimes(1); + }); +}); \ No newline at end of file diff --git a/tests/setupTests.ts b/tests/setupTests.ts new file mode 100644 index 0000000..a41c4ec --- /dev/null +++ b/tests/setupTests.ts @@ -0,0 +1,32 @@ +// Basic jsdom setup and minimal Worker mock (only if needed in other tests). +// These tests avoid creating a real Worker; we test plugin logic by simulating messages. + +class MockWorker { + addEventListener() {} + removeEventListener() {} + postMessage() {} + terminate() {} +} +// Only define if not present, to avoid clobbering if jsdom/vitest adds one later +if (typeof globalThis.Worker === 'undefined') { + // @ts-ignore + globalThis.Worker = MockWorker as any; +} + +// Tiny event bus used by tests (mimics the plugin's expected interface) +export function createEventBus() { + const map = new Map(); + return { + on(e: string, fn: Function) { + if (!map.has(e)) map.set(e, []); + map.get(e)!.push(fn); + }, + off(e: string, fn: Function) { + if (!map.has(e)) return; + map.set(e, map.get(e)!.filter((x) => x !== fn)); + }, + emit(e: string, payload?: any) { + (map.get(e) || []).forEach((fn) => fn(payload)); + } + }; +} \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..75b2f0f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./tests/setupTests.ts'], + coverage: { + provider: 'v8', + all: true, + include: ['src/**/*.js'], + thresholds: { + lines: 60, + statements: 60, + branches: 50, + functions: 60 + } + } + } +}); \ No newline at end of file From 1c3d0159628bcec77e7442d6fba17c82f2d6030c Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 14:21:38 +0100 Subject: [PATCH 06/10] feat(tests): add extra coverage tests for ArtoolkitPlugin and update coverage configuration --- package-lock.json | 235 +++++++++++++++++++++++++++++++++++++ package.json | 1 + tests/plugin.extra.spec.ts | 97 +++++++++++++++ vitest.config.ts | 17 +-- 4 files changed, 343 insertions(+), 7 deletions(-) create mode 100644 tests/plugin.extra.spec.ts diff --git a/package-lock.json b/package-lock.json index e04ff52..efa4d78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@ar-js-org/artoolkit5-js": "^0.3.2" }, "devDependencies": { + "@vitest/coverage-v8": "^4.0.6", "eslint": "^9.39.0", "husky": "^9.1.7", "jsdom": "^27.1.0", @@ -71,6 +72,42 @@ "dev": true, "license": "MIT" }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -80,6 +117,30 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -853,6 +914,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -860,6 +931,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.5", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", @@ -1207,6 +1289,38 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.6.tgz", + "integrity": "sha512-cv6pFXj9/Otk7q1Ocoj8k3BUVVwnFr3jqcqpwYrU5LkKClU9DpaMEdX+zptx/RyIJS+/VpoxMWmfISXchmVDPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.6", + "ast-v8-to-istanbul": "^0.3.5", + "debug": "^4.4.3", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.6", + "vitest": "4.0.6" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.6.tgz", @@ -1374,6 +1488,18 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2202,6 +2328,13 @@ "node": ">=18" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -2333,6 +2466,67 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2474,6 +2668,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2817,6 +3039,19 @@ "node": ">=v12.22.7" } }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 73312ed..287cdca 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "author": "AR.js contributors", "license": "MIT", "devDependencies": { + "@vitest/coverage-v8": "^4.0.6", "eslint": "^9.39.0", "husky": "^9.1.7", "jsdom": "^27.1.0", diff --git a/tests/plugin.extra.spec.ts b/tests/plugin.extra.spec.ts new file mode 100644 index 0000000..55ca5d3 --- /dev/null +++ b/tests/plugin.extra.spec.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ArtoolkitPlugin } from '../src/plugin.js'; +import { createEventBus } from './setupTests'; + +describe('ArtoolkitPlugin (extra coverage)', () => { + let core: { eventBus: ReturnType }; + beforeEach(() => { + core = { eventBus: createEventBus() }; + }); + + it('forwards ar:getMarker payloads to the event bus', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + + const handler = vi.fn(); + core.eventBus.on('ar:getMarker', handler); + + // @ts-ignore private use for testing + plugin._onWorkerMessage({ data: { type: 'getMarker', payload: { type: 0, matrix: new Array(16).fill(1), marker: { idPatt: 7, cfPatt: 0.9 } } } }); + + expect(handler).toHaveBeenCalledTimes(1); + const payload = handler.mock.calls[0][0]; + expect(Array.isArray(payload.matrix) || payload.matrix instanceof Float32Array).toBeTruthy(); + }); + + it('emits ar:workerError on error messages', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + + const errListener = vi.fn(); + core.eventBus.on('ar:workerError', errListener); + + // @ts-ignore + plugin._onWorkerMessage({ data: { type: 'error', payload: { message: 'boom' } } }); + + expect(errListener).toHaveBeenCalledTimes(1); + expect(errListener.mock.calls[0][0]).toEqual({ message: 'boom' }); + }); + + it('sweeps markers and emits ar:markerLost when lastSeen is stale', async () => { + const plugin = new ArtoolkitPlugin({ worker: false, lostThreshold: 1, frameDurationMs: 1 }); + await plugin.init(core); + await plugin.enable(); + + const lost = vi.fn(); + core.eventBus.on('ar:markerLost', lost); + + // Seed a marker that was seen long ago + // @ts-ignore access internals for test + plugin._markers.set(123, { lastSeen: Date.now() - 10, visible: true, lostCount: 0 }); + + // @ts-ignore invoke internal sweep + plugin._sweepMarkers(); + + expect(lost).toHaveBeenCalledTimes(1); + expect(lost.mock.calls[0][0].id).toBe(123); + // marker removed + expect(plugin.getMarkerState(123)).toBeNull(); + }); + + it('posts processFrame for imageBitmap frames', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + + const postMessage = vi.fn(); + // @ts-ignore + plugin._worker = { postMessage }; + + // Simulate engine:update with an ImageBitmap-like object + const fakeBitmap = {} as ImageBitmap; + // @ts-ignore call private + plugin._onEngineUpdate({ id: 1, imageBitmap: fakeBitmap, width: 100, height: 50 }); + + expect(postMessage).toHaveBeenCalledTimes(1); + const arg = postMessage.mock.calls[0][0]; + expect(arg.type).toBe('processFrame'); + expect(arg.payload.width).toBe(100); + expect(arg.payload.height).toBe(50); + }); + + it('rejects loadMarker on timeout when no worker reply', async () => { + vi.useFakeTimers(); + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + const postMessage = vi.fn(); + // @ts-ignore + plugin._worker = { postMessage }; + + const p = plugin.loadMarker('/never-responds.patt', 1); + + // advance the 10s timeout + vi.advanceTimersByTime(10000); + + await expect(p).rejects.toThrow(/timed out/i); + vi.useRealTimers(); + }); +}); \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts index 75b2f0f..cd0a22f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,12 +8,15 @@ export default defineConfig({ provider: 'v8', all: true, include: ['src/**/*.js'], + // Exclude files that are either pure re-exports or not yet unit-testable (worker) + exclude: ['src/worker/**', 'src/index.js'], thresholds: { - lines: 60, - statements: 60, - branches: 50, - functions: 60 - } - } - } + // Slightly relaxed thresholds while we build out more tests + lines: 50, + statements: 50, + branches: 40, + functions: 50, + }, + }, + }, }); \ No newline at end of file From 2d3712905ad5936dee5f14207f0bcb71f9986dad Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 14:37:22 +0100 Subject: [PATCH 07/10] feat(ci): add CI configuration and coverage tests for ArtoolkitPlugin --- .github/workflows/build.yml | 31 +++++++++++++++++ tests/plugin.more.spec.ts | 66 +++++++++++++++++++++++++++++++++++++ vitest.config.ts | 10 +++--- 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 tests/plugin.more.spec.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..df98373 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install + run: npm ci + + - name: Build + run: npm run build + + - name: Test with coverage + run: npm run coverage diff --git a/tests/plugin.more.spec.ts b/tests/plugin.more.spec.ts new file mode 100644 index 0000000..3f3806c --- /dev/null +++ b/tests/plugin.more.spec.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ArtoolkitPlugin } from '../src/plugin.js'; +import { createEventBus } from './setupTests'; + +describe('ArtoolkitPlugin (more coverage)', () => { + let core: { eventBus: ReturnType }; + + beforeEach(() => { + core = { eventBus: createEventBus() }; + }); + + it('disable() removes handlers and terminates worker', async () => { + const plugin = new ArtoolkitPlugin({ worker: true }); + await plugin.init(core); + + // Fake a browser worker with spies + const addEventListener = vi.fn(); + const removeEventListener = vi.fn(); + const terminate = vi.fn(); + // @ts-ignore + plugin._worker = { addEventListener, removeEventListener, terminate, postMessage: vi.fn() }; + + await plugin.enable(); + // Simulate that we added a message listener during start + expect(typeof plugin.enabled).toBe('boolean'); + + await plugin.disable(); + + expect(removeEventListener).toHaveBeenCalledWith('message', expect.any(Function)); + expect(terminate).toHaveBeenCalledTimes(1); + }); + + it('engine:update falls back when postMessage throws', async () => { + const plugin = new ArtoolkitPlugin({ worker: true }); + await plugin.init(core); + + const postMessage = vi.fn(() => { throw new Error('boom'); }); + // @ts-ignore + plugin._worker = { postMessage }; + + // No throw should propagate + // @ts-ignore call private + plugin._onEngineUpdate({ id: 99, imageBitmap: {} as ImageBitmap, width: 2, height: 2 }); + + // Fallback tries a second post without ImageBitmap, so we expect at least one call + expect(postMessage).toHaveBeenCalled(); + }); + + it('getMarkerState returns null when marker not tracked', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + expect(plugin.getMarkerState(12345)).toBeNull(); + }); + + it('detectionResult with no detections is safely ignored', async () => { + const plugin = new ArtoolkitPlugin({ worker: false }); + await plugin.init(core); + await plugin.enable(); + + // @ts-ignore + plugin._onWorkerMessage({ data: { type: 'detectionResult', payload: {} } }); + + // No exception; no markers added + expect(plugin.getMarkerState(1)).toBeNull(); + }); +}); \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts index cd0a22f..3769a6c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,14 +8,12 @@ export default defineConfig({ provider: 'v8', all: true, include: ['src/**/*.js'], - // Exclude files that are either pure re-exports or not yet unit-testable (worker) exclude: ['src/worker/**', 'src/index.js'], thresholds: { - // Slightly relaxed thresholds while we build out more tests - lines: 50, - statements: 50, - branches: 40, - functions: 50, + lines: 65, + statements: 65, + branches: 50, + functions: 65, }, }, }, From 742047762e37bb9b985243f817f00c53a6c4461f Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 4 Nov 2025 14:59:11 +0100 Subject: [PATCH 08/10] feat(docs): update README and index.html for ESM module usage and paths --- README.md | 61 +++++++++++++++++++++++-------- examples/simple-marker/README.md | 28 ++++++++------ examples/simple-marker/index.html | 2 +- vite.config.ts | 2 +- 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 14b37af..fde132a 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ ARToolKit marker detection plugin for AR.js core with WebAssembly support. ## Features -- Web Worker-based detection — marker detection runs off the main thread +- Web Worker-based detection — marker detection runs off the main thread (Browser Module Worker) - ImageBitmap support — zero-copy frame transfer (browser) -- Cross-platform — browser Workers and Node.js worker_threads (stub path) - ARToolKit integration — square pattern markers - Event-driven API — marker found/updated/lost + raw getMarker forwarding - Filtering — only forwards PATTERN_MARKER events above a minimum confidence @@ -17,26 +16,53 @@ ARToolKit marker detection plugin for AR.js core with WebAssembly support. npm install @ar-js-org/arjs-plugin-artoolkit ``` -## Configuration (module + assets) +## Using the ESM build (recommended) -To ensure the Worker can import ARToolKit in the browser, pass an explicit ESM URL (recommended): +When you import the built ESM bundle from `dist/`, the worker and ARToolKit are already bundled and referenced correctly. You do NOT need to pass `artoolkitModuleUrl`. + +Example: + +```html + +``` + +Serving notes: +- Serve from a web server so `/dist` assets resolve. The build is configured with `base: './'`, so the worker asset is referenced relative to the ESM file (e.g., `/dist/assets/worker-*.js`). +- In your own apps, place `dist/` where you serve static assets and import the ESM with the appropriate path (absolute or relative). + +## Using source (development mode) + +If you develop against `src/` (not the built `dist/`), the worker will attempt to dynamically import ARToolKit. In that case you should provide `artoolkitModuleUrl` or ensure your dev server can resolve `@ar-js-org/artoolkit5-js`. ```js const plugin = new ArtoolkitPlugin({ worker: true, - artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', // explicit ESM - cameraParametersUrl: '/path/to/camera_para.dat', // optional override - wasmBaseUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/', // optional; if your build needs it - minConfidence: 0.6 // forward only confident detections + artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', // provide when using src/ + cameraParametersUrl: '/path/to/camera_para.dat', + wasmBaseUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/', // optional; if your build requires it + minConfidence: 0.6 }); ``` -CDN fallback (if you don’t serve node_modules): -- Set `artoolkitModuleUrl` to a CDN ESM endpoint, e.g. a jsDelivr/UNPKG URL for the package’s ESM bundle. +CDN fallback (for source/dev): +- Set `artoolkitModuleUrl` to a CDN ESM endpoint (e.g., jsDelivr/UNPKG) for `@ar-js-org/artoolkit5-js`. Notes: - The previous “loader.js” and manual WASM placement flow is no longer used. -- If your ARToolKit build fetches auxiliary assets (WASM, data), set `wasmBaseUrl` accordingly. +- In the `dist/` build, ARToolKit is bundled and `artoolkitModuleUrl` is NOT needed. ## Usage @@ -49,7 +75,7 @@ const plugin = new ArtoolkitPlugin({ worker: true, lostThreshold: 5, // frames before a marker is considered lost frameDurationMs: 100, // expected ms per frame (affects lost timing) - artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', + // artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', // Only for src/dev cameraParametersUrl: '/data/camera_para.dat', minConfidence: 0.6 }); @@ -114,7 +140,7 @@ const { markerId, size } = await plugin.loadMarker('/examples/simple-marker/data A complete webcam-based example is available under `examples/simple-marker/`. -Serve from the repository root so that ES modules and node_modules paths resolve: +Serve from the repository root so that `dist/` and example paths resolve: ```bash # From repository root @@ -142,9 +168,9 @@ The example demonstrates: lostThreshold?: number; // Frames before 'lost' (default: 5) frameDurationMs?: number; // ms per frame (default: 200) sweepIntervalMs?: number; // Lost-sweep interval (default: 100) - artoolkitModuleUrl?: string; // ESM URL for ARToolKit (recommended) + artoolkitModuleUrl?: string; // Only needed when using source/dev; NOT needed for dist build cameraParametersUrl?: string;// Camera params file URL - wasmBaseUrl?: string; // Base URL for ARToolKit assets (if required) + wasmBaseUrl?: string; // Base URL for ARToolKit assets (if required by your build) minConfidence?: number; // Minimum confidence to forward getMarker (default: 0.6) } ``` @@ -160,7 +186,10 @@ The example demonstrates: ## Troubleshooting -- “Failed to resolve module specifier” in the Worker: +- Worker asset 404: + - Ensure you import the ESM from `/dist/arjs-plugin-artoolkit.esm.js` and that `/dist/assets/worker-*.js` is served. + - The build uses `base: './'`, so worker URLs are relative to the ESM file location. +- “Failed to resolve module specifier” in the Worker (source/dev only): - Provide `artoolkitModuleUrl` or serve `/node_modules` from your dev server - Worker not starting: - Serve via HTTP/HTTPS; ensure ES modules and Workers are supported diff --git a/examples/simple-marker/README.md b/examples/simple-marker/README.md index 3ae49a5..5defecc 100644 --- a/examples/simple-marker/README.md +++ b/examples/simple-marker/README.md @@ -15,8 +15,8 @@ npm install ### 2. Serve the Example You must serve from the repository root so that: -- ES modules resolve (../../src/plugin.js) -- The worker module URL (../../node_modules/...) is reachable +- The built ESM (`/dist/arjs-plugin-artoolkit.esm.js`) and worker asset (`/dist/assets/worker-*.js`) resolve +- Example paths under `/examples/simple-marker/` resolve You can use any static file server. Examples: @@ -56,19 +56,26 @@ If you're using VS Code with the Live Server extension: ## Module resolution -The example config (in `index.html`) passes explicit URLs so the worker can import ARToolKit and camera params: +When importing the built ESM from `dist/`, ARToolKit is bundled and no extra configuration is required: ```js +import { ArtoolkitPlugin } from '/dist/arjs-plugin-artoolkit.esm.js'; + const plugin = new ArtoolkitPlugin({ worker: true, - artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', cameraParametersUrl: '/examples/simple-marker/data/camera_para.dat' }); ``` -If your server can’t serve `/node_modules`, either: -- Adjust `artoolkitModuleUrl` to a path your server exposes, or -- Use a CDN ESM URL as a fallback (see project README for details) +If you develop against `src/` instead, provide an explicit ARToolKit module URL: + +```js +const plugin = new ArtoolkitPlugin({ + worker: true, + artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', + cameraParametersUrl: '/examples/simple-marker/data/camera_para.dat' +}); +``` ## What’s Happening @@ -93,10 +100,9 @@ The `data/patt.hiro` file is a standard ARToolKit pattern. You can replace it wi Key parts of the example: ```javascript -// Create plugin instance with worker enabled and explicit module/params URLs +// Create plugin instance with worker enabled (no artoolkitModuleUrl needed with dist) const plugin = new ArtoolkitPlugin({ worker: true, - artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', cameraParametersUrl: '/examples/simple-marker/data/camera_para.dat' }); @@ -113,9 +119,7 @@ console.log(`Marker loaded with ID: ${result.markerId}`); - Worker not loading? - Ensure you’re serving via HTTP/HTTPS from the repository root (not `file://`) - - Check console for module resolution/CORS errors -- Module import errors? - - Make sure `/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js` is reachable, or use a CDN URL + - Confirm `/dist/arjs-plugin-artoolkit.esm.js` and `/dist/assets/worker-*.js` are reachable - Marker not loading? - Verify the pattern file path is correct and accessible - Ensure the worker is ready before calling `loadMarker()` diff --git a/examples/simple-marker/index.html b/examples/simple-marker/index.html index 8f2076e..363d9a2 100644 --- a/examples/simple-marker/index.html +++ b/examples/simple-marker/index.html @@ -33,7 +33,7 @@

Event Log: